Mixed-grain tables: diagnose and fix wrong agent answers with a GRAIN dimension and verified queries: code¶
Every file in the Mixed-grain tables: diagnose and fix wrong agent answers with a GRAIN dimension and verified queries recipe. To run the recipe, download mixed-grain-and-verified-queries.zip, unzip it, and run the scripts from the mixed-grain-and-verified-queries/ folder as the recipe describes. Comments in the files that say “see README” refer to the recipe page; the zip includes it as README.md.
sql/¶
sql/01_setup.sql¶
-- =============================================================================
-- Mixed-grain semantic views: setup
-- A store sales table that holds two grains as sibling rows: one row per store
-- per day, and one roll-up row per store per month. Nothing in the table says
-- which is which except PERIOD_START = PERIOD_END on daily rows. Also creates
-- the test questions the agent is scored on, with expected answers computed
-- from the data.
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
SET ANALYST_ROLE = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ANALYST';
SET CUR_USER = '"' || CURRENT_USER() || '"';
-- -----------------------------------------------------------------------------
-- 1. Account objects (ACCOUNTADMIN only for these statements)
-- -----------------------------------------------------------------------------
USE ROLE ACCOUNTADMIN;
CREATE ROLE IF NOT EXISTS IDENTIFIER($ROLE_NAME)
COMMENT = 'Cortex AI Cookbook: mixed-grain-and-verified-queries';
GRANT ROLE IDENTIFIER($ROLE_NAME) TO USER IDENTIFIER($CUR_USER);
-- Keeps the recipe role in the system hierarchy so ACCOUNTADMIN can drop its objects in teardown.
GRANT ROLE IDENTIFIER($ROLE_NAME) TO ROLE SYSADMIN;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE IDENTIFIER($ROLE_NAME);
-- A consumer role that only gets SELECT on the semantic view. Used to show that
-- CREATE OR ALTER keeps grants when the view is fixed in 06.
CREATE ROLE IF NOT EXISTS IDENTIFIER($ANALYST_ROLE)
COMMENT = 'Cortex AI Cookbook: mixed-grain-and-verified-queries consumer';
CREATE WAREHOUSE IF NOT EXISTS IDENTIFIER($WH)
WAREHOUSE_SIZE = 'XSMALL'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE
COMMENT = 'Cortex AI Cookbook: mixed-grain-and-verified-queries';
GRANT USAGE, OPERATE ON WAREHOUSE IDENTIFIER($WH) TO ROLE IDENTIFIER($ROLE_NAME);
CREATE DATABASE IF NOT EXISTS IDENTIFIER($DB)
COMMENT = 'Cortex AI Cookbook: mixed-grain-and-verified-queries';
GRANT OWNERSHIP ON DATABASE IDENTIFIER($DB) TO ROLE IDENTIFIER($ROLE_NAME) COPY CURRENT GRANTS;
-- -----------------------------------------------------------------------------
-- 2. Data (runs as the recipe role)
-- -----------------------------------------------------------------------------
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
CREATE SCHEMA IF NOT EXISTS RETAIL;
USE SCHEMA RETAIL;
-- Four fictional Northwind Outfitters stores, January to March 2026.
CREATE OR REPLACE TABLE STORE_SALES_SUMMARY (
STORE_ID VARCHAR,
STORE_NAME VARCHAR,
REGION VARCHAR,
PERIOD_START DATE,
PERIOD_END DATE,
NET_SALES NUMBER(12,2),
ORDER_COUNT NUMBER,
SALES_TARGET NUMBER(12,2) -- set on monthly rows only
);
-- Daily rows. Values are deterministic (hash-based), so every run gets the same numbers.
INSERT INTO STORE_SALES_SUMMARY
WITH stores AS (
SELECT * FROM VALUES
('S01', 'Harbor Street', 'North', 1.00),
('S02', 'Mill Road', 'North', 0.85),
('S03', 'Canal Park', 'South', 1.20),
('S04', 'Elm Square', 'South', 0.70) AS t(STORE_ID, STORE_NAME, REGION, SCALE)
), days AS (
SELECT DATEADD(day, SEQ4(), '2026-01-01'::DATE) AS D
FROM TABLE(GENERATOR(ROWCOUNT => 90))
)
SELECT s.STORE_ID, s.STORE_NAME, s.REGION, d.D, d.D,
ROUND(s.SCALE * (900 + MOD(ABS(HASH(s.STORE_ID, d.D)), 600)
+ IFF(DAYOFWEEKISO(d.D) >= 6, 400, 0)), 2) AS NET_SALES,
ROUND(s.SCALE * (18 + MOD(ABS(HASH(d.D, s.STORE_ID)), 14))) AS ORDER_COUNT,
NULL
FROM stores s CROSS JOIN days d;
-- Monthly roll-up rows: the exact sum of each store's daily rows, plus a target.
INSERT INTO STORE_SALES_SUMMARY
SELECT STORE_ID, STORE_NAME, REGION,
DATE_TRUNC('month', PERIOD_START) AS PERIOD_START,
LAST_DAY(PERIOD_START) AS PERIOD_END,
SUM(NET_SALES), SUM(ORDER_COUNT),
ROUND(SUM(NET_SALES) * CASE STORE_ID
WHEN 'S01' THEN 0.97 WHEN 'S02' THEN 1.04
WHEN 'S03' THEN 0.99 ELSE 1.06 END, -2) AS SALES_TARGET
FROM STORE_SALES_SUMMARY
WHERE PERIOD_START = PERIOD_END
GROUP BY STORE_ID, STORE_NAME, REGION, DATE_TRUNC('month', PERIOD_START), LAST_DAY(PERIOD_START);
-- -----------------------------------------------------------------------------
-- 3. Test questions and their correct answers, computed from the daily rows
-- -----------------------------------------------------------------------------
CREATE OR REPLACE TABLE AGENT_TEST_QUESTIONS (
QUESTION_ID VARCHAR,
QUESTION VARCHAR,
EXPECTED_VALUE NUMBER(14,2), -- for numeric answers
EXPECTED_STORES ARRAY -- for "which stores" answers
);
INSERT INTO AGENT_TEST_QUESTIONS (QUESTION_ID, QUESTION, EXPECTED_VALUE)
SELECT 'Q1', 'What were total net sales for the Harbor Street store in February 2026?', SUM(NET_SALES)
FROM STORE_SALES_SUMMARY
WHERE PERIOD_START = PERIOD_END AND STORE_NAME = 'Harbor Street'
AND PERIOD_START BETWEEN '2026-02-01' AND '2026-02-28';
INSERT INTO AGENT_TEST_QUESTIONS (QUESTION_ID, QUESTION, EXPECTED_VALUE)
SELECT 'Q2', 'What were total net sales across all stores in Q1 2026 (January through March)?', SUM(NET_SALES)
FROM STORE_SALES_SUMMARY
WHERE PERIOD_START = PERIOD_END;
INSERT INTO AGENT_TEST_QUESTIONS (QUESTION_ID, QUESTION, EXPECTED_VALUE)
SELECT 'Q3', 'What was the average daily net sales for Canal Park in January 2026?', ROUND(AVG(NET_SALES), 2)
FROM STORE_SALES_SUMMARY
WHERE PERIOD_START = PERIOD_END AND STORE_NAME = 'Canal Park'
AND PERIOD_START BETWEEN '2026-01-01' AND '2026-01-31';
INSERT INTO AGENT_TEST_QUESTIONS (QUESTION_ID, QUESTION, EXPECTED_STORES)
SELECT 'Q4', 'Which stores missed their March 2026 sales target?', ARRAY_AGG(STORE_NAME) WITHIN GROUP (ORDER BY STORE_NAME)
FROM STORE_SALES_SUMMARY
WHERE PERIOD_START <> PERIOD_END AND PERIOD_START = '2026-03-01' AND NET_SALES < SALES_TARGET;
SELECT QUESTION_ID, QUESTION, EXPECTED_VALUE, EXPECTED_STORES FROM AGENT_TEST_QUESTIONS ORDER BY QUESTION_ID;
sql/02_semantic_view_before.sql¶
-- =============================================================================
-- Mixed-grain semantic views: the "before" semantic view
-- A reasonable-looking first draft. It exposes the table as if every row were a
-- day of sales. Nothing tells Cortex Analyst that monthly roll-up rows sit next
-- to the daily rows, so sums and averages over a date range count each sale
-- twice (or skew the average).
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
SET ANALYST_ROLE = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ANALYST';
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
USE SCHEMA RETAIL;
CREATE OR ALTER SEMANTIC VIEW STORE_SALES_SV
TABLES (
sales AS STORE_SALES_SUMMARY
PRIMARY KEY (STORE_ID, PERIOD_START, PERIOD_END)
COMMENT = 'Northwind Outfitters store sales.'
)
FACTS (
sales.net_sales AS NET_SALES COMMENT = 'Net sales in USD.',
sales.order_count AS ORDER_COUNT COMMENT = 'Number of orders.',
sales.sales_target AS SALES_TARGET COMMENT = 'Sales target in USD.'
)
DIMENSIONS (
sales.store_name AS STORE_NAME COMMENT = 'Store name.',
sales.region AS REGION COMMENT = 'Store region: North or South.',
sales.sale_date AS PERIOD_START COMMENT = 'Date of the sales.'
)
METRICS (
sales.total_net_sales AS SUM(net_sales) COMMENT = 'Total net sales in USD.',
sales.avg_net_sales AS AVG(net_sales) COMMENT = 'Average net sales in USD.',
sales.total_orders AS SUM(order_count) COMMENT = 'Total orders.',
sales.total_target AS SUM(sales_target) COMMENT = 'Total sales target in USD.'
)
COMMENT = 'Store sales for Northwind Outfitters.';
-- A consumer role needs SELECT on the semantic view, not on the table.
GRANT USAGE ON DATABASE IDENTIFIER($DB) TO ROLE IDENTIFIER($ANALYST_ROLE);
GRANT USAGE ON SCHEMA RETAIL TO ROLE IDENTIFIER($ANALYST_ROLE);
GRANT SELECT ON SEMANTIC VIEW STORE_SALES_SV TO ROLE IDENTIFIER($ANALYST_ROLE);
sql/03_agent_and_test_harness.sql¶
-- =============================================================================
-- Mixed-grain semantic views: the agent and the test harness
-- An agent with one Cortex Analyst tool over STORE_SALES_SV, and a procedure
-- that asks it every question in AGENT_TEST_QUESTIONS and scores the answers.
-- The agent itself never changes in this recipe: only the semantic view does.
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
SET AGENT_MODEL = 'auto'; -- orchestration model; auto lets Snowflake choose
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
USE SCHEMA RETAIL;
-- 1. Agent. CREATE AGENT IF NOT EXISTS + MODIFY LIVE VERSION keeps grants on rerun.
-- The spec is built in a block so the tool warehouse comes from $WH and the
-- orchestration model from $AGENT_MODEL. Both are checked against a pattern
-- before they go into the spec.
EXECUTE IMMEDIATE $$
DECLARE
wh VARCHAR;
model VARCHAR;
spec VARCHAR;
BEGIN
SELECT UPPER($WH), LOWER($AGENT_MODEL) INTO :wh, :model;
IF (NOT REGEXP_LIKE(wh, '^[A-Z][A-Z0-9_]*$')) THEN
RETURN 'Not deployed: WH must be a plain identifier.';
END IF;
IF (NOT REGEXP_LIKE(model, '^[a-z0-9][a-z0-9._-]*$')) THEN
RETURN 'Not deployed: AGENT_MODEL must be a model name such as auto or claude-sonnet-4-6.';
END IF;
spec := REPLACE(REPLACE('{
"models": {"orchestration": "__MODEL__"},
"instructions": {
"response": "Answer with the number or list the user asked for, the period, and the store or region it covers.",
"orchestration": "Use store_sales for every question about store sales, orders, and targets."
},
"tools": [
{"tool_spec": {"type": "cortex_analyst_text_to_sql", "name": "store_sales",
"description": "Northwind Outfitters store sales, orders, and sales targets by store and date."}}
],
"tool_resources": {
"store_sales": {"semantic_view": "COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.STORE_SALES_SV",
"execution_environment": {"type": "warehouse", "warehouse": "__WH__", "query_timeout": 60}}
}
}', '__WH__', wh), '__MODEL__', model);
-- The spec is spliced into a quoted literal below; escape any apostrophe.
spec := REPLACE(spec, '''', '''''');
EXECUTE IMMEDIATE 'CREATE AGENT IF NOT EXISTS COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.STORE_SALES_AGENT '
|| 'COMMENT = ''Cortex AI Cookbook: mixed-grain-and-verified-queries'' '
|| 'FROM SPECIFICATION ''' || spec || '''';
EXECUTE IMMEDIATE 'ALTER AGENT COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.STORE_SALES_AGENT '
|| 'MODIFY LIVE VERSION SET SPECIFICATION = ''' || spec || '''';
RETURN 'Agent deployed with warehouse ' || wh || ' and orchestration model ' || model;
END;
$$;
-- 2. Results of every test run, one row per question per run.
CREATE TABLE IF NOT EXISTS AGENT_TEST_RESULTS (
RUN_LABEL VARCHAR,
QUESTION_ID VARCHAR,
QUESTION VARCHAR,
ANSWER_TEXT VARCHAR,
AGENT_SQL VARCHAR,
EXPECTED VARCHAR,
PASSED BOOLEAN,
RESPONSE VARIANT,
RUN_AT TIMESTAMP_LTZ DEFAULT CURRENT_TIMESTAMP()
);
-- 3. Test harness (caller's rights). Numeric answers pass when a number in the
-- answer is within 0.5% of the expected value. "Which stores" answers are
-- judged by AI_COMPLETE with a boolean structured output.
CREATE OR REPLACE PROCEDURE RUN_AGENT_TESTS(RUN_LABEL VARCHAR, JUDGE_MODEL VARCHAR)
RETURNS VARCHAR
LANGUAGE SQL
COMMENT = 'Cortex AI Cookbook: asks STORE_SALES_AGENT every test question and scores the answers'
EXECUTE AS CALLER
AS
$$
DECLARE
resp VARIANT;
answer VARCHAR;
agent_sql VARCHAR;
expected VARCHAR;
passed BOOLEAN;
judge VARIANT;
n_pass NUMBER DEFAULT 0;
n_total NUMBER DEFAULT 0;
v_qid VARCHAR;
v_question VARCHAR;
v_expected_value NUMBER(14,2);
v_expected_stores ARRAY;
v_content VARIANT;
body VARCHAR;
BEGIN
DELETE FROM COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.AGENT_TEST_RESULTS WHERE RUN_LABEL = :run_label;
LET rs RESULTSET := (SELECT QUESTION_ID, QUESTION, EXPECTED_VALUE, EXPECTED_STORES
FROM COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.AGENT_TEST_QUESTIONS
ORDER BY QUESTION_ID);
LET c1 CURSOR FOR rs;
FOR q IN c1 DO
v_qid := q.QUESTION_ID;
v_question := q.QUESTION;
v_expected_value := q.EXPECTED_VALUE;
v_expected_stores := q.EXPECTED_STORES;
passed := NULL;
-- DATA_AGENT_RUN needs a constant request body, so build it into a variable first.
body := TO_JSON(OBJECT_CONSTRUCT('messages', ARRAY_CONSTRUCT(OBJECT_CONSTRUCT(
'role', 'user',
'content', ARRAY_CONSTRUCT(OBJECT_CONSTRUCT('type', 'text', 'text', v_question))))));
SELECT TRY_PARSE_JSON(SNOWFLAKE.CORTEX.DATA_AGENT_RUN(
'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.STORE_SALES_AGENT', :body))
INTO :resp;
v_content := resp:content;
-- Final answer text, and the SQL the agent wrote (tool_use inputs only; tool
-- results also carry the verified queries and the compiled SQL).
SELECT LISTAGG(c.value:text::VARCHAR, ' ') WITHIN GROUP (ORDER BY c.index) INTO :answer
FROM TABLE(FLATTEN(input => :v_content)) c WHERE c.value:type::VARCHAR = 'text';
SELECT LISTAGG(s.value::VARCHAR, '\n---\n') INTO :agent_sql
FROM TABLE(FLATTEN(input => :v_content, recursive => TRUE)) s
WHERE s.path LIKE '%tool_use.input.sql' AND IS_VARCHAR(s.value);
IF (v_expected_value IS NOT NULL) THEN
expected := v_expected_value::VARCHAR;
SELECT COUNT_IF(ABS(TRY_TO_NUMBER(REPLACE(n.value::VARCHAR, ',', ''), 18, 2) - :v_expected_value)
<= 0.005 * ABS(:v_expected_value)) > 0
INTO :passed
FROM TABLE(FLATTEN(input => REGEXP_SUBSTR_ALL(COALESCE(:answer, ''), '[0-9][0-9,]*[.]?[0-9]*'))) n;
ELSE
expected := ARRAY_TO_STRING(v_expected_stores, ', ');
SELECT AI_COMPLETE(
model => :judge_model,
prompt => 'Question: ' || :v_question || '\nCorrect answer: exactly these stores: ' || :expected
|| '\nAgent answer: ' || COALESCE(:answer, '')
|| '\nDoes the agent answer name exactly the correct stores as the ones that missed, and no others?',
model_parameters => {'temperature': 0},
response_format => {'type': 'json', 'schema': {'type': 'object',
'properties': {'correct': {'type': 'boolean'}, 'reason': {'type': 'string'}},
'required': ['correct', 'reason']}})
INTO :judge;
passed := judge:correct::BOOLEAN;
END IF;
INSERT INTO COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES.RETAIL.AGENT_TEST_RESULTS
(RUN_LABEL, QUESTION_ID, QUESTION, ANSWER_TEXT, AGENT_SQL, EXPECTED, PASSED, RESPONSE)
SELECT :run_label, :v_qid, :v_question, :answer, :agent_sql, :expected, COALESCE(:passed, FALSE), :resp;
n_total := n_total + 1;
n_pass := n_pass + IFF(COALESCE(passed, FALSE), 1, 0);
END FOR;
RETURN run_label || ': ' || n_pass || ' of ' || n_total || ' answers correct';
END;
$$;
sql/04_test_before.sql¶
-- =============================================================================
-- Mixed-grain semantic views: test the agent against the "before" view
-- Takes a few minutes: one agent call per question.
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
SET MODEL = 'claude-sonnet-4-6'; -- judge model for the "which stores" question
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
USE SCHEMA RETAIL;
CALL RUN_AGENT_TESTS('before', $MODEL);
SELECT QUESTION_ID, PASSED, EXPECTED, ANSWER_TEXT
FROM AGENT_TEST_RESULTS
WHERE RUN_LABEL = 'before'
ORDER BY QUESTION_ID;
sql/05_diagnose.sql¶
-- =============================================================================
-- Mixed-grain semantic views: diagnose the wrong answers
-- Three checks, from symptom to cause.
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
USE SCHEMA RETAIL;
-- 1. Symptom: how far off was each answer? A ratio near 2.0 on a SUM is the
-- signature of two grains being added together.
SELECT r.QUESTION_ID, r.EXPECTED,
MAX(TRY_TO_NUMBER(REPLACE(n.value::VARCHAR, ',', ''), 18, 2)) AS LARGEST_NUMBER_IN_ANSWER,
ROUND(LARGEST_NUMBER_IN_ANSWER / NULLIF(TRY_TO_NUMBER(r.EXPECTED, 18, 2), 0), 2) AS RATIO_TO_EXPECTED
FROM AGENT_TEST_RESULTS r,
LATERAL FLATTEN(input => REGEXP_SUBSTR_ALL(r.ANSWER_TEXT, '[0-9][0-9,]*[.][0-9]+')) n
WHERE r.RUN_LABEL = 'before' AND TRY_TO_NUMBER(r.EXPECTED, 18, 2) IS NOT NULL
GROUP BY r.QUESTION_ID, r.EXPECTED
ORDER BY r.QUESTION_ID;
-- 2. The SQL the agent ran. Note the date filter with no grain filter.
SELECT QUESTION_ID, AGENT_SQL
FROM AGENT_TEST_RESULTS
WHERE RUN_LABEL = 'before'
ORDER BY QUESTION_ID;
-- 3. Cause: the table holds two grains that the semantic view never mentions.
SELECT IFF(PERIOD_START = PERIOD_END, 'DAILY', 'MONTHLY') AS IMPLICIT_GRAIN,
COUNT(*) AS ROWS_, SUM(NET_SALES) AS NET_SALES, COUNT(SALES_TARGET) AS ROWS_WITH_TARGET
FROM STORE_SALES_SUMMARY
GROUP BY 1
ORDER BY 1;
-- Both grains sum to the same total, so any SUM over a date range that includes
-- both counts every sale twice, and SALES_TARGET exists only on MONTHLY rows.
sql/06_fix_semantic_view.sql¶
-- =============================================================================
-- Mixed-grain semantic views: the fix
-- Same view name, applied with CREATE OR ALTER so grants on the view are kept.
-- Three changes:
-- 1. A GRAIN dimension that names the two row types, marked IS_ENUM.
-- 2. Grain rules in AI_SQL_GENERATION: filter to exactly one GRAIN.
-- 3. Verified queries for the question shapes that broke. They use different
-- stores and months from the test questions on purpose, so the re-test
-- measures whether the rules generalize, not whether a stored query matched.
-- Syntax for these clauses is covered by the coco-skills semantic-view-patterns
-- ai_metadata snippet; this file only shows how to apply them to a grain problem.
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
USE SCHEMA RETAIL;
CREATE OR ALTER SEMANTIC VIEW STORE_SALES_SV
TABLES (
sales AS STORE_SALES_SUMMARY
PRIMARY KEY (STORE_ID, PERIOD_START, PERIOD_END)
COMMENT = 'Northwind Outfitters store sales. Holds two grains as sibling rows: one row per store per day (DAILY) and one roll-up row per store per month (MONTHLY). Always filter to one GRAIN; summing both counts every sale twice.'
)
FACTS (
sales.net_sales AS NET_SALES
COMMENT = 'Net sales in USD for the row''s period: one day on DAILY rows, one month on MONTHLY rows.',
sales.order_count AS ORDER_COUNT
COMMENT = 'Orders in the row''s period.',
sales.sales_target AS SALES_TARGET
COMMENT = 'Monthly sales target in USD. Present on MONTHLY rows only; NULL on DAILY rows.'
)
DIMENSIONS (
sales.store_name AS STORE_NAME
COMMENT = 'Store name.'
SAMPLE_VALUES ('Harbor Street', 'Mill Road', 'Canal Park', 'Elm Square')
IS_ENUM,
sales.region AS REGION
COMMENT = 'Store region.'
SAMPLE_VALUES ('North', 'South')
IS_ENUM,
sales.grain AS IFF(PERIOD_START = PERIOD_END, 'DAILY', 'MONTHLY')
WITH SYNONYMS = ('granularity', 'row level')
COMMENT = 'Row type. DAILY = one store, one day. MONTHLY = one store, one calendar month, equal to the sum of that month''s DAILY rows, and the only rows with SALES_TARGET. Every query must filter to exactly one GRAIN.'
SAMPLE_VALUES ('DAILY', 'MONTHLY')
IS_ENUM,
sales.period_start AS PERIOD_START
COMMENT = 'First day of the row''s period: the sales date on DAILY rows, the first of the month on MONTHLY rows.',
sales.period_end AS PERIOD_END
COMMENT = 'Last day of the row''s period: equal to PERIOD_START on DAILY rows, the last day of the month on MONTHLY rows.'
)
METRICS (
sales.total_net_sales AS SUM(net_sales)
COMMENT = 'Total net sales in USD. Valid only within one GRAIN.',
sales.avg_net_sales AS AVG(net_sales)
COMMENT = 'Average net sales per row in USD. With GRAIN = DAILY this is average daily sales.',
sales.total_orders AS SUM(order_count)
COMMENT = 'Total orders. Valid only within one GRAIN.',
sales.total_target AS SUM(sales_target)
COMMENT = 'Total sales target in USD. Use with GRAIN = MONTHLY.'
)
COMMENT = 'Store sales for Northwind Outfitters. Mixed-grain table: always filter to one GRAIN.'
AI_SQL_GENERATION
'STORE_SALES_SUMMARY stores two grains as sibling rows. GRAIN = DAILY rows cover one day (PERIOD_START = PERIOD_END). GRAIN = MONTHLY rows cover a calendar month and already equal the sum of that month''s DAILY rows. Every query must filter to exactly one grain; never aggregate across both. Choose the grain as follows. Totals for whole calendar months, quarters, or years: use MONTHLY rows and filter on PERIOD_START. Anything about targets or attainment: use MONTHLY rows, because SALES_TARGET exists only there. Daily figures, averages per day, individual dates, or ranges that do not cover whole months: use DAILY rows. Compare net sales with target on the same MONTHLY row rather than summing either across grains.'
AI_VERIFIED_QUERIES (
vq_store_month_total AS (
QUESTION 'What were total net sales for Mill Road in January 2026?'
VERIFIED_AT 1790000000
ONBOARDING_QUESTION TRUE
VERIFIED_BY '(STEWARD = retail_analytics)'
SQL 'SELECT store_name, period_start AS month, net_sales
FROM sales
WHERE grain = ''MONTHLY''
AND store_name = ''Mill Road''
AND period_start = ''2026-01-01'''
),
vq_region_quarter_total AS (
QUESTION 'What were total net sales for the South region in Q1 2026?'
VERIFIED_AT 1790000000
ONBOARDING_QUESTION TRUE
VERIFIED_BY '(STEWARD = retail_analytics)'
SQL 'SELECT region, SUM(net_sales) AS total_net_sales
FROM sales
WHERE grain = ''MONTHLY''
AND region = ''South''
AND period_start BETWEEN ''2026-01-01'' AND ''2026-03-01''
GROUP BY region'
),
vq_stores_vs_target AS (
QUESTION 'How did each store do against its February 2026 sales target?'
VERIFIED_AT 1790000000
ONBOARDING_QUESTION FALSE
VERIFIED_BY '(STEWARD = retail_analytics)'
SQL 'SELECT store_name, net_sales, sales_target, net_sales - sales_target AS variance,
IFF(net_sales < sales_target, ''missed'', ''met'') AS status
FROM sales
WHERE grain = ''MONTHLY''
AND period_start = ''2026-02-01''
ORDER BY variance'
)
);
-- CREATE OR ALTER kept the SELECT grant from 02:
SHOW GRANTS ON SEMANTIC VIEW STORE_SALES_SV;
sql/07_test_after.sql¶
-- =============================================================================
-- Mixed-grain semantic views: re-test the unchanged agent against the fixed view
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
SET MODEL = 'claude-sonnet-4-6'; -- judge model for the "which stores" question
USE ROLE IDENTIFIER($ROLE_NAME);
USE WAREHOUSE IDENTIFIER($WH);
USE DATABASE IDENTIFIER($DB);
USE SCHEMA RETAIL;
CALL RUN_AGENT_TESTS('after', $MODEL);
-- Before and after, side by side.
SELECT b.QUESTION_ID, b.EXPECTED,
IFF(b.PASSED, 'PASS', 'FAIL') AS BEFORE_RESULT, b.ANSWER_TEXT AS BEFORE_ANSWER,
IFF(a.PASSED, 'PASS', 'FAIL') AS AFTER_RESULT, a.ANSWER_TEXT AS AFTER_ANSWER,
a.AGENT_SQL AS AFTER_SQL
FROM AGENT_TEST_RESULTS b
JOIN AGENT_TEST_RESULTS a ON a.QUESTION_ID = b.QUESTION_ID AND a.RUN_LABEL = 'after'
WHERE b.RUN_LABEL = 'before'
ORDER BY b.QUESTION_ID;
SELECT RUN_LABEL, COUNT_IF(PASSED) AS CORRECT, COUNT(*) AS QUESTIONS
FROM AGENT_TEST_RESULTS
GROUP BY RUN_LABEL
ORDER BY RUN_LABEL DESC;
sql/99_teardown.sql¶
-- =============================================================================
-- Mixed-grain semantic views: teardown
-- The agent, semantic view, tables, and procedure go with the database.
-- =============================================================================
SET DB = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES';
SET WH = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_WH';
SET ROLE_NAME = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ROLE';
SET ANALYST_ROLE = 'COOKBOOK_MIXED_GRAIN_AND_VERIFIED_QUERIES_ANALYST';
USE ROLE ACCOUNTADMIN;
DROP DATABASE IF EXISTS IDENTIFIER($DB);
DROP WAREHOUSE IF EXISTS IDENTIFIER($WH);
DROP ROLE IF EXISTS IDENTIFIER($ANALYST_ROLE);
DROP ROLE IF EXISTS IDENTIFIER($ROLE_NAME);
SHOW DATABASES LIKE 'COOKBOOK_MIXED_GRAIN%';
SHOW WAREHOUSES LIKE 'COOKBOOK_MIXED_GRAIN%';
SHOW ROLES LIKE 'COOKBOOK_MIXED_GRAIN%';