Composable semantic views

What are composable semantic views?

Composable semantic views introduce an IMPORTS clause that lets one semantic view pull in the full set of tables, dimensions, facts, metrics, and relationships from another semantic view. This creates a reusable, shared semantic layer where common business definitions are authored once and referenced by many downstream views.

Without composability, teams that need the same customer, account, or calendar definitions must duplicate them in every semantic view. When definitions drift apart, queries return inconsistent results and maintenance costs increase. Composable semantic views solve this by establishing a single source of truth.

Use cases

  • Shared dimensions: Define customer, account, product, geography, or calendar entities in one base semantic view. Domain teams (sales, marketing, finance) import the base view and add their own local facts and metrics.

  • Centralized governance: Business-critical definitions (revenue, churn, active users) are owned by a central analytics team. Downstream consumers import these definitions and can’t accidentally alter them.

  • Modular semantic layers: Large organizations can decompose a monolithic semantic view into focused modules (one per domain) that compose together, reducing complexity and improving change isolation.

  • Cross-domain queries: A composed view that imports both a customer dimension and an orders domain lets users query revenue by customer segment without writing joins or understanding the physical schema.

  • Top-down decomposition: Start with a large, consolidated semantic view and extract focused domain-specific views by selectively importing only the calculations each team needs. This avoids creating separate views from scratch while keeping each domain’s surface area small.

SQL syntax

CREATE SEMANTIC VIEW with IMPORTS

CREATE [ OR REPLACE ] SEMANTIC VIEW <name>
  IMPORTS (
    <semantic_view_name> [
      [ FACTS ( <fact_name> [ , ... ] ) ]
      [ DIMENSIONS ( <dim_name> [ , ... ] ) ]
      [ METRICS ( <metric_name> [ , ... ] ) ]
    ]
    [ , ... ]
  )
  [ TABLES ( ... ) ]
  [ RELATIONSHIPS ( ... ) ]
  [ FACTS ( ... ) ]
  [ DIMENSIONS ( ... ) ]
  [ METRICS ( ... ) ]
  ...

The IMPORTS clause appears before the TABLES clause. You can import one or more semantic views. A composed view can also omit TABLES entirely if it only combines imported entities.

Key rules:

  • By default, all public calculations from an imported semantic view are brought in, including their associated entities, relationships, primary keys, unique keys, constraints, synonyms, comments, ai_sql_generation, ai_question_categorization, and verified queries. To import only specific calculations, list them by name using FACTS, DIMENSIONS, or METRICS sub-clauses inside the IMPORTS clause (see Selective imports).
  • Imported entity definitions and relationship keys are immutable in the composing view. You can’t rename imported entities or update the join keys of imported relationships.
  • Imported entities are referenced by their logical table name and calculation name (for example, customers.d_region or orders.m_total), the same way you reference local objects.
  • Relationships between imported entities use logical dimension or fact names, not physical column names.
  • A composed view can define local tables alongside imports.
  • A composed view can define new metrics on imported entities.
  • Any semantic view can be imported as long as the executing role has REFERENCES privilege on it, including cross-schema, cross-database, and shared semantic views.

Selective imports

To import only specific calculations from a semantic view, list them by name inside the IMPORTS clause using FACTS, DIMENSIONS, and METRICS sub-clauses:

CREATE SEMANTIC VIEW sv_subset
IMPORTS (
  sv_large
    DIMENSIONS (nation.d_nation_name)
    METRICS (orders.m_order_count)
);

If any of FACTS, DIMENSIONS, or METRICS are specified, only those named calculations are imported. Any sub-clause that is omitted imports nothing for that calculation type. For example, specifying only FACTS(...) means no dimensions or metrics are imported from that source view.

Entities (tables) and relationships are not listed explicitly. Snowflake automatically imports the minimal subgraph of entities and relationships required to support the imported calculations:

  • Each entity that has an imported calculation is included.
  • If a path exists between any two included entities in the source view, all entities and relationships along that path are also included.

