{"slug": "how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex", "title": "How to run generative AI on SQL tables with Snowflake Cortex", "summary": "A developer's walkthrough demonstrates how to run generative AI on SQL tables using Snowflake Cortex, covering access setup, AI functions like AI_COMPLETE and AI_CLASSIFY, and cost monitoring. The guide emphasizes testing on small data slices and tracking credit consumption to avoid unexpected expenses on large tables.", "body_md": "The question that decides whether Cortex belongs in your stack is not what it can do. It is what it costs once the table has millions of rows instead of five.\n\nThat question exists because the model call is an ordinary SQL function. It sits inside a `SELECT`\n\n, composes with `WHERE`\n\n, `JOIN`\n\nand `GROUP BY`\n\n, and runs once per row. So this walkthrough goes in that order. Access first, then the functions on a small slice, then the credit consumption before anything scales up.\n\n`ACCOUNTADMIN`\n\nonce, to grant access.`support.tickets`\n\n, with `ticket_id`\n\n, `body`\n\nand `region`\n\n.Cortex access lives in a database role called `SNOWFLAKE.CORTEX_USER`\n\n. It cannot be granted to a user directly, only to a role, which is the first thing that trips people up:\n\n```\nUSE ROLE ACCOUNTADMIN;\n\nCREATE ROLE IF NOT EXISTS ai_analyst;\nGRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE ai_analyst;\nGRANT USAGE ON WAREHOUSE ai_wh TO ROLE ai_analyst;\nGRANT ROLE ai_analyst TO USER my_user;\n```\n\nTwo habits are worth adopting right away. The first is to put AI work on its own warehouse, so the credits appear separated in your billing without any extra tagging. The second is to grant the database role to a purpose-built role instead of something broad like `ANALYST`\n\n, because revoking access later is the only real spending control you have.\n\n`AI_COMPLETE`\n\nis the general-purpose function. It takes a model name and a prompt, and returns text. Since model availability changes by region and by release, start by listing what your account can actually call:\n\n```\nSHOW CORTEX BASE MODELS;\n```\n\nThen use one of those names below:\n\n```\nUSE ROLE ai_analyst;\nUSE WAREHOUSE ai_wh;\n\nSELECT AI_COMPLETE(\n  'claude-4-sonnet',   -- replace with a model from the list above\n  'Summarize this support ticket in one sentence: ' || body\n) AS summary\nFROM support.tickets\nLIMIT 5;\n```\n\nNote the `LIMIT 5`\n\n. Without it, the statement runs one model call per row, and a table with two million tickets will happily oblige. Every AI function here behaves the same way, so develop against a `LIMIT`\n\nand remove it only when the prompt is settled.\n\nIf you find older material online using `SNOWFLAKE.CORTEX.COMPLETE`\n\n, that is the previous generation. `AI_COMPLETE`\n\nis the updated version, and the same rename ran across the family, so `SENTIMENT`\n\nbecame `AI_SENTIMENT`\n\nand `CLASSIFY_TEXT`\n\nbecame `AI_CLASSIFY`\n\n. Copying a 2024 tutorial gets you working but deprecated syntax.\n\nFree-text prompting is the least interesting part of Cortex. The task-specific functions are where the SQL actually gets shorter, because they return typed values you can group and aggregate, instead of prose you would then have to parse.\n\nClassification into your own categories:\n\n```\nSELECT\n    ticket_id,\n    region,\n    AI_CLASSIFY(body, ['billing', 'bug', 'feature request', 'churn risk']) AS category\nFROM support.tickets;\n```\n\nFiltering in natural language, inside the `WHERE`\n\nclause:\n\n```\nSELECT ticket_id, body\nFROM support.tickets\nWHERE AI_FILTER(\n    'This message describes a customer threatening to cancel: ' || body\n);\n```\n\nFinally, aggregation across rows, which is the function that removes the most code. `AI_AGG`\n\nreads an entire column against a single prompt and is not bound by the model context window, so there is no chunking loop to write:\n\n```\nSELECT\n    region,\n    AI_AGG(body, 'What are the three most repeated complaints in these tickets?') AS themes\nFROM support.tickets\nWHERE created_at >= DATEADD('day', -7, CURRENT_DATE())\nGROUP BY region;\n```\n\nDo this on day one rather than after the invoice arrives. Every call is recorded in the Account Usage schema. Run `SELECT *`\n\nagainst the view once to see its current columns, since it has changed shape more than once, then aggregate:\n\n```\nSELECT\n    function_name,\n    model_name,\n    SUM(token_credits) AS credits\nFROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY\nWHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())\nGROUP BY 1, 2\nORDER BY credits DESC;\n```\n\nOne trap here: `CORTEX_FUNCTIONS_USAGE_HISTORY`\n\n, the view most existing tutorials point at, is no longer updated. Use `CORTEX_AI_FUNCTIONS_USAGE_HISTORY`\n\nfor full coverage, or `CORTEX_AISQL_USAGE_HISTORY`\n\n. Data can take a few hours to appear, so an empty result right after your first query is expected rather than a permissions problem.\n\n**Preview status.** Several of these functions, `AI_FILTER`\n\nand `AI_AGG`\n\namong them, are marked as preview in Snowflake's documentation, and preview means the signature can change under you. So check the current status of every function you depend on before it reaches a scheduled task.\n\n**Model and region availability.** When the model you want is missing from that `SHOW CORTEX BASE MODELS`\n\nlist, the account needs cross-region inference, which is an `ACCOUNTADMIN`\n\ndecision made once for the whole account through `CORTEX_ENABLED_CROSS_REGION`\n\n, never per user or per session. The real question there is compliance rather than cost, so if your data cannot leave a jurisdiction, that parameter is the conversation to have before writing any SQL.\n\n**Cost scales with rows, not with queries.** A warehouse costs the same whether the query touches ten rows or ten million. An AI function does not. The mental model you built tuning SQL stops applying here, and the discipline that replaces it is simple: filter before the function call, never after.\n\n**Non-determinism.** The same prompt on the same row can return different text on different days. If a downstream table depends on the output, materialize the result and version it, rather than calling the function inside a view that recomputes on every read.\n\nInference now sits next to your data, under the same access controls and the same query engine. That covers a large class of work that used to justify a separate service.\n\nMeaning is the part that does not come with it. The model can classify a ticket, but it has no idea what your company counts as an active customer, which revenue definition finance signed off on, or which of four `customer_id`\n\ncolumns is the governed one. Those definitions live in a semantic layer, and this blueprint on [the semantic layer as a single source of business meaning](https://bixtech.ai/aifirst-data-architecture-a-practical-blueprint-for-the-future-of-enterprise-intelligence/?utm_source=hashnode&utm_campaign=backlinks) covers how that layer sits between raw tables and anything that answers questions.\n\nSkip it and Cortex still works. You just get fluent answers that quietly disagree with the finance report, produced faster than before.", "url": "https://wpnews.pro/news/how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex", "canonical_source": "https://dev.to/laura_cristinachicovisd/how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex-5gh6", "published_at": "2026-09-03 02:21:09+00:00", "updated_at": "2026-09-03 02:52:32.756715+00:00", "lang": "en", "topics": ["generative-ai", "ai-tools", "ai-infrastructure", "developer-tools"], "entities": ["Snowflake Cortex", "AI_COMPLETE", "AI_CLASSIFY", "AI_FILTER", "AI_AGG"], "alternates": {"html": "https://wpnews.pro/news/how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex", "markdown": "https://wpnews.pro/news/how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex.md", "text": "https://wpnews.pro/news/how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex.txt", "jsonld": "https://wpnews.pro/news/how-to-run-generative-ai-on-sql-tables-with-snowflake-cortex.jsonld"}}