Skill 1 · 35–40%

Design and develop database solutions

The biggest-weighted area with Skill 2. It asks you to pick the right table, type, index and programmability object for a requirement, to read advanced T-SQL, and to set up GitHub Copilot and Copilot in Fabric safely — with instruction files and MCP servers that give the assistant real schema context.

Skills measured as of March 12, 2026

What this skill tests

Four objective groups: database objects (tables, specialized tables, JSON, constraints, sequences, partitioning), programmability objects (views, functions, procedures, triggers), advanced T-SQL (CTEs, window, JSON, regex and fuzzy functions, graph MATCH, correlated queries, error handling) and AI-assisted tools. The T-SQL family lives in its own deep dive; this page covers the design decisions and summarises it.

Tables, data types and indexes

Table design questions are usually about size and correctness: the smallest type that fits, exact numerics for money, Unicode only where users type text, and a clustered key that is narrow, unique, static and ever-increasing. Rows live on 8 KB pages, and a row's fixed in-row data is limited to 8,060 bytes — large (max) values move off-row.

NeedChooseAvoid, and why
Whole numberstinyint 1 B · smallint 2 B · int 4 B · bigint 8 BOversizing every key to bigint widens every index that carries it
Money, quantitiesdecimal(p, s)float/real are approximate; money loses precision in division
Date and timedate 3 B, datetime2(n) 6–8 B, datetimeoffset for time zonesdatetime: 3.33 ms precision, larger range than needed
Textvarchar for ASCII codes; nvarchar or a _UTF8 collation for international textnvarchar(max) everywhere: cannot be an index key, blocks some optimizations
Semi-structured datajson (SQL Server 2025+, Azure SQL, Fabric)nvarchar(max) JSON: reparsed on every read
Embeddingsvector(n) — n must match the model's dimensionsvarbinary or JSON arrays: no vector functions, no vector index
Concurrency tokenrowversionTimestamps compared by value: clock skew and ties
T-SQL
-- Applies to: all four platforms (vector and json need SQL Server 2025 or later)
CREATE TABLE dbo.Product
(
    ProductID     int IDENTITY(1, 1) NOT NULL,
    Sku           varchar(20)        NOT NULL,       -- ASCII codes: varchar, not nvarchar
    Name          nvarchar(200)      NOT NULL,       -- user-facing text: Unicode
    Description   nvarchar(max)      NULL,           -- LOB, stored off-row when large
    ListPrice     decimal(10, 2)     NOT NULL,       -- exact numeric; avoid float and money
    CategoryID    smallint           NOT NULL,       -- smallest type that fits
    Attributes    json               NULL,           -- native binary JSON
    Embedding     vector(1536)       NULL,           -- float32 elements; size the model's dimensions
    CreatedAt     datetime2(3)       NOT NULL
        CONSTRAINT DF_Product_CreatedAt DEFAULT (SYSUTCDATETIME()),
    RowVer        rowversion         NOT NULL,       -- optimistic concurrency token
    CONSTRAINT PK_Product PRIMARY KEY CLUSTERED (ProductID),
    CONSTRAINT UQ_Product_Sku UNIQUE (Sku),
    CONSTRAINT CK_Product_ListPrice CHECK (ListPrice >= 0),
    CONSTRAINT FK_Product_Category FOREIGN KEY (CategoryID)
        REFERENCES dbo.Category (CategoryID) ON DELETE NO ACTION
);

-- Covering nonclustered index for a frequent lookup
CREATE NONCLUSTERED INDEX IX_Product_Category
    ON dbo.Product (CategoryID) INCLUDE (Name, ListPrice)
    WHERE ListPrice > 0;                               -- filtered index
  • Clustered index — the table itself, sorted by the key; one per table. The clustering key is copied into every nonclustered index.
  • Nonclustered index — up to 999 per table; INCLUDE columns make it covering, WHERE makes it filtered.
  • Unique constraint vs unique index — a constraint is implemented as a unique index; use the constraint when it expresses a business rule.

Columnstore indexes

Columnstore stores each column separately in compressed rowgroups of up to 1,048,576 rows and runs queries in batch mode. A clustered columnstore index (CCI) suits fact and history tables; a nonclustered columnstore (NCCI) on an OLTP table gives real-time operational analytics without an ETL copy. New rows land in a delta store until the tuple mover compresses them.

