omni-to-snowflake-semantic-view

Par exploreomni · omni-agent-skills

Convertit un topic Omni Analytics en définition YAML de Snowflake Semantic View. Utilise cette skill chaque fois que quelqu'un souhaite exporter des métriques Omni vers Snowflake, créer une Semantic View à partir d'un topic Omni, consolider des métriques BI dans le warehouse, ou relier la couche sémantique d'Omni à Snowflake Cortex Analyst.

npx skills add https://github.com/exploreomni/omni-agent-skills --skill omni-to-snowflake-semantic-view

Omni → Snowflake Semantic View

Convertit un topic Omni en une définition YAML de Snowflake Semantic View en explorant d'abord le modèle Omni via API, puis en traduisant ses définitions au format Snowflake Semantic View.


Prérequis

# Vérifiez que l'Omni CLI est installé — sinon, demandez à l'utilisateur de l'installer
# Voir : https://github.com/exploreomni/cli#readme
command -v omni >/dev/null || echo "ERROR: Omni CLI is not installed."
# Affiche les profils disponibles et en sélectionne un
omni config show
# S'il existe plusieurs profils, demandez à l'utilisateur lequel utiliser, puis basculez :
omni config use <profile-name>

Conseil : Utilisez -o json pour forcer la sortie structurée en analyse programmatique, ou -o human pour des tableaux lisibles. La valeur par défaut est auto (lisible en TTY, JSON quand redirigé).

Détection de l'environnement d'exécution

Avant de commencer, déterminez l'environnement d'exécution — cela contrôle la manière dont le SQL est exécuté sur Snowflake à l'étape 7.

Comment détecter

Vérifiez la présence de la capacité native d'exécution de SQL Snowflake :

  • Cortex Code (Snowflake Notebooks / Cortex Analyst) : l'exécution SQL native est disponible. Aucun outil supplémentaire requis.
  • Claude Code, Cursor, ou tout IDE/agent externe : pas de connexion Snowflake native. Vous devez utiliser l'une des options CLI ci-dessous.

Heuristique de détection

Si l'agent peut exécuter une instruction SQL comme `SELECT CURRENT_USER()` directement sans aucune commande shell → vous êtes dans Cortex Code.
Sinon → vous êtes dans un environnement externe.

En cas de doute, demandez à l'utilisateur : « Exécutez-vous cela dans Snowflake (Cortex Code / Notebooks), ou dans un outil externe comme Claude Code ou Cursor ? »

Environnement externe : options de connectivité Snowflake

Pour Claude Code, Cursor, ou tout agent basé sur terminal, choisissez l'une des options suivantes. Vérifiez la disponibilité dans cet ordre :

Option A — Snowflake CLI (snow) ✅ Recommandé

# Vérifiez la disponibilité
command -v snow

# Configurez une connexion (première fois)
snow connection add

# Exécutez le SQL
snow sql -q "SELECT CURRENT_USER();" --connection <connection_name>

Définissez une connexion par défaut pour éviter de répéter --connection à chaque appel :

snow connection set-default <connection_name>

Option B — SnowSQL (CLI classique)

# Vérifiez la disponibilité
command -v snowsql

# Exécutez le SQL
snowsql -a <account> -u <user> -q "SELECT CURRENT_USER();"

Option C — Python (snowflake-connector-python)

pip install snowflake-connector-python
import snowflake.connector
conn = snowflake.connector.connect(
    account="<account>",
    user="<user>",
    password="<password>",   # ou utilisez key-pair / SSO
    warehouse="<warehouse>",
    database="<database>",
    schema="<schema>",
)
conn.cursor().execute("<SQL here>")

ARRÊTEZ — Confirmez la méthode de connexion Snowflake avec l'utilisateur avant de passer à l'étape 7. Enregistrez quelle méthode est utilisée afin que l'étape 7 génère la bonne commande d'exécution.


Flux de travail

Étape 1 — Recueillir les exigences

Demandez à l'utilisateur :

  1. Quel Topic veut-il convertir ?
  2. Quel nom doit avoir la Semantic View ?
  3. Où doit-elle être créée dans Snowflake (base de données et schéma) ?
  4. Quel rôle doit avoir accès à la Semantic View ?

⚠️ ARRÊTEZ — Confirmez les quatre réponses avant de continuer.


Étape 2 — Explorer le modèle Omni

2a. Trouvez l'ID du modèle

omni models list --modelkind SHARED

