Skill 3 · 25–30%

Implement AI capabilities in database solutions

The smallest area by weight, and the newest material on the exam. It is a pipeline you can build entirely in T-SQL: register a model, turn rows into embeddings, search them by meaning (and keywords), and hand the best rows to a chat model as grounded context.

Skills measured as of March 12, 2026

What this skill tests

Three objective groups: models and embeddings, intelligent search and retrieval-augmented generation. Expect "which function", "which metric", "ANN or ENN", "which maintenance method" and "fix this RAG procedure" items. The platform line matters more here than anywhere: vector indexes and VECTOR_SEARCH are GA in Azure SQL and Fabric but preview in SQL Server 2025. The worked, end-to-end build is in the Vectors, search & RAG deep dive.

Models and embeddings

An embedding is a vector — a fixed-length array of numbers — that places a piece of text (or an image) in a space where similar meanings sit close together. An embedding model produces them; a chat model generates text. A RAG solution needs both.

Evaluate external models

CharacteristicWhy it matters for a SQL workload
ModalityText-only versus multimodal (images, documents). Product photos or scanned forms need a multimodal model.
Language coverageA multilingual embedding model maps "chaqueta" and "jacket" close together; an English-only one does not.
SizeLarger models reason better but cost more tokens and latency; small models are usually right for embeddings and classification.
DimensionsMust equal the vector(n) column size. Some models accept a dimensions parameter to shorten output (storage versus accuracy).
Context window and tokensCaps how much text one embedding or one prompt can hold, and drives cost — the reason you chunk and keep RAG context compact.
Structured outputModels that return JSON (JSON mode or a JSON schema) can be parsed with OPENJSON instead of string handling.

Microsoft Foundry's model catalog is where you compare capabilities, benchmarks, versions and lifecycle status, and deploy the model whose endpoint the database will call.

Create and manage external models

An external model is a database object that stores an inference endpoint's location, API format, model name, credential and default parameters — so T-SQL can say USE MODEL EmbeddingSmall instead of building HTTP requests.