T-SQL
-- Real-time operational analytics: a nonclustered columnstore index on an OLTP table
CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_SalesOrderLine
    ON dbo.SalesOrderLine (OrderDate, ProductID, Quantity, LineTotal);

-- Analytics table: clustered columnstore, ordered to improve segment elimination
-- (ordered CCI: SQL Server 2022+ and Azure SQL; ordered NCCI: SQL Server 2025)
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
    ON dbo.FactSales
    ORDER (OrderDateKey);
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric Azure SQL Database needs Standard S3 or above, General Purpose, Business Critical or Hyperscale. In Fabric a clustered columnstore index must be created with the table, or mirroring stopped first.

Specialized tables

The outline lists five specialized table types. Each answers one specific requirement — pick it from the requirement, not from the feature list.

flowchart LR
  R1["Query rows as they were<br/>at a past time"] --> T["Temporal table<br/>SYSTEM_VERSIONING = ON"]
  R2["Prove rows were never<br/>altered or deleted"] --> L["Ledger table<br/>updatable or append-only"]
  R3["Extreme OLTP latency,<br/>hot latch contention"] --> M["Memory-optimized table<br/>MEMORY_OPTIMIZED = ON"]
  R4["Query files in a data lake<br/>without loading them"] --> E["External table<br/>or OPENROWSET"]
  R5["Traverse many-to-many<br/>relationships"] --> G["Graph tables<br/>AS NODE · AS EDGE"]
  R6["None of these"] --> N["Ordinary rowstore table<br/>plus the right indexes"]
Pick the table type from the requirement. Several can combine — a temporal table can also carry a columnstore index — but ledger and in-memory are not available in SQL database in Fabric.

Temporal tables

A system-versioned temporal table keeps the current row and moves every previous version to a history table, stamped with the period columns. Query the past with FOR SYSTEM_TIME (AS OF, FROM … TO, BETWEEN … AND, CONTAINED IN, ALL). HIDDEN period columns keep SELECT * unchanged for existing applications.

T-SQL
-- Applies to: all four platforms
CREATE TABLE dbo.Price
(
    ProductID  int            NOT NULL PRIMARY KEY CLUSTERED,
    ListPrice  decimal(10, 2) NOT NULL,
    ValidFrom  datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
    ValidTo    datetime2 GENERATED ALWAYS AS ROW END   HIDDEN NOT NULL,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (
        HISTORY_TABLE = dbo.PriceHistory,
        HISTORY_RETENTION_PERIOD = 2 YEARS));

-- What was the price on 1 March?
SELECT ProductID, ListPrice
FROM dbo.Price FOR SYSTEM_TIME AS OF '2026-03-01T00:00:00'
WHERE ProductID = 42;

Ledger tables

Ledger makes tampering detectable: each transaction is hashed into a blockchain-like structure, and database digests stored outside the database (immutable Azure Blob storage or Azure Confidential Ledger) let you prove the data has not changed. An updatable ledger table keeps history; an append-only one blocks updates and deletes. Ledger cannot be turned off for a table once it is on.

T-SQL
-- Applies to: SQL Server 2022+, Azure SQL Database, Azure SQL MI (not SQL database in Fabric)

-- Updatable ledger table: keeps a history table and a ledger view
CREATE TABLE dbo.AccountBalance
(
    AccountID int            NOT NULL PRIMARY KEY,
    Balance   decimal(19, 4) NOT NULL
)
WITH (SYSTEM_VERSIONING = ON, LEDGER = ON);

-- Append-only ledger table: INSERT only, UPDATE and DELETE are blocked
CREATE TABLE dbo.AuditEvent
(
    EventID   bigint IDENTITY NOT NULL,
    EventTime datetime2      NOT NULL DEFAULT SYSUTCDATETIME(),
    Detail    nvarchar(4000) NOT NULL
)
WITH (LEDGER = ON (APPEND_ONLY = ON));

-- Verify the database against its stored digests
EXECUTE sys.sp_verify_database_ledger_from_digest_storage;
Applies to SQL Server 2022+ Azure SQL Database Azure SQL MI SQL database in Fabric (not available)

Memory-optimized tables