This means you can query across entities in the composed view without manually redefining the join paths.

:::note Only the calculations explicitly listed are queryable in the composed view. Dependent calculations that the imported calculations reference are automatically included as part of the minimal subgraph, but they aren’t directly queryable unless also listed in the IMPORTS clause. :::

Transitive imports

Import chains are resolved transitively. If view A imports view B, and view B imports view C, then view A has access to all entities from both B and C:

CREATE OR REPLACE SEMANTIC VIEW sv_mid IMPORTS (sv_orders);
CREATE OR REPLACE SEMANTIC VIEW sv_top IMPORTS (sv_mid);

-- Queries entities originally defined in sv_orders:
SELECT * FROM SEMANTIC_VIEW(sv_top METRICS orders.m_total);

Transitive imports bring in all public calculations from each view in the chain. The import graph must be acyclic: circular imports cause a creation error.

Runtime behavior

A composed view resolves imported objects at query time (late binding). It doesn’t freeze a snapshot of the source view’s definitions. If the source is altered to remove an object that the composed view references, the query fails with error 000904 (invalid identifier).

To fix this, restore the missing object in the source view or update the query.

Privilege resolution

To create a composed view, the role must have REFERENCES privilege on each directly imported semantic view, plus SELECT privilege on any new base tables referenced in the composing view’s own TABLES clause.

At query time, imported base tables resolve under the source semantic view’s owner role, not the caller’s role. A user can query a composed view without direct access to the underlying base tables, as long as they have the appropriate privileges on the composed view itself.

YAML format

Composable semantic views are supported in YAML. Use the imports top-level block to specify which semantic views to import:

imports:
  - database_name: <DB>
    schema_name: <SCHEMA>
    name: <SEMANTIC_VIEW_NAME>

When a composed view defines a local calc on an imported entity, the exported table entry includes is_imported: true to mark it as a shadow of the imported entity:

tables:
  - name: ORDERS
    is_imported: true
    metrics:
      - name: M_DOUBLE_TOTAL
        expr: orders.m_total * 2
        access_modifier: public_access

The export also includes an imported_semantic_models block containing the full YAML body of every semantic view in the transitive import closure.

Diamond imports

Diamond import graphs (the same upstream semantic view reachable through multiple paths) are supported. When two imported semantic views both import from the same upstream view, that upstream view’s entities, calculations, and relationships appear exactly once in the composed surface. Snowflake detects the shared source by owning-view identity, not by comparing field values.

-- sv_base is imported by both sv_sales and sv_marketing
CREATE SEMANTIC VIEW sv_sales    IMPORTS (sv_base) TABLES (...);
CREATE SEMANTIC VIEW sv_marketing IMPORTS (sv_base) TABLES (...);

-- Diamond: sv_combined imports both, but sv_base appears only once
CREATE SEMANTIC VIEW sv_combined IMPORTS (sv_sales, sv_marketing);

Name collisions between two different imported views (not the same upstream source) still cause a creation error. All identifiers across your import graph must be unique.

Metadata and introspection

DESCRIBE SEMANTIC VIEW

DESCRIBE SEMANTIC VIEW supports a MODE option to control the format of the result:

DESC SEMANTIC VIEW <name> MODE = { EXPANDED | COMPACT }
  • EXPANDED (default): Returns the fully flattened semantic view metadata across all imports, including all entities and calculations from transitively imported views.
  • COMPACT: Returns only the definitions local to this semantic view, including the IMPORTS clause.

The existing row types are:

  • IMPORT rows: One row per directly imported semantic view, with properties IMPORTED_SEMANTIC_VIEW_DATABASE_NAME, IMPORTED_SEMANTIC_VIEW_SCHEMA_NAME, and IMPORTED_SEMANTIC_VIEW_NAME.
  • Shadow TABLE rows: Appear when the composed view defines a local calc on an imported entity. These rows have empty BASE_TABLE_* values.
  • Calculations on shadows: Normal METRIC/DIMENSION rows attached to shadow tables.