Identifiez le Shared Model et notez son id. Préférez toujours le Shared Model aux modèles Schema ou Workbook.

2b. Récupérez le fichier topic

omni models yaml-get <modelId> --filename <topic_name>.topic

2c. Récupérez le fichier relationships

omni models yaml-get <modelId> --filename relationships

2d. Récupérez chaque fichier view référencé dans le topic

Pour chaque view dans base_view et joins, récupérez son YAML :

omni models yaml-get <modelId> --filename <view_name>.view

Si une view est préfixée par omni_dbt_, récupérez le fichier qui commence aussi par omni_dbt_ (ex. omni_dbt_ecomm__order_items.view).


Étape 3 — Identifier les tables et les jointures

Mappage des noms de view aux tables Snowflake

La base_view du fichier topic est la table primaire. Convertissez les noms de view en références de table Snowflake :

base_view / valeur de join Table Snowflake
ecomm__order_items ECOMM.ORDER_ITEMS
omni_dbt_ecomm__order_items ECOMM.ORDER_ITEMS (supprimez omni_dbt_)

Le séparateur __ correspond au schéma (gauche) et au nom de table (droite). Si le schéma n'existe pas dans Snowflake, ignorez entièrement cette table.

Lecture de la hiérarchie des jointures

Le paramètre joins du topic utilise l'indentation pour définir la chaîne de jointure — une table indentée sous une autre se joint à sa parente :

joins:
  user_order_facts: {}        # ignorer — c'est une CTE dérivée, pas une table physique
  ecomm__users: {}            # se joint à base_view (ORDER_ITEMS)
  ecomm__inventory_items:     # se joint à base_view (ORDER_ITEMS)
    ecomm__products:          # se joint à INVENTORY_ITEMS
      demo__product_images: {}           # se joint à PRODUCTS
      ecomm__distribution_centers: {}   # se joint à PRODUCTS

Ignorez toute view qui est une table dérivée (CTE définie en SQL dans Omni). Elles n'ont pas de table Snowflake physique à référencer.

Clés primaires

Dans chaque fichier view, trouvez la dimension avec primary_key: true — celle-ci devient la dimension unique: true dans la Semantic View.

ARRÊTEZ — Confirmez la liste des tables avec l'utilisateur avant de continuer.


Étape 4 — Résoudre la liste des champs

Le paramètre fields du topic contrôle les champs des views inclus dans la Semantic View.

Règles de ciblage des champs

Syntaxe Signification
(pas de paramètre fields) Inclure tous les champs de toutes les views
all_views.* Inclure tous les champs de toutes les views
view.* Inclure tous les champs dans la view nommée
tag:<value> Inclure tous les champs marqués avec cette valeur
view.field Inclure ce champ spécifique
-view.field Exclure ce champ spécifique

Comment appliquer correctement les exclusions

Les exclusions (préfixées par -) doivent être appliquées après que toutes les inclusions soient résolues. Le processus est :

  1. Commencez avec un ensemble d'inclusion vide
  2. Traitez chaque entrée dans fields dans l'ordre :
    • Si c'est une règle d'inclusion (view.*, view.field, tag:x) → ajoutez les champs correspondants à l'ensemble
    • Si c'est une règle d'exclusion (-view.field) → supprimez ce champ de l'ensemble, même s'il a été ajouté par un wildcard
  3. L'ensemble final est la liste complète des champs à inclure dans la Semantic View

Exemple :

fields:
  - ecomm__order_items.*          # inclure tous les champs order_items
  - ecomm__users.country          # inclure ce seul champ users
  - -ecomm__order_items.cost      # supprimer cost — exclu même si * a été utilisé ci-dessus
  - -ecomm__order_items.raw_json  # supprimer raw_json — même raison

Résultat : tous les champs order_items sauf cost et raw_json, plus users.country.

⚠️ Critique : Une exclusion - gagne toujours. N'incluez jamais un champ qui a été explicitement exclu, peu importe ce que le wildcard a inclus.


Étape 5 — Construire les relations

En utilisant la hiérarchie des jointures de l'étape 3 et le relationships.yaml récupéré à l'étape 2c, mappez chaque jointure à une relation Snowflake Semantic View.

Chaque entrée dans relationships.yaml ressemble à :

- join_from_view: ecomm__order_items
  join_to_view: ecomm__inventory_items
  join_type: always_left
  on_sql: ${ecomm__order_items.inventory_item_id} = ${ecomm__inventory_items.id}
  relationship_type: assumed_many_to_one