In-Memory OLTP keeps the table in memory with lock- and latch-free structures — for contention hot spots such as session state, shopping carts and high-rate ingestion. Every memory-optimized table needs at least one index (hash for point lookups, nonclustered for ranges); SCHEMA_ONLY durability trades persistence for speed.

T-SQL
-- Applies to: SQL Server (needs a MEMORY_OPTIMIZED_DATA filegroup),
-- Azure SQL Database Premium/Business Critical, Azure SQL MI Business Critical.
-- Not available in SQL database in Fabric.
CREATE TABLE dbo.ShoppingCart
(
    CartID     int       NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000),
    CustomerID int       NOT NULL INDEX IX_Customer NONCLUSTERED,
    CreatedAt  datetime2 NOT NULL
)
WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);   -- SCHEMA_ONLY: data lost on restart

-- Natively compiled procedure for the hot path
CREATE PROCEDURE dbo.usp_AddCart @CartID int, @CustomerID int
WITH NATIVE_COMPILATION, SCHEMABINDING
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
    INSERT dbo.ShoppingCart (CartID, CustomerID, CreatedAt)
    VALUES (@CartID, @CustomerID, SYSUTCDATETIME());
END;
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric (not available) Azure SQL Database: Premium and Business Critical tiers only. Azure SQL MI: Business Critical only.

External tables

An external table maps files in a data lake — Parquet or CSV in Azure Blob storage or ADLS Gen2 — so they can be queried with T-SQL without loading. OPENROWSET(BULK …) is the ad hoc alternative that needs only the data source. Use the endpoint-specific prefixes abs:// and adls://; the generic https:// prefix is not accepted for external tables.

T-SQL
-- Data virtualization: Parquet files in ADLS Gen2
-- GA in SQL Server 2022+ and Azure SQL MI, preview in Azure SQL Database;
-- SQL database in Fabric reads Parquet and CSV from OneLake.
CREATE EXTERNAL DATA SOURCE SalesLake
WITH (LOCATION = 'adls://sales@contosolake.dfs.core.windows.net');   -- endpoint-specific prefix, not https://

CREATE EXTERNAL FILE FORMAT ParquetFormat WITH (FORMAT_TYPE = PARQUET);

CREATE EXTERNAL TABLE dbo.ext_WebClicks
(
    ClickTime datetime2,
    Url       nvarchar(400),
    UserId    int
)
WITH (LOCATION = 'clicks/2026/*.parquet', DATA_SOURCE = SalesLake, FILE_FORMAT = ParquetFormat);

-- Ad hoc exploration without an external table
SELECT TOP (10) *
FROM OPENROWSET(BULK 'clicks/2026/*.parquet', DATA_SOURCE = 'SalesLake', FORMAT = 'parquet') AS f;
Applies to SQL Server 2022+ Azure SQL Database · preview Azure SQL MI SQL database in Fabric SQL Server 2025 reads Parquet, CSV and Delta natively, without the PolyBase services. Azure SQL Database data virtualization is in preview. Fabric: Parquet and CSV from OneLake.

Graph tables

Graph tables model entities as node tables and relationships as edge tables, each with hidden pseudo-columns ($node_id, $from_id, $to_id). An edge constraint restricts which node types an edge may connect. Traverse them with MATCH and SHORTEST_PATH — see the deep dive.

T-SQL
-- Applies to: SQL Server 2017+, Azure SQL Database, Azure SQL MI
CREATE TABLE dbo.Person   (PersonID int PRIMARY KEY, Name nvarchar(100)) AS NODE;
CREATE TABLE dbo.Skill    (SkillID  int PRIMARY KEY, Name nvarchar(100)) AS NODE;
CREATE TABLE dbo.HasSkill
(
    Level tinyint,
    CONSTRAINT EC_HasSkill CONNECTION (dbo.Person TO dbo.Skill) ON DELETE CASCADE
) AS EDGE;

-- Edges are inserted with the pseudo-columns $from_id and $to_id
INSERT dbo.HasSkill ($from_id, $to_id, Level)
SELECT p.$node_id, s.$node_id, 4
FROM dbo.Person AS p, dbo.Skill AS s
WHERE p.PersonID = 1 AND s.SkillID = 7;

JSON columns and indexes