DESCRIBE SEMANTIC VIEW sv_composed;

SHOW SEMANTIC VIEWS

The SHOW SEMANTIC VIEWS command includes an imports column that lists direct imports as an array of fully qualified names. Non-composed views show an empty array ([]).

SHOW SEMANTIC VIEWS LIKE 'SV_COMPOSED';
-- imports column: ["MY_DB.PUBLIC.SV_CUSTOMERS","MY_DB.PUBLIC.SV_ORDERS"]

SHOW SEMANTIC VIEWS LIKE 'SV_CUSTOMERS';
-- imports column: []

GET_DDL

GET_DDL output includes the IMPORTS clause with fully qualified semantic view names:

SELECT GET_DDL('SEMANTIC_VIEW', 'sv_composed');

SYSTEM$READ_YAML_FROM_SEMANTIC_VIEW

See YAML format for the full output structure.

SELECT SYSTEM$READ_YAML_FROM_SEMANTIC_VIEW('sv_composed');

Limitations

  • Snowsight: Snowsight doesn’t support semantic views with imports. Attempting to view or manage a composed semantic view in Snowsight generates an error.
  • Replication: Semantic views that use the IMPORTS clause aren’t replicated. They are skipped during account or database replication.
  • Variables: Semantic view variables are not supported in composability. Importing a semantic view that defines variables has no effect: the variables are not available in the composing view.
  • Composition fails if two imported views define the same logical table alias pointing to different physical tables, or the same calculation name with a different SQL expression.
  • When using selective imports, only the named calculations are directly queryable. Their dependencies are automatically included via the minimal subgraph but aren’t queryable unless also listed in the IMPORTS clause.

Example: selective import for top-down decomposition

This example starts with a single large semantic view covering three entities and extracts a smaller view by importing only the calculations that a specific team needs.

-- === Large consolidated semantic view ===

CREATE OR REPLACE SEMANTIC VIEW sv_nation_customer_orders
TABLES (
  nation   AS tpch.tpch.nation   PRIMARY KEY (n_nationkey),
  customer AS tpch.tpch.customer PRIMARY KEY (c_custkey),
  orders   AS tpch.tpch.orders   PRIMARY KEY (o_orderkey)
)
RELATIONSHIPS (
  cust_to_nation  AS customer (c_nationkey) REFERENCES nation (n_nationkey),
  orders_to_cust  AS orders   (o_custkey)   REFERENCES customer (c_custkey)
)
FACTS (
  orders.d_orderkey AS o_orderkey
)
DIMENSIONS (
  nation.d_nation_name               AS n_name,
  customer.d_customer_market_segment AS c_mktsegment
)
METRICS (
  customer.m_customer_count AS COUNT(c_custkey),
  orders.m_order_count      AS COUNT(o_orderkey)
);

-- === Smaller view for the analytics team: nation name + order count only ===

CREATE OR REPLACE SEMANTIC VIEW sv_orders_by_nation
IMPORTS (
  sv_nation_customer_orders
    DIMENSIONS (nation.d_nation_name)
    METRICS (orders.m_order_count)
);

-- The composing view contains:
--   Calculations: nation.d_nation_name, orders.m_order_count
--   Entities:     nation, customer, orders  (full path imported)
--   Relationships: cust_to_nation, orders_to_cust
--
-- NOT imported: orders.d_orderkey, customer.d_customer_market_segment, customer.m_customer_count

SELECT * FROM SEMANTIC_VIEW(
  sv_orders_by_nation
  DIMENSIONS nation.d_nation_name
  METRICS    orders.m_order_count
);

Example: shared account dimension across domains

This end-to-end example creates a shared account dimension and imports it into two domain-specific semantic views (sales and marketing), then runs queries that combine imported and local objects.