T-SQL
-- Applies to: SQL Server 2025, Azure SQL Database, Azure SQL MI (Always-up-to-date), SQL database in Fabric
CREATE EXTERNAL MODEL EmbeddingSmall
AUTHORIZATION dbo
WITH (
    LOCATION   = 'https://contoso-ai.openai.azure.com/openai/deployments/text-embedding-3-small/embeddings?api-version=2024-02-01',
    API_FORMAT = 'Azure OpenAI',                 -- also 'OpenAI', 'Ollama', 'ONNX Runtime'
    MODEL_TYPE = EMBEDDINGS,                     -- the only accepted model type
    MODEL      = 'text-embedding-3-small',
    CREDENTIAL = [https://contoso-ai.openai.azure.com/],
    PARAMETERS = '{"dimensions": 1536, "sql_rest_options": {"retry_count": 3}}'
);

ALTER EXTERNAL MODEL EmbeddingSmall SET (MODEL = 'text-embedding-3-large');   -- swap the model behind the name
GRANT EXECUTE ON EXTERNAL MODEL::EmbeddingSmall TO app_writer;                -- needed to use it in AI functions
SELECT name, location, api_format, model_type, model FROM sys.external_models;
DROP EXTERNAL MODEL EmbeddingSmall;
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric MI: Always-up-to-date update policy only. SQL Server 2025 also supports a local ONNX Runtime model (Windows, preview, PREVIEW_FEATURES plus 'external AI runtimes enabled').
  • Permissions: CREATE EXTERNAL MODEL or ALTER ANY EXTERNAL MODEL to manage; EXECUTE ON EXTERNAL MODEL::name to use.
  • Credential: a database scoped credential named after the endpoint URL — HTTPEndpointHeaders with an API key, or Managed Identity (preferred; see Skill 2).
  • HTTPS only. On SQL Server 2025 and MI, enable external rest endpoint enabled first.

External models are for embeddings

MODEL_TYPE accepts only EMBEDDINGS. To call a chat model for the generation step of RAG you use sp_invoke_external_rest_endpoint directly — with the same credential.

Choose an embedding maintenance method

Embeddings go stale the moment their source text changes. The outline lists seven ways to keep them aligned:

MethodHow it worksChoose it when
Table triggerMarks rows for re-embedding (or re-embeds) inside the write transactionFreshness must be immediate and volume is low — it adds latency and an external call risk to every write
Change TrackingA scheduled job reads CHANGETABLE and re-embeds changed rows in batchesBatching is fine; you want low overhead and only need to know which rows changed
Change data captureReads before-and-after values from change tablesHigh volume, or you only re-embed when the text columns really changed
Azure Functions SQL triggerA function receives changed rows (via Change Tracking) and calls the model outside the databaseYou want the work off the database, scaling independently
Azure Logic AppsA workflow polls for new or modified rows and calls the embedding serviceLow-code orchestration alongside other systems
Change event streamingEvery change is pushed to Event Hubs; consumers re-embedDecoupled, near-real-time, possibly several consumers (preview)
Microsoft FoundryHosts, evaluates and versions the embedding model; another process detects changesCentralized model management — paired with one of the methods above

Identify which columns to include

  • Include the columns that carry meaning a user might search for: names, titles, descriptions, categories, reviews — concatenated into one labelled string.
  • Exclude keys, codes, timestamps, numbers you filter on exactly, and sensitive data (the text is sent to the model).
  • Keep filters relational: price, stock, region and tenant stay as ordinary columns in the WHERE clause, where the latest vector indexes apply them during the search.
  • If the meaning of an attribute changes the answer ("for children"), put it in the text; if it is a hard constraint ("in stock"), keep it as a filter.
T-SQL
-- One embedding per product, built from the columns that carry meaning
UPDATE p
SET    p.Embedding = AI_GENERATE_EMBEDDINGS(
           CONCAT(p.Name, N'. Category: ', c.Name, N'. ', p.Description)
           USE MODEL EmbeddingSmall)
FROM   dbo.Product AS p
JOIN   dbo.Category AS c ON c.CategoryID = p.CategoryID
WHERE  p.Embedding IS NULL;                        -- or rows flagged as changed

Design and implement chunks

Long documents are split into chunks so each fits the model's input limit and each embedding is specific enough to match a question. Too large and the embedding blurs many topics; too small and chunks lose the context needed to answer. Overlap repeats the end of one chunk at the start of the next so a sentence on a boundary is not lost. Store chunks in their own table keyed by the parent row and chunk order.

T-SQL
-- Chunk long text and embed every chunk in one statement (compatibility level 170)
CREATE TABLE dbo.DocChunk
(
    DocID      int           NOT NULL,
    ChunkOrder bigint        NOT NULL,
    ChunkText  nvarchar(max) NOT NULL,
    Embedding  vector(1536)  NOT NULL,
    CONSTRAINT PK_DocChunk PRIMARY KEY (DocID, ChunkOrder)
);

INSERT dbo.DocChunk (DocID, ChunkOrder, ChunkText, Embedding)
SELECT d.DocID, c.chunk_order, c.chunk,
       AI_GENERATE_EMBEDDINGS(c.chunk USE MODEL EmbeddingSmall)
FROM dbo.Document AS d
CROSS APPLY AI_GENERATE_CHUNKS(
        SOURCE     = d.Title + N'. ' + d.Body,   -- put the context the chunk needs into the text
        CHUNK_TYPE = FIXED,                       -- the only chunk type
        CHUNK_SIZE = 1000,                        -- characters, not tokens
        OVERLAP    = 10                           -- percent of CHUNK_SIZE, 0 to 50
     ) AS c;

Generate embeddings

AI_GENERATE_EMBEDDINGS(text USE MODEL name [PARAMETERS json]) calls the model and returns the vector, which converts implicitly into a vector(n) column of the same dimension. It runs per row, so generate in batches (an initial backfill, then the maintenance method's changed rows) and add retry_count for throttling (HTTP 429).

Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric AI_GENERATE_CHUNKS needs compatibility level 170. On MI, AI_GENERATE_EMBEDDINGS needs the Always-up-to-date policy.

Full-text, semantic vector or hybrid

flowchart TB
  Q["What does the user type?"] --> A{"Exact words, codes,<br/>names, phrases?"}
  A -->|"yes"| FT["Full-text search<br/>CONTAINS · FREETEXTTABLE"]
  A -->|"no, a meaning"| B{"Do exact terms<br/>still matter?"}
  B -->|"no"| VS["Vector search<br/>VECTOR_SEARCH · VECTOR_DISTANCE"]
  B -->|"yes, both"| HY["Hybrid search<br/>full-text + vector, fused with RRF"]
Keyword search finds the words; vector search finds the meaning; hybrid gets both, which is why it usually wins on real catalogues with product codes and brand names.
Full-textVectorHybrid
MatchesWords, inflections, phrases, proximityMeaning, synonyms, paraphrases, other languagesBoth
MissesSynonyms ("parka" for "coat")Exact codes, rare names, negationsLittle — at the cost of two queries
NeedsA full-text indexEmbeddings and (for scale) a vector indexBoth, plus a fusion step
ScoreRANK from CONTAINSTABLE/FREETEXTTABLEDistance (smaller is closer)RRF score from the two ranks
T-SQL
-- Full-text index (preview in SQL database in Fabric)
CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
CREATE FULLTEXT INDEX ON dbo.Product (Name, Description)
    KEY INDEX PK_Product
    WITH CHANGE_TRACKING AUTO;

-- Predicates: filter only
SELECT ProductID, Name FROM dbo.Product
WHERE CONTAINS(Description, N'"waterproof" AND NEAR((jacket, hood), 5)');

-- Rowset functions: return a RANK to order by or fuse
SELECT TOP (20) p.ProductID, p.Name, ft.[RANK]
FROM FREETEXTTABLE(dbo.Product, (Name, Description), N'light rain jacket for hiking') AS ft
JOIN dbo.Product AS p ON p.ProductID = ft.[KEY]
ORDER BY ft.[RANK] DESC;
  • CONTAINS — precise: words, phrases, prefixes ("hik*"), NEAR, Boolean logic. FREETEXT — natural language: stems and thesaurus.
  • CONTAINSTABLE / FREETEXTTABLE return [KEY] and [RANK], which you need to sort or to fuse with vector results.
  • The full-text index needs a unique, single-column, non-nullable key index.
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric · preview

Design for vector data

  • vector(n) stores float32 elements, up to 1,998 dimensions; vector(n, float16) halves the storage and allows up to 3,996.
  • Size is roughly 4 bytes per dimension (2 for float16): a 1,536-dimension float32 vector is about 6 KB, so keep embeddings in a narrow table (or a chunk table) rather than widening a hot OLTP table.
  • Vectors are exposed as JSON arrays ('[0.1, 0.2, …]') and convert implicitly from strings and json.
  • A vector index is created per column and per metric; the latest version needs at least 100 non-NULL rows, supports full DML, and blocks TRUNCATE TABLE.
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric Applies to the vector type and functions. float16 vectors are GA in Azure SQL and Fabric, preview in SQL Server 2025.

Vector types and functions for semantic search

T-SQL
DECLARE @a vector(3) = '[1, 2, 2]', @b vector(3) = '[2, 1, 2]';

SELECT VECTOR_DISTANCE('cosine',    @a, @b) AS cosine_distance,   -- 1 - cosine similarity: 0 = same direction
       VECTOR_DISTANCE('euclidean', @a, @b) AS euclidean,         -- straight-line distance
       VECTOR_DISTANCE('dot',       @a, @b) AS neg_dot,           -- negative dot product: smaller = more similar
       VECTOR_NORM(@a, 'norm2')            AS length,             -- 3
       VECTOR_NORMALIZE(@a, 'norm2')       AS unit_vector,        -- [0.333, 0.667, 0.667]
       VECTORPROPERTY(@a, 'Dimensions')    AS dims,               -- 3
       VECTORPROPERTY(@a, 'BaseType')      AS base_type;          -- float32
VECTOR_DISTANCE(metric, v1, v2)
Exact distance between two vectors — the building block of ENN search and of similarity thresholds.
VECTOR_NORMALIZE(v, 'norm2') and VECTOR_NORM
Scale a vector to unit length (or measure it). With normalized vectors, dot orders results exactly like cosine, and is cheaper.
VECTORPROPERTY(v, 'Dimensions' | 'BaseType')
Inspect a vector — useful to validate that a model returned the expected size.
VECTOR_SEARCH(TABLE, COLUMN, SIMILAR_TO, METRIC)
Approximate search through a vector index, returning the table's columns plus distance.

ANN or ENN

ENN (exact, kNN)ANN (approximate)
HowTOP (k) … ORDER BY VECTOR_DISTANCE(…) over every rowSELECT TOP (k) WITH APPROXIMATE … FROM VECTOR_SEARCH(…) through a DiskANN index
Recall100% by definitionHigh, tunable by the engine; may miss a few true neighbours
CostGrows linearly with rowsSub-linear; scales to millions of vectors
Choose whenSmall tables, heavily filtered subsets, or building a recall baselineLarge tables where latency matters
T-SQL
DECLARE @q vector(1536) = AI_GENERATE_EMBEDDINGS(N'warm jacket for winter cycling' USE MODEL EmbeddingSmall);

-- ENN / kNN: exact, scans every row — fine up to tens of thousands of rows, or as a recall baseline
SELECT TOP (10) ProductID, Name, VECTOR_DISTANCE('cosine', Embedding, @q) AS distance
FROM dbo.Product
WHERE CategoryID = 3
ORDER BY distance;

-- ANN: approximate, served by a DiskANN vector index with the same metric
CREATE VECTOR INDEX vix_Product_Embedding ON dbo.Product (Embedding)
WITH (METRIC = 'cosine', TYPE = 'DiskANN');      -- latest version needs at least 100 non-NULL rows

SELECT TOP (10) WITH APPROXIMATE p.ProductID, p.Name, r.distance
FROM VECTOR_SEARCH(TABLE = dbo.Product AS p, COLUMN = Embedding,
                   SIMILAR_TO = @q, METRIC = 'cosine') AS r
WHERE p.CategoryID = 3                           -- iterative filtering: applied during the search
ORDER BY r.distance;                             -- ascending distance only
Applies to SQL Server 2025 · preview Azure SQL Database Azure SQL MI SQL database in Fabric Vector index and VECTOR_SEARCH. MI: GA with the Always-up-to-date policy, preview with the SQL Server 2025 policy. SQL Server 2025 needs PREVIEW_FEATURES = ON.

VECTOR_SEARCH rules the exam can test

  • Latest-version indexes need SELECT TOP (N) WITH APPROXIMATE; the old TOP_N parameter is deprecated and raises error 42274 against them.
  • ORDER BY must be on distance, ascending, and nothing else; WITH APPROXIMATE without VECTOR_SEARCH is an error.
  • TOP without APPROXIMATE runs an exact kNN search; with no matching index on the same column and metric the engine falls back to kNN with a warning.
  • GROUP BY, aggregates, window functions, UNION and DISTINCT go in an outer query over a VECTOR_SEARCH subquery; it cannot be used inside a view.
  • The FORCE_ANN_ONLY table hint forces the index path.

Evaluate vector index types and metrics

MetricMeasuresUse it when
cosineAngle between vectors, ignoring lengthThe default for text embeddings; what most embedding models are trained for
dotNegative dot product (direction and magnitude)Vectors are normalized (then it ranks like cosine), or the model's docs recommend it
euclideanStraight-line (L2) distanceMagnitude carries meaning, for example some image or sensor embeddings

The only index type is DiskANN, a graph-based ANN index designed to work from disk with a small memory footprint. The index metric must match the metric in the query, or the index is not used.

Implement hybrid search and reciprocal rank fusion

Full-text RANK and vector distance are on different scales, so you cannot add them. Reciprocal rank fusion uses only each item's position in each list: score = Σ 1 / (k + rank), with k typically 60. An item ranked well by both searches rises to the top; an item found by only one still gets partial credit.

T-SQL
-- Hybrid search: fuse a keyword ranking and a semantic ranking with reciprocal rank fusion
DECLARE @text nvarchar(400) = N'light rain jacket for hiking';
DECLARE @q vector(1536) = AI_GENERATE_EMBEDDINGS(@text USE MODEL EmbeddingSmall);
DECLARE @k int = 60;                              -- RRF constant; 60 is the conventional default

WITH keyword AS (
    SELECT TOP (50) ft.[KEY] AS ProductID,
           ROW_NUMBER() OVER (ORDER BY ft.[RANK] DESC) AS rnk
    FROM FREETEXTTABLE(dbo.Product, (Name, Description), @text) AS ft
),
semantic AS (
    SELECT ProductID, ROW_NUMBER() OVER (ORDER BY distance) AS rnk
    FROM (SELECT TOP (50) WITH APPROXIMATE p.ProductID, r.distance
          FROM VECTOR_SEARCH(TABLE = dbo.Product AS p, COLUMN = Embedding,
                             SIMILAR_TO = @q, METRIC = 'cosine') AS r
          ORDER BY r.distance) AS v                -- window functions need the subquery pattern
)
SELECT TOP (10) COALESCE(k.ProductID, s.ProductID) AS ProductID,
       COALESCE(1.0 / (@k + k.rnk), 0) + COALESCE(1.0 / (@k + s.rnk), 0) AS rrf_score
FROM keyword AS k
FULL OUTER JOIN semantic AS s ON s.ProductID = k.ProductID
ORDER BY rrf_score DESC;

Evaluate performance of vector and hybrid search

  • Relevance: measure recall of ANN against an exact (ENN) baseline for a sample of queries, and judge hybrid results on a labelled set.
  • Latency and cost: check the plan uses the vector index (not a kNN scan), keep vector columns narrow, and pre-filter with indexed relational columns.
  • Index health: sys.vector_indexes lists type, metric and build parameters; sys.dm_db_vector_indexes shows graph_catchup_pending_percent — background maintenance still to apply after heavy DML.
  • Model calls: embedding the question is a network call on every search — cache frequent queries and watch for HTTP 429.

Retrieval-augmented generation (RAG)

flowchart TB
  subgraph R["Retrieve"]
    direction LR
    Q["1 · User question"] --> EMB["2 · Embed the question<br/>AI_GENERATE_EMBEDDINGS"]
    EMB --> RET["3 · Find rows<br/>VECTOR_SEARCH · hybrid"]
  end
  subgraph A["Augment"]
    direction LR
    CTX["4 · Shape context<br/>FOR JSON · JSON_OBJECT"] --> PROMPT["5 · Build the prompt<br/>system + context + question"]
  end
  subgraph G["Generate"]
    direction LR
    CALL["6 · Call the chat model<br/>sp_invoke_external_rest_endpoint"] --> ANS["7 · Extract the answer<br/>JSON_VALUE $.result…"]
  end
  R ~~~ A
  A ~~~ G
RAG in T-SQL: retrieve the rows that matter, turn them into compact JSON, wrap them in a grounded prompt, call the chat model and read the answer out of the response envelope.

Identify use cases for RAG

  • Good fits: questions answered from your own, changing data — product and support assistants, policy and document Q&A, summarising a customer's history.
  • Poor fits: precise aggregates and reports ("total sales by region") — write SQL; or knowledge the model already has and that never depends on your data.
  • Why not fine-tuning: RAG reads current rows at question time and respects row-level security; a fine-tuned model is a snapshot.

Create a prompt and send it with sp_invoke_external_rest_endpoint

T-SQL
CREATE OR ALTER PROCEDURE dbo.usp_AskCatalog
    @Question nvarchar(1000),
    @Answer   nvarchar(max) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @q vector(1536) = AI_GENERATE_EMBEDDINGS(@Question USE MODEL EmbeddingSmall);

    -- 1. Retrieve and 2. convert structured rows to compact JSON context
    DECLARE @context nvarchar(max) = (
        SELECT Name, Color, ListPrice, Description
        FROM (SELECT TOP (5) WITH APPROXIMATE p.Name, p.Color, p.ListPrice, p.Description, r.distance
              FROM VECTOR_SEARCH(TABLE = dbo.Product AS p, COLUMN = Embedding,
                                 SIMILAR_TO = @q, METRIC = 'cosine') AS r
              WHERE p.IsActive = 1
              ORDER BY r.distance) AS hits
        ORDER BY distance
        FOR JSON PATH);

    -- 3. Build the chat request: grounding instructions + context + question
    DECLARE @payload nvarchar(max) = JSON_OBJECT(
        'messages': JSON_ARRAY(
            JSON_OBJECT('role': 'system',
                        'content': 'Answer only from the products in the context. If the answer is not there, say you do not know.'),
            JSON_OBJECT('role': 'user',
                        'content': CONCAT('Context: ', @context, ' Question: ', @Question))),
        'temperature': 0.2,
        'max_tokens': 400);

    -- 4. Send it to the chat model (an external model cannot do this: MODEL_TYPE is EMBEDDINGS only)
    DECLARE @response nvarchar(max), @rc int;
    EXECUTE @rc = sp_invoke_external_rest_endpoint
        @url         = N'https://contoso-ai.openai.azure.com/openai/deployments/gpt-chat/chat/completions?api-version=2024-10-21',
        @method      = 'POST',
        @payload     = @payload,
        @credential  = [https://contoso-ai.openai.azure.com/],
        @timeout     = 60,
        @retry_count = 3,                          -- retries 408, 429, 500, 502, 503, 504
        @response    = @response OUTPUT;

    -- 5. Extract the answer; @rc is 0 for any 2xx, otherwise the HTTP status code
    SET @Answer = CASE WHEN @rc = 0
                       THEN JSON_VALUE(@response, '$.result.choices[0].message.content')
                       ELSE CONCAT('Model call failed: HTTP ', @rc) END;
END;
Arguments
@url (HTTPS only), @method (default POST), @headers (flat JSON), @payload, @credential, @timeout (1–230 s, default 30), @retry_count (0–10) and @response OUTPUT.
Return value
0 for any 2xx status; otherwise the HTTP status code itself. If the call cannot be made at all, an error is raised.
Response envelope
$.response.status.http.code and .description, $.response.headers, and the endpoint's body under $.result.
Permissions
EXECUTE ANY EXTERNAL ENDPOINT on the database, and REFERENCES on the credential.
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric Enabled by default in Azure SQL Database and Fabric. SQL Server 2025 and MI (2025 or AUTD policy) need 'external rest endpoint enabled'.

Convert structured data to JSON for the model

  • FOR JSON PATH turns a result set into an array of objects; column aliases become keys (use them to give the model readable names).
  • JSON_OBJECT and JSON_ARRAY build the request body; JSON_ARRAYAGG aggregates rows into an array inside a larger object.
  • Send only the columns the answer needs — every column is tokens, cost and a possible data leak.

Send results and extract responses

The chat completion sits at $.result.choices[0].message.content — read it with JSON_VALUE (scalar) or JSON_QUERY (object). Check the return code before reading, handle 429 (throttled) and 401/403 (credential) explicitly, and ask for structured output when the answer must be processed further.

T-SQL
-- Ask for structured output, then shred it relationally
-- (add to the payload: 'response_format': JSON_OBJECT('type': 'json_object') and say "reply in JSON")
SELECT j.ProductName, j.Reason
FROM OPENJSON(JSON_VALUE(@response, '$.result.choices[0].message.content'), '$.recommendations')
     WITH (ProductName nvarchar(200) '$.name',
           Reason      nvarchar(400) '$.reason') AS j;

Ground the model and keep it honest

Tell the model to answer only from the context and to say when it does not know; keep temperature low for factual answers; and include identifiers in the context so the app can link the answer back to real rows. Retrieval runs as the calling user, so RLS and permissions decide what can ever reach the prompt.