The native json type stores documents in a parsed binary form: faster reads, in-place updates through the modify method, and better compression. A JSON index indexes paths inside the document so one index serves JSON_VALUE, JSON_PATH_EXISTS and JSON_CONTAINS predicates. A json column cannot be the key of an ordinary index.

T-SQL
-- Applies to: SQL Server 2025, Azure SQL Database, Azure SQL MI (2025 or AUTD policy),
-- SQL database in Fabric. A JSON index needs a clustered primary key.
CREATE TABLE dbo.Orders
(
    OrderID      int IDENTITY PRIMARY KEY CLUSTERED,
    OrderDetails json NOT NULL
);

CREATE JSON INDEX IX_Orders_Details
    ON dbo.Orders (OrderDetails)
    FOR ('$.customer.id', '$.status');          -- optional: index only these paths

-- Served by the JSON index
SELECT OrderID
FROM dbo.Orders
WHERE JSON_VALUE(OrderDetails, '$.status') = 'shipped';

-- JSON stored as nvarchar (any version): validate it and index one property
ALTER TABLE dbo.LegacyOrders
    ADD CONSTRAINT CK_LegacyOrders_IsJson CHECK (ISJSON(Payload) = 1),
        CustomerId AS CAST(JSON_VALUE(Payload, '$.customer.id') AS int);
CREATE INDEX IX_LegacyOrders_CustomerId ON dbo.LegacyOrders (CustomerId);
Applies to SQL Server 2025 Azure SQL Database Azure SQL MI SQL database in Fabric MI needs the SQL Server 2025 or Always-up-to-date update policy. On SQL Server 2022 and earlier, store JSON as nvarchar and index a computed column.

Constraints

PRIMARY KEY
Unique and not null; one per table; clustered by default unless you say NONCLUSTERED.
FOREIGN KEY
References a primary key or unique constraint. Referential actions ON DELETE/ON UPDATE: NO ACTION (default), CASCADE, SET NULL, SET DEFAULT. Index the foreign key column yourself — SQL Server does not.
UNIQUE
Allows a single NULL (use a filtered unique index to allow many).
CHECK
A row-level predicate; it passes when the expression is true or unknown — so NULL passes.
DEFAULT
Used only when the column is omitted from the INSERT, not when NULL is supplied explicitly.

Trusted versus untrusted constraints

ALTER TABLE … WITH NOCHECK ADD CONSTRAINT skips validating existing rows and leaves the constraint not trusted (is_not_trusted = 1), so the optimizer cannot use it. Re-enable with WITH CHECK CHECK CONSTRAINT to validate the data and make it trusted again.

Sequences

A SEQUENCE is a schema-bound number generator independent of any table. Choose it over IDENTITY when you need the number before the insert, one series shared across tables, a defined cycle (CYCLE), or a block of values at once (sp_sequence_get_range).

T-SQL
-- Applies to: all four platforms
CREATE SEQUENCE dbo.OrderNumber
    AS bigint
    START WITH 100000
    INCREMENT BY 1
    CACHE 50;                    -- faster, but cached values are lost on an unexpected shutdown (gaps)

CREATE TABLE dbo.OrderHeader
(
    OrderNo   bigint NOT NULL
        CONSTRAINT DF_OrderHeader_OrderNo DEFAULT (NEXT VALUE FOR dbo.OrderNumber),
    OrderDate date   NOT NULL,
    CONSTRAINT PK_OrderHeader PRIMARY KEY (OrderNo)
);

-- The same sequence can number rows in several tables, and you can take the
-- value before the INSERT — neither is possible with IDENTITY.
DECLARE @next bigint = NEXT VALUE FOR dbo.OrderNumber;

Neither sequences nor IDENTITY are gap-free

Rolled-back transactions consume values, and CACHE loses unused cached values on an unexpected shutdown. If the requirement is "no gaps" (invoice numbers in some jurisdictions), you need a serialized counter table, not a sequence.

Partitioning tables and indexes

Partitioning splits one table into ranges by a column — almost always a date — using a partition function (the boundaries) and a partition scheme (where each partition lives). Benefits are manageability (truncate, switch, rebuild one partition) and partition elimination; it is not a substitute for indexing. Indexes created on the same scheme are aligned, which partition switching requires.