Le champ on_sql vous indique les colonnes de jointure. Extrayez les noms de colonnes pour remplir relationship_columns dans la sortie.

Paramètres de relation disponibles :

Paramètre Description
join_from_view Vue source d'où provient la jointure
join_to_view Vue cible qui est jointe
join_type Type de jointure SQL (ex. always_left)
on_sql Condition SQL — extrayez les noms de colonnes de celle-ci
relationship_type Cardinalité (ex. assumed_many_to_one)
reversible Si la jointure est bidirectionnelle
where_sql Clause WHERE supplémentaire quand la jointure est active

Étape 6 — Mapper les dimensions et les mesures

Pour chaque view dans la liste des champs résolue, traduisez ses définitions de champ Omni en entrées Semantic View.

⚠️ Traduisez uniquement les champs qui ont survécu à la résolution inclusion/exclusion de l'étape 4. N'ajoutez pas les champs qui ont été exclus.

Dimensions → dimensions ou time_dimensions

Le nom du champ devient le nom de la dimension sauf si un label est défini. Portez description et synonyms directement.


Dimension standard :

# YAML de view Omni
city:
  sql: '"CITY"'
  label: City
  description: Customer's city
# Sortie Semantic View
- name: city
  expr: CITY
  description: Customer's city
  data_type: TEXT

Dimension date/timestamp → utilisez time_dimensions :

# Omni
created_at:
  sql: '"CREATED_AT"'
  type: time
  label: Created At
# Sortie Semantic View
time_dimensions:
  - name: created_at
    expr: CREATED_AT
    data_type: TIMESTAMP

Dimension de groupe → traduisez en expression CASE WHEN :

# Omni
device_type_groups:
  sql: ${device_type}
  label: Device Type Groups
  groups:
    - filter:
        is: [ mobile, tablet ]
      name: Handheld
    - filter:
        is: desktop
      name: Desktop
  else: Other
-- valeur expr
CASE
  WHEN "DEVICE_TYPE" IN ('mobile', 'tablet') THEN 'Handheld'
  WHEN "DEVICE_TYPE" = 'desktop' THEN 'Desktop'
  ELSE 'Other'
END

Dimension bin → traduisez en expression CASE WHEN de plage :

# Omni
age_bin:
  sql: ${age}
  bin_boundaries: [ 18, 35, 50, 65 ]
-- valeur expr
CASE
  WHEN "AGE" < 18 THEN 'below 18'
  WHEN "AGE" >= 18 AND "AGE" < 35 THEN '>= 18 and < 35'
  WHEN "AGE" >= 35 AND "AGE" < 50 THEN '>= 35 and < 50'
  WHEN "AGE" >= 50 AND "AGE" < 65 THEN '>= 50 and < 65'
  WHEN "AGE" >= 65 THEN '65 and above'
  ELSE NULL
END

Dimension de durée → traduisez en expression TIMESTAMPDIFF :

# Omni
fulfillment_days:
  duration:
    sql_start: ${created_at[date]}
    sql_end: ${delivered_at[date]}
    intervals: [ days ]
-- valeur expr (days)
CASE
  WHEN TIMESTAMPADD(DAY, 1 * TIMESTAMPDIFF(DAY, DATE_TRUNC('DAY', "CREATED_AT"), DATE_TRUNC('DAY', "DELIVERED_AT")), DATE_TRUNC('DAY', "CREATED_AT"))
       <= DATE_TRUNC('DAY', "DELIVERED_AT")
  THEN TIMESTAMPDIFF(DAY, DATE_TRUNC('DAY', "CREATED_AT"), DATE_TRUNC('DAY', "DELIVERED_AT"))
  ELSE TIMESTAMPDIFF(DAY, DATE_TRUNC('DAY', "CREATED_AT"), DATE_TRUNC('DAY', "DELIVERED_AT")) - 1
END

Dimension booléenne → devient un filter nommé (pas une dimension) :

# Omni
is_returned:
  sql: '"IS_RETURNED"'
  description: Whether the item was returned

completed_orders:
  sql: ${status} = 'Complete'
  label: Completed Orders
# Sortie Semantic View — va dans filters, pas dimensions
filters:
  - name: is_returned
    expr: IS_RETURNED
    description: Whether the item was returned
  - name: completed_orders
    expr: STATUS = 'Complete'
    description: Completed Orders

Mesures → metrics

Le champ sql référence la colonne source et aggregate_type définit l'agrégation.


Mesure standard :