-- === Physical tables ===

CREATE OR REPLACE TABLE accounts (
  account_id INT,
  account_name VARCHAR,
  industry VARCHAR,
  region VARCHAR
);
CREATE OR REPLACE TABLE sales (
  sale_id INT,
  account_id INT,
  amount NUMBER,
  sale_date DATE
);
CREATE OR REPLACE TABLE campaigns (
  campaign_id INT,
  account_id INT,
  spend NUMBER,
  impressions INT
);

INSERT INTO accounts VALUES
  (1, 'Acme Corp', 'Technology', 'US'),
  (2, 'Globex Inc', 'Finance', 'EU'),
  (3, 'Initech', 'Technology', 'US');
INSERT INTO sales VALUES
  (101, 1, 50000, '2025-01-15'),
  (102, 2, 75000, '2025-02-20'),
  (103, 1, 30000, '2025-03-10'),
  (104, 3, 45000, '2025-03-22');
INSERT INTO campaigns VALUES
  (201, 1, 5000, 10000),
  (202, 2, 8000, 25000),
  (203, 3, 3000, 7000);

-- === Base semantic view: shared account dimension ===

CREATE OR REPLACE SEMANTIC VIEW sv_account_dimension
TABLES (
  accounts AS accounts PRIMARY KEY (d_account_id)
)
DIMENSIONS (
  accounts.d_account_id AS account_id,
  accounts.d_account_name AS account_name
    WITH SYNONYMS = ('company', 'client'),
  accounts.d_industry AS industry,
  accounts.d_region AS region
)
METRICS (
  accounts.m_account_count AS COUNT(account_id)
);

-- === Sales domain: imports the shared account dimension ===

CREATE OR REPLACE SEMANTIC VIEW sv_sales
IMPORTS (sv_account_dimension)
TABLES (
  sales AS sales PRIMARY KEY (sale_id)
)
DIMENSIONS (
  sales.d_sale_date AS sale_date,
  sales.d_account_id AS account_id
)
METRICS (
  sales.m_total_revenue AS SUM(amount),
  sales.m_deal_count AS COUNT(sale_id)
)
RELATIONSHIPS (
  sales_to_accounts AS sales(d_account_id) REFERENCES accounts
);

-- === Marketing domain: imports the same shared account dimension ===

CREATE OR REPLACE SEMANTIC VIEW sv_marketing
IMPORTS (sv_account_dimension)
TABLES (
  campaigns AS campaigns PRIMARY KEY (campaign_id)
)
DIMENSIONS (
  campaigns.d_campaign_id AS campaign_id,
  campaigns.d_account_id AS account_id
)
METRICS (
  campaigns.m_total_spend AS SUM(spend),
  campaigns.m_total_impressions AS SUM(impressions)
)
RELATIONSHIPS (
  campaigns_to_accounts AS campaigns(d_account_id) REFERENCES accounts
);

-- === Queries ===

-- Revenue by industry (imported dimension + local metric)
SELECT * FROM SEMANTIC_VIEW(
  sv_sales
  DIMENSIONS accounts.d_industry
  METRICS sales.m_total_revenue
);
-- Expected: Technology = 125000, Finance = 75000

-- Spend by region (imported dimension + local metric)
SELECT * FROM SEMANTIC_VIEW(
  sv_marketing
  DIMENSIONS accounts.d_region
  METRICS campaigns.m_total_spend
);
-- Expected: US = 8000, EU = 8000

-- Account count from imported metric
SELECT * FROM SEMANTIC_VIEW(
  sv_sales
  METRICS accounts.m_account_count
);
-- Expected: 3

-- Mix imported dimension with local dimension
SELECT * FROM SEMANTIC_VIEW(
  sv_sales
  DIMENSIONS accounts.d_account_name, sales.d_sale_date
  METRICS sales.m_total_revenue
);