T-SQL
-- Applies to: all four platforms; Azure SQL Database and Fabric have only the PRIMARY
-- filegroup, and SQL database in Fabric does not allow SWITCH PARTITION.
CREATE PARTITION FUNCTION pfOrderMonth (date)
    AS RANGE RIGHT FOR VALUES ('2026-01-01', '2026-02-01', '2026-03-01');   -- boundary starts a partition

CREATE PARTITION SCHEME psOrderMonth
    AS PARTITION pfOrderMonth ALL TO ([PRIMARY]);

CREATE TABLE dbo.OrderLine
(
    OrderDate date   NOT NULL,
    OrderNo   bigint NOT NULL,
    LineNo    int    NOT NULL,
    Amount    decimal(19, 4) NOT NULL,
    CONSTRAINT PK_OrderLine PRIMARY KEY CLUSTERED (OrderDate, OrderNo, LineNo)   -- partitioning column in the key
) ON psOrderMonth (OrderDate);

-- Which partition holds a row, and fast removal of old data
SELECT $PARTITION.pfOrderMonth('2026-02-14') AS PartitionNumber;
TRUNCATE TABLE dbo.OrderLine WITH (PARTITIONS (1));
  • RANGE RIGHT: a boundary value belongs to the partition on its right — natural for dates.
  • A partitioned unique index must include the partitioning column in its key.
  • Sliding window: SPLIT RANGE to add a new boundary, SWITCH out the oldest partition, MERGE RANGE to remove it.

Programmability objects

ObjectUse it forKnow this
View Encapsulating a query, simplifying permissions, a stable contract over changing tables WITH SCHEMABINDING blocks changes to underlying columns; WITH CHECK OPTION rejects writes that would fall outside the view; an indexed view needs SCHEMABINDING and a unique clustered index
Scalar function A reusable expression returning one value Called per row; scalar UDF inlining (compat 150+) rewrites eligible functions into the query; avoid data access inside them
Inline table-valued function A parameterized view — a single SELECT Expanded into the calling query and optimized with it; the preferred TVF
Multi-statement TVF Procedural logic that fills a @table Interleaved execution (compat 140+) fixes its old fixed cardinality guess, but it is still a black box to the optimizer
Stored procedure Transactions, multiple statements, output parameters, a permission boundary SET NOCOUNT ON, SET XACT_ABORT ON, TRY…CATCH + THROW; ownership chaining lets callers run it without table permissions
DML trigger Enforcing rules or auditing on INSERT/UPDATE/DELETE Fires once per statement; read inserted and deleted set-based; INSTEAD OF makes a multi-table view updatable
DDL trigger Auditing or blocking schema changes Database-scoped only in Azure SQL Database and Fabric; reads EVENTDATA()
T-SQL
-- Indexed view: SCHEMABINDING, two-part names, COUNT_BIG(*) with GROUP BY
CREATE OR ALTER VIEW dbo.vw_SalesByProduct
WITH SCHEMABINDING
AS
SELECT ProductID, SUM(LineTotal) AS Total, COUNT_BIG(*) AS LineCount
FROM dbo.SalesOrderLine
GROUP BY ProductID;
GO
CREATE UNIQUE CLUSTERED INDEX UCX_vw_SalesByProduct ON dbo.vw_SalesByProduct (ProductID);
GO

-- Inline table-valued function: one SELECT, optimized like a parameterized view
CREATE OR ALTER FUNCTION dbo.tvf_OrdersForCustomer (@CustomerID int)
RETURNS TABLE
AS
RETURN
    SELECT OrderNo, OrderDate, Total
    FROM dbo.OrderHeader
    WHERE CustomerID = @CustomerID;
GO

-- Stored procedure skeleton the exam expects
CREATE OR ALTER PROCEDURE dbo.usp_PlaceOrder
    @CustomerID int,
    @OrderNo    bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON;      -- no "n rows affected" messages
    SET XACT_ABORT ON;   -- any runtime error dooms and rolls back the transaction
    BEGIN TRY
        BEGIN TRANSACTION;
        SET @OrderNo = NEXT VALUE FOR dbo.OrderNumber;
        INSERT dbo.OrderHeader (OrderNo, CustomerID, OrderDate) VALUES (@OrderNo, @CustomerID, GETUTCDATE());
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
        THROW;           -- re-raise the original error to the caller
    END CATCH;