# Omni
total_sale_price:
  sql: ${sale_price}
  aggregate_type: sum
  label: Total Sale Price
  description: Total revenue of orders
  synonyms: [ Total Revenue, Total Receipts ]
# Sortie Semantic View
metrics:
  - name: total_sale_price
    expr: COALESCE(SUM("SALE_PRICE"), 0)
    description: Total revenue of orders
    synonyms: [ Total Revenue, Total Receipts ]

Mesure dérivée (pas de aggregate_type) — référence d'autres mesures :

# Omni
gross_margin:
  sql: ${total_sale_price} - ${total_cost}
  label: Gross Margin
# Sortie Semantic View — métrique dérivée de haut niveau
metrics:
  - name: gross_margin
    expr: total_sale_price - total_cost

Mesure filtrée → enveloppe dans CASE WHEN :

# Omni
california_revenue:
  sql: ${sale_price}
  aggregate_type: sum
  filters:
    users.state:
      is: California
-- valeur expr
COALESCE(SUM(CASE WHEN "users"."STATE" = 'California' THEN "SALE_PRICE" ELSE NULL END), 0)

Variantes de filtres array et booléen :

# is: [New York, New Jersey]  →  "STATE" IN ('New York', 'New Jersey')
# is: true                    →  field IS TRUE

Contexte IA → module_custom_instructions

Si le topic a un paramètre ai_context, incluez-le comme :

module_custom_instructions:
  question_categorization: <valeur ai_context>

Exemples de requêtes → verified_queries

Convertissez chaque entrée sous sample_queries en instruction SQL pour verified_queries. Utilisez le prompt comme question et la description comme contexte pour écrire le SQL.

ARRÊTEZ — Examinez toutes les dimensions, mesures et relations avec l'utilisateur avant de générer la sortie finale.


Étape 7 — Générer et exécuter la Semantic View

Structure YAML de sortie

name: <name>
description: <string>

tables:
  - name: <name>
    description: <string>
    base_table:
      database: <database>
      schema: <schema>
      table: <table name>

    dimensions:
      - name: <name>
        synonyms: [ <string>, ... ]
        description: <string>
        expr: <SQL expression>
        data_type: <data type>
        unique: <boolean>

    time_dimensions:
      - name: <name>
        synonyms: [ <string>, ... ]
        description: <string>
        expr: <SQL expression>
        data_type: <data type>

    facts:
      - name: <name>
        synonyms: [ <string>, ... ]
        description: <string>
        expr: <SQL expression>
        data_type: <data type>

    metrics:
      - name: <name>
        synonyms: [ <string>, ... ]
        description: <string>
        expr: <SQL expression>

    filters:
      - name: <name>
        synonyms: [ <string>, ... ]
        description: <string>
        expr: <SQL expression>

relationships:
  - name: <string>
    left_table: <table>
    right_table: <table>
    relationship_columns:
      - left_column: <column>
        right_column: <column>

metrics:
  - name: <name>
    synonyms: [ <string>, ... ]
    description: <string>
    expr: <SQL expression>

verified_queries:
  - name: <string>
    question: <string>
    sql: <string>
    use_as_onboarding_question: <boolean>

Clés de haut niveau valides uniquement : name, description, tables, relationships, metrics, verified_queries, module_custom_instructions


Créez la Semantic View

Le SQL est identique quel que soit l'environnement. La différence est comment il est exécuté.

SQL à exécuter :

CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML('<database>.<schema>', $$
<contenu yaml ici>
$$);

Exécution par environnement :

Environnement Commande
Cortex Code Exécutez le SQL directement dans la session Snowflake active
snow CLI snow sql -q "CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML(...)" ou écrivez le YAML dans un fichier temporaire et dirigez-le
SnowSQL snowsql -a <account> -u <user> -f <sql_file.sql>
Python conn.cursor().execute("<sql>")

Conseil pour les environnements CLI : Si le YAML est long, écrivez-le d'abord dans un fichier .sql temporaire et exécutez le fichier plutôt que de le passer en ligne — cela évite les problèmes d'échappement shell avec le dollar-quoting $$.

# Exemple snow CLI avec un fichier
snow sql -f /tmp/create_semantic_view.sql

Accordez l'accès

GRANT SELECT ON SEMANTIC VIEW <database>.<schema>.<name> TO ROLE <role>;

Exécutez cela de la même manière que l'appel CREATE ci-dessus, en utilisant la méthode de connexion établie pendant l'étape de détection de l'environnement d'exécution.


Référence

Skills similaires