END;
T-SQL
-- AFTER trigger: set-based, works for multi-row statements
CREATE OR ALTER TRIGGER dbo.trg_Product_Audit
ON dbo.Product
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    IF NOT UPDATE(ListPrice) RETURN;
    INSERT dbo.PriceChange (ProductID, OldPrice, NewPrice, ChangedAt)
    SELECT d.ProductID, d.ListPrice, i.ListPrice, SYSUTCDATETIME()
    FROM inserted AS i
    JOIN deleted  AS d ON d.ProductID = i.ProductID
    WHERE i.ListPrice <> d.ListPrice;
END;

Triggers that assume one row

SELECT @id = ProductID FROM inserted silently handles only one arbitrary row of a multi-row update. The exam's "fix the trigger" items are almost always this: rewrite it as a set-based INSERT … SELECT joining inserted and deleted.

Advanced T-SQL at a glance

The outline names eight families. Each has worked examples in the Advanced T-SQL deep dive; this is the one-line summary of each.

FamilyReach for it whenPlatform line
CTEsNaming a step, or walking a hierarchy recursivelyAll
Window functionsRanking, running totals, previous/next row, top-n per group — without collapsing rowsAll
JSON functionsBuilding JSON (JSON_OBJECT, JSON_ARRAY, JSON_ARRAYAGG) or reading it (OPENJSON, JSON_VALUE, JSON_CONTAINS)Aggregates and JSON_CONTAINS: SQL Server 2025+, Azure SQL, Fabric
Regular expressionsValidating, extracting, replacing or splitting by patternSQL Server 2025+, Azure SQL, Fabric (GA)
Fuzzy matchingTypos and near-duplicates: EDIT_DISTANCE, JARO_WINKLER_*Preview everywhere
Graph MATCHTraversing node and edge tables, shortest pathsSQL Server 2017+, Azure SQL
Correlated queriesPer-row subqueries: EXISTS, APPLYAll
Error handlingTRY…CATCH, THROW, XACT_STATE()All

SQL solutions with AI-assisted tools

The exam covers two assistant families — GitHub Copilot (in VS Code with the MSSQL extension, in Visual Studio and in SSMS 22) and Copilot in Fabric (in the SQL database query editor) — and how to give them real context safely: a chosen model, instruction files and MCP tools.

flowchart TB
  DEV["Developer prompt<br/>ask · edit · agent · plan"] --> INSTR
  subgraph HOST["Copilot chat session"]
    MODEL["Model picker<br/>choose per task"]
    INSTR["Instruction files<br/>copilot-instructions.md"]
    TOOLS["Tools picker<br/>MCP tools on or off"]
  end
  TOOLS --> MSSQL["MSSQL extension tools<br/>connect · run_query"]
  TOOLS --> SQLMCP["SQL MCP Server<br/>Data API builder /mcp"]
  TOOLS --> FAB["Fabric data agent<br/>as an MCP server"]
A Copilot chat session combines the model you pick, the instruction files it reads and the MCP tools you allow. Each MCP server on the right reaches the database with its own identity and permissions.

Security impact

  • What leaves your machine: the prompt, the code being edited, content from open files and — when connected — schema information. Prompts and responses are not used to train the models, and traffic is encrypted.
  • Data classification and compliance: check whether policy allows AI assistance on databases holding PII, financial or health data, and whether schema is itself considered confidential.
  • Credentials: never paste passwords or connection strings into prompts or commit them in mcp.json; use input prompts (${input:…}), environment variables or a secret store.
  • Least privilege for MCP: a dedicated identity per MCP server, read-only and schema-only where that is all the workflow needs, and dev or test databases — not production — for routine work.
  • Review every suggestion: check generated GRANT statements, dynamic SQL (parameterized sp_executesql, never concatenation) and views that expose more columns than intended.
  • Approvals: agent mode asks before each tool call; "always allow" removes that safeguard for the tool, so grant it only for read-only tools.

Enable GitHub Copilot and Copilot in Fabric

AssistantPrerequisitesHow it is switched on
GitHub Copilot in SSMS 22 A GitHub account with a Copilot plan (Free, Pro, Business or Enterprise) Visual Studio Installer → Modify SSMS → AI Assistance workload; then sign in from the Copilot badge
GitHub Copilot in VS Code The same Copilot plan, plus the MSSQL extension for database context Install Copilot and sign in; right-click a connected database → Chat with this database, or use @mssql
Copilot in Fabric A paid Fabric capacity (F2 or higher) or Power BI Premium (P1 or higher) Fabric admin portal → tenant setting Users can use Copilot and other features powered by Azure OpenAI, scoped to security groups; then the Copilot button in the SQL database query editor

Organization policies decide what users can switch on

For Copilot Business and Enterprise, organization and enterprise owners control policies such as which models are available, whether MCP servers may be used, and whether suggestions matching public code are allowed. A developer who cannot see a model or an MCP option in the picker is usually blocked by policy, not by the IDE.

Models and MCP tools in a chat session

  • Model picker: choose a stronger reasoning model for design and optimization, a faster one for routine completions.
  • Modes: ask for a single answer, edit for changes across files, agent for multi-step work that calls tools, plan to produce a written plan before any DDL.
  • Tools picker (agent mode): lists every MCP server and extension tool; enable only what the task needs. The MSSQL extension contributes tools such as connect, list_databases and run_query automatically — no @mssql mention needed in agent mode.
  • Schema awareness: the @mssql participant and agent tools see your connected schema; inline completions do not.
  • Copilot in Fabric already has the workspace's context; to use a Fabric data agent from outside Fabric, publish it and copy its MCP server URL from Settings → Model Context Protocol.

Copilot instruction files

FileScopeNotes
.github/copilot-instructions.mdEvery request in the repositoryPlain Markdown; the team's conventions
.github/instructions/NAME.instructions.mdFiles matching applyTo globsYAML frontmatter; combined with the repository-wide file when both apply
AGENTS.mdAgents; the nearest file in the tree winsCan sit in any folder
.github/prompts/NAME.prompt.mdA reusable task, run on demandInvoked as /NAME in chat; inputs use ${input:name}
copilot-instructions.md
<!-- .github/copilot-instructions.md — applies to every chat request in the repository -->
# Database conventions
- Target platform: Azure SQL Database, compatibility level 170.
- Always schema-qualify objects (dbo.Customer) and list columns explicitly; never SELECT *.
- Stored procedures: SET NOCOUNT ON, SET XACT_ABORT ON, TRY...CATCH with THROW.
- Parameterize everything; build dynamic SQL only with sp_executesql and parameters.
- Never emit GRANT ... TO public, credentials, or connection strings.
tsql.instructions.md
---
applyTo: "src/**/*.sql"
---
<!-- .github/instructions/tsql.instructions.md — only for files that match applyTo -->
Use the json data type for semi-structured columns and add a JSON index for queried paths.
Prefer CREATE OR ALTER for programmability objects.

Good instructions are specific (exact naming patterns, not "use good names"), give an example of the preferred pattern and put the most important rules first. Full samples, including a prompt file, are in the Configuration reference.

Connect to MCP server endpoints

TargetMCP optionHow you connect
SQL Server, Azure SQL, SQL database in Fabric MSSQL extension tools Built in: connect in the extension and use agent mode — no separate server to configure
SQL Server, Azure SQL, SQL database in Fabric SQL MCP Server (Data API builder 2.x) Enable runtime.mcp in dab-config.json, run dab start, then add the /mcp URL (HTTP) or run DAB with --mcp-stdio
Fabric lakehouse, warehouse, semantic model Fabric data agent published as an MCP server Publish the data agent, copy the MCP server URL, add it as an HTTP server; sign in with Entra ID
Azure resources broadly Azure MCP Server Install it, sign in to Azure, then use agent mode

In VS Code, add servers with MCP: Add Server or by editing .vscode/mcp.json, then check in agent mode's tools picker that the server is running and its tools are listed. Troubleshooting follows the usual order: JSON syntax (server missing), expired sign-in (authentication failed), firewall (timeout), permissions (empty schema).

SQL MCP Server tools are generated from configuration

SQL MCP Server exposes seven DML tools — describe_entities, create_record, read_records, update_record, delete_record, execute_entity and aggregate_records — and only for entities and roles your dab-config.json allows. Agents never see raw SQL access, so DAB permissions are the guardrail. Turning delete-record off globally is a common production setting.