DP-800 · Every trap from every sheet · One page

The Trap Compendium

All of it in one place — no jumping between files. Your own repeat misses come first, then everything by topic. Cover the right column, work down the left, and click any row you're solid on to dim it.

Traps—
Sections18
Skim time~25 min
Morning-ofThe Final Twenty
★ Your repeatsPlatform & tiersPartitioning IndexingPlans & diagnosisProgrammability JSONRegexFull-text & fuzzy Window functionsTemporal & sequencesGraph EncryptionRLS, DDM & permissionsIdentity, audit, network Vectors & chunkingRAG & RESTDAB DevOpsFabric & Copilot★ Final Twenty
The meta-trap that sits above all of these: requirements are elimination rules, not descriptions. Almost every mark you've dropped went to a qualifying phrase you read as background — "CPU and I/O are low", "capacity in Canada", "on every execution path", "distinct price levels", "normalized 0–100", "unless they are granted key access", "or a secret store". Before you answer anything: which phrase, if deleted, would change my answer?

★ 0 — Your own repeat misses the ones that have actually cost you marks

TrapThe truth
★★ Chunking strategy — missed twiceStructure exists → structural. Precise match and wide context → hierarchical. No structure + topic drift → semantic. "Quality over ingestion cost" licenses the expensive ones.
★★ ExpectedSqlException — lost twiceA database test expecting a database error → the answer contains "Sql" + "Exception". Not "Scalar Value", not [ExpectedException].
★ Always Encrypted "unless they are granted key access"That clause describes the mechanism's boundary. It is not an instruction to grant access. Descriptions of limits belong in correct answers.
★ RANK for "top 3 distinct levels"RANK skips after ties (1,2,2,4) so <=3 loses a whole level. Use DENSE_RANK.
★ EDIT_DISTANCE for a "normalized 0–100" thresholdThat's a raw edit count. The 0–100 ones are the _SIMILARITY functions.
★ Temporal CONTAINED IN = "active during the period"It means the row's entire life fit inside the window. A currently-active row can never match it.
★ Scalar UDF vs inline TVFMissed in both directions. One value inside an expression → scalar. Parameterised rowset → inline TVF + CROSS APPLY. Check the return shape first.
★ Query Performance Insight viewLow CPU + low I/O + high latency = too many round trips. Custom view + Execution count metric. Long Running Queries is wrong — the queries are short.
★ dotnet build to deployBuild makes the dacpac and touches no database. Deploying is a separate publish step: sqlpackage /Action:Publish, or azure/sql-action.
★ Return code to carry a row countRETURN is status only. A scalar alongside a result set = OUTPUT parameter, initialised explicitly.
★ Fabric Copilot with only the usage settingA named non-US region also needs cross-geo processing — and "delegated" means tenant AND capacity. Four switches.
★ GET to call chat completionsRetrieval already happened in SQL. You're sending a prompt → POST. GET has no request body.
JSON cluster — three lossesName the verb: construct (JSON_OBJECT/ARRAY/AGG) · convert (FOR JSON) · parse (JSON_VALUE/QUERY/OPENJSON).
DML vs DDL triggerINSERT/UPDATE/DELETE = DML. CREATE/ALTER/DROP = DDL.
SNAPSHOT concurrency outcomeFirst committer wins; the second gets an update conflict. Not blocking, not a deadlock.
"Repeated deployments" seed dataMust be idempotent → post-deployment MERGE, never INSERT.

1 — Platform, tiers & compute

TrapThe truth
Azure SQL Database can run Agent jobs / linked servers / cross-database queriesNo. Those are instance-level → Managed Instance or VM.
Managed Instance gives you OS accessNo. A named SQL version, OS access or third-party install → VM only.
Multiple filegroups on Azure SQL DatabasePRIMARY only. Any storage-tiering or filegroup-backup option is wrong on that platform.
Fabric SQL database is just another Azure SQLIts point is OLTP + analytics together — auto-mirroring to OneLake as Delta Parquet, read-only analytics endpoint.
GP or BC for a 6 TB databaseBoth cap at 4 TB → Hyperscale.
Business Critical is just "faster GP"It's also where In-Memory OLTP and the free read replica live.
Serverless suits a steady workload"Continuously allocated" or "must not pause" kills serverless.
DTU can express a CPU requirementIt's a blended unit. "CPU/memory baselines" kills DTU → vCore.
Elastic pool for one busy databasePools are for many databases with non-overlapping peaks.

2 — Partitioning

TrapThe truth
"Partitioning improves query performance"Only for queries filtering on the partitioning column. Its real value is manageability.
N boundaries = N partitionsN + 1. Always.
Direction changes the partition countIt doesn't. RIGHT/LEFT only changes which side the boundary value falls on.
RANGE LEFT with first-of-month boundariesPuts the 1st into the previous month. First-of-period → RANGE RIGHT.
Building the scheme before the functionImpossible. Function → Scheme → Table.
Unique index without the partitioning columnMsg 1908. Unique keys must include it.
A non-aligned index is harmlessIt compiles, then silently blocks SWITCH.
SWITCH into a non-empty partitionFails. Target must be empty.
SWITCH in without a CHECK constraintFails. Staging must prove its rows fit the target range.
SPLIT without NEXT USED"Partition scheme has no next used filegroup."
SPLIT or MERGE on a populated partitionPhysically moves data, fully logged, holding locks. Always operate on empty partitions.
Partitioning = shardingPartitioning is within one database. Sharding spans databases.
Sliding-window orderSWITCH out → MERGE → NEXT USED → SPLIT.

3 — Indexing & table design

TrapThe truth
Fix a Key Lookup by adding key columnsUse INCLUDE — key columns bloat every level of the tree.
Index on (A,B,C) helps WHERE B = …Leftmost prefix. Only A, A+B, or A+B+C.
Function around a column in WHERENon-SARGable → scan. Rewrite as a range, or PERSISTED computed column.
NVARCHAR column vs VARCHAR literalImplicit conversion on the column → scan. Match the types.
Clustered columnstore for an OLTP table needing analyticsIt replaces the rowstore. HTAP wants nonclustered columnstore.
Sequential IDENTITY clustered key is always bestIt's the N-U-S-E ideal and the cause of last-page latch contention at extreme insert rates.
A B-tree index accelerates vector distanceIt can't. Nearest-neighbour in 1536 dimensions isn't a range predicate.
An index speeds up a regex or fuzzy predicateNeither is SARGable. Pre-filter, or use a PERSISTED computed column.
REORGANIZE updates statisticsIt doesn't. REBUILD does.
REBUILD is always onlineIt takes an exclusive lock unless ONLINE = ON. REORGANIZE is always online.
Filtered index will always be usedNeeds the right session SET options and a predicate provably covered by the filter.

4 — Execution plans & diagnosis

TrapThe truth
A big estimate/actual gap means "add an index"It means update statistics. Fix estimates before tuning indexes.
SHOWPLAN_TEXT shows actual rowsEstimated only, and it doesn't execute. Actual needs STATISTICS PROFILE / XML.
Read a text plan top-downMost-indented executes first.
A Missing Index warning is an instructionA suggestion that ignores existing indexes and write cost.
"No Join Predicate" is a performance hintIt's a bug — an accidental cross join.
Elapsed time is the tuning metricLogical reads — stable between runs. SET STATISTICS IO ON.
Spill warnings mean you need more memoryUsually the estimate was wrong → statistics.
Query Store and DMVs answer the same questionQuery Store = history and plan regression. DMVs = right now.
Query Store must be enabled firstOn by default in Azure SQL Database.
Query Store verb mapRegressed Queries view finds · plan forcing fixes · hints shape · wait stats explain.
PAGELATCH_EX means a locking problemA latch is an internal memory-page protection. This is last-page insert contention.
Error 1205Deadlock → retry with a random delay.

5 — Procedures, functions & triggers

TrapThe truth
Multi-statement TVFs are fineOpaque to the optimiser, fixed row estimate. One SELECT → inline TVF.
Inline TVF syntaxRETURNS TABLE then RETURN (SELECT …) — no BEGIN/END, no declared table shape.
A function can update a tableNo side effects. That's a stored procedure.
A default on an OUTPUT parameter guarantees a valueOnly if the caller omits the argument. "On every execution path" → assign explicitly.
@@ROWCOUNT is reliable in a RETURNIt reflects the last statement — any inserted statement changes it.
Concatenate user input into dynamic SQLsp_executesql with parameters — injection-safe and plan-reusable.
Grant SELECT for least privilegeGRANT EXECUTE on procedures — ownership chaining means no table permission at all.
Least privilege = a clever permissionThe narrowest boring one. VIEW DEFINITION is metadata only, not data.

6 — JSON

TrapThe truth
JSON_VALUE on an array or object pathReturns NULL silently in lax mode — no error. Use JSON_QUERY.
JSON_QUERY on a scalar pathReturns NULL. Use JSON_VALUE.
Nesting JSON inside FOR JSON outputEscaped as a string. Wrap in JSON_QUERY.
JSON_MODIFY with a JSON string valueSame escaping problem. Wrap in JSON_QUERY.
JSON_MODIFY(…, NULL)lax = deletes the key · strict = sets it to null.
AS JSON in OPENJSON on any typeMust be NVARCHAR(MAX).
OPENJSON column at an array without AS JSONSilent NULL.
FOR JSON and NULL columnsOmitted by default → INCLUDE_NULL_VALUES.
ROOT + WITHOUT_ARRAY_WRAPPERCannot be combined.
WITHOUT_ARRAY_WRAPPER with multiple rowsObjects concatenated without commas — invalid JSON. Pair with TOP 1.
Which option for which problemWITHOUT_ARRAY_WRAPPER = single row. INCLUDE_NULL_VALUES = multi-row nulls.
JSON_VALUE(...) IS NULL to detect a missing keyAlso true when the key exists holding null → JSON_PATH_EXISTS.
JSON_ARRAY with GROUP BYIt's not an aggregate → JSON_ARRAYAGG.
JSON_VALUE for a long stringCaps at nvarchar(4000) → OPENJSON … WITH (col NVARCHAR(MAX)).
JSON_VALUE compared to a numberReturns text → implicit conversion, no seek. CAST + PERSISTED computed column.
ORDER BY after FOR JSONSyntax error. FOR JSON is always last.

7 — Regular expressions

TrapThe truth
Regex default is case-insensitiveDefault flag is 'c' — case-sensitive. An empty string also means 'c'.
Contradictory flags error outThe last character wins. 'ci' insensitive, 'ic' sensitive.
SELECT REGEXP_LIKE(...)It's a predicate. Use in WHERE/HAVING/CHECK, or wrap in CASE/IIF.
Every regex function needs compatibility level 170Only REGEXP_LIKE does; the other scalar regex functions work at any level. None of them exist before SQL Server 2025. (Updated Oct 2026.)
All seven regex functions are scalarTwo return tables — REGEXP_MATCHES, REGEXP_SPLIT_TO_TABLE. Need CROSS APPLY.
Regex beats LIKE for a simple prefixLIKE 'ABC%' can seek; regex always scans.
STRING_SPLIT handles mixed delimitersSingle character only → REGEXP_SPLIT_TO_TABLE.
Pattern length is unlimitedMax 8,000 bytes; input LOB up to 2 MB.

8 — Full-text & fuzzy matching

TrapThe truth
CONTAINS(col, 'two words')Syntax error. A phrase needs double quotes inside the string.
FREETEXT(col, 'red AND blue')"AND" is treated as a word. FREETEXT has no operators.
Multiple full-text indexes per tableOne per table. Add columns to it.
KEY INDEX can be compositeMust be unique, single-column, non-nullable.
Stale full-text resultsCHANGE_TRACKING is MANUAL or OFF — should be AUTO.
Full-text RANK sorts ascendingHigher RANK is better → DESC. Opposite of vector distance.
All fuzzy functions share a scaleCount (EDIT_DISTANCE) · 0–100 (EDIT_DISTANCE_SIMILARITY, JARO_WINKLER_SIMILARITY) · float (JARO_WINKLER_DISTANCE) · 0–4 (DIFFERENCE). Match the stem.
"Distance" and "similarity" sort the same wayDistance: lower better. Similarity: higher better.
FREETEXT understands conceptsIt matches word forms, not meaning. "Keep coffee hot" won't find "insulated flask" — that's vector search.

9 — Window functions

TrapThe truth
ROW_NUMBER when ties must be returnedUnique numbers, drops ties.
DENSE_RANK for deduplicationTied rows share rank 1 → you'd keep both. Dedup wants ROW_NUMBER.
NTILE(4) gives quartiles by valueBuckets by row count. Value cut-offs need PERCENT_RANK / PERCENTILE_*.
Omitting the frame on a running totalDefaults to RANGE, which lumps tied ORDER BY values. Write ROWS.
LAST_VALUE without a frameReturns the current row's value. Add ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
FIRST_VALUE needs the same fixIt doesn't — the default frame already starts at UNBOUNDED PRECEDING.
Window function in a WHERE clauseNot allowed — they run after WHERE. Wrap in a CTE.
PERCENTILE_CONT returns a real data valueIt interpolates. "Must exist in the data" → PERCENTILE_DISC.
PERCENTILE_* uses ORDER BY inside OVERNeeds WITHIN GROUP (ORDER BY …) OVER (PARTITION BY …).
PERCENT_RANK and CUME_DIST are the samePERCENT_RANK excludes peers; CUME_DIST includes them.

10 — Temporal, sequences & grouping

TrapThe truth
FROM…TO and BETWEEN…AND are synonymsFROM's upper bound is exclusive; BETWEEN's is inclusive.
Temporal tables need a triggerNo. The engine copies rows automatically.
You can TRUNCATE a temporal tableNot while versioning is on. Also can't DROP without turning it off.
Temporal proves data wasn't alteredIt's a history feature. Proof needs ledger.
CACHE reserves values for youAn internal performance setting. Reserving is sp_sequence_get_range.
NO CACHE guarantees a gapless seriesRemoves one cause. Rolled-back transactions still consume numbers.
IDENTITY can span several tablesIt can't. That's a SEQUENCE.
Aggregates can go in WHERENo — HAVING. The aggregate doesn't exist when WHERE runs.
ROLLUP(a,b) gives 4 groupings3 — it's n+1. CUBE gives 2ⁿ = 4.
A NULL in a subtotal row is just a NULLGROUPING() tells a subtotal placeholder from real NULL data.
DECIMAL with no parametersDefaults to DECIMAL(18,0) — every decimal place rounded away.
Too many decimals throws an errorIt rounds silently. Too many digits before the point throws (Msg 8115).

11 — Graph tables

TrapThe truth
Graph edges traverse both waysNo. $from_id/$to_id fix the direction; reversing the arrow returns nothing.
FOR PATH on the anchor nodeOnly on tables inside the repeating pattern.
Selecting a FOR PATH column directlyOnly inside WITHIN GROUP (GRAPH PATH).
MATCH goes in a JOINIt goes in the WHERE clause; tables are comma-separated.
+ and {1,5} are the same+ is unbounded; {1,5} caps the hops.

12 — Encryption

TrapThe truth
TDE protects from administratorsNo. It decrypts transparently for anyone who authenticates. At-rest only.
Always Encrypted needs no app changeNeeds Column Encryption Setting=Enabled and driver access to the CMK. "No application changes" rules it out.
Deterministic is just as secureRepeated ciphertexts leak frequency. Never on low-cardinality columns.
Randomized supports equality searchIt supports no operations at all.
Deterministic supports range queriesEquality, join, GROUP BY, index only. Ranges need secure enclaves.
Deterministic strings work with any collationThey need a BIN2 collation.
Column-level encryption hides data from the DBANo. The server decrypts; key access = plaintext access.
CEK and CMK both live in the databaseCEK inside (encrypted), CMK outside — Key Vault, cert store or HSM.
The three states are interchangeableRest = TDE · Transit = TLS · In use = Always Encrypted.

13 — RLS, masking & permissions

TrapThe truth
DDM satisfies a compliance requirementNever. Inference via WHERE / ORDER BY / CAST defeats it.
DDM changes the stored dataNo. It rewrites output only.
DDM is always the wrong answerNot quite — if the stem says "usability measure, not a compliance control", it's right.
An RLS filter predicate prevents bad writesNo. Filter = visibility. Writes need a BLOCK predicate.
Block predicate operations are interchangeableCreate → AFTER INSERT · move out → AFTER UPDATE · modify another's → BEFORE UPDATE · delete → BEFORE DELETE.
The RLS function can be any function typeInline TVF with SCHEMABINDING.
Views give the same protection as RLSBypassable via the base table. RLS is enforced in the engine.
GRANT beats DENY if more specificDENY wins — except db_owner/sysadmin.

14 — Identity, auditing & network

TrapThe truth
A managed identity needs no role assignmentIt does. Identity ≠ authorisation. This is the classic 401.
Role assignments take effect immediatelyUp to 10 minutes, and much longer if the identity gets the role through a group, because its tokens are cached for up to about 24 hours.
Assign the role to the Foundry projectAssign it to the Foundry resource (Kind: AIServices).
System-assigned identity can be sharedNo. Sharing needs user-assigned.
Contributor lets you read blobs or secretsManagement plane ≠ data plane. Look for "Data" in the role name.
Key Vault satisfies "no API keys anywhere"The key still exists. "Eliminated" means managed identity.
Database-level audit captures failed loginsAuthentication is server-level. Use a server-level audit.
You must pick one audit destinationSeveral can run simultaneously — watch for "choose two".
Audit destinationsAnalyse/alert → Log Analytics · SIEM → Event Hubs · retain years → Storage.
Auditing proves data wasn't tampered withIt records events. Proof needs ledger.
Service endpoint = private connectivityThe resource keeps a public endpoint. Use a private endpoint.

15 — Embeddings, vectors & chunking

TrapThe truth
VECTOR_SEARCH is a faster VECTOR_DISTANCEWith TOP (N) WITH APPROXIMATE and a DiskANN index it's approximate (ANN). "Exact" → VECTOR_DISTANCE (ENN).
VECTOR_SEARCH without a vector indexStill runs, as an exact full scan (kNN). The DiskANN index is what makes WITH APPROXIMATE fast. (Updated Oct 2026.)
TOP_N inside VECTOR_SEARCH()Msg 42274 with newer index versions. Use outer SELECT TOP (n) WITH APPROXIMATE + ORDER BY vs.distance.
Higher distance = more similarLower is more similar. ORDER BY distance ASC.
Index metric can differ from query metricThey must match, or the index isn’t used and the query falls back to an exact scan.
Different models' embeddings are comparableNo. Change model → alter the column and re-embed everything.
VECTOR(n) is flexibleFixed, and must equal the model's dimension count.
Vectors are float64float32 — 4 bytes per dimension. Edm.Single = single precision = 32-bit.
Embed the whole documentChunk first. One vector for a long document matches nothing well.
Overlap supplies broad contextIt only prevents boundary loss. Broad context is hierarchical chunking.
Vector fields can be filterable in AI SearchSearchable only — not filterable, sortable or facetable.
The AI Search key field can be any typeExactly one, and Edm.String.
hnsw gives exact resultsApproximate. Exact is exhaustiveKnn.
RRF averages relevance scoresIt fuses rank positions — scores aren't comparable across systems. 1/(k+rank), k≈60.
RRF uses an INNER JOINFULL OUTER JOIN + ISNULL(...,0).

16 — RAG & REST calls

TrapThe truth
GET for a chat completionPOST. GET has no request body — the prompt has nowhere to go.
FOR JSON returns a single object for one rowIt returns an array. Add WITHOUT_ARRAY_WRAPPER.
JSON_QUERY to extract the model's answerThe content is a scalar → JSON_VALUE(@response,'$.result.choices[0].message.content').
404 means a permissions problem401/403 = permissions. 404 = wrong URL, usually the deployment name. 400 = bad payload.
429 means something is brokenRate limiting. @retry_count or raise the quota.
Credential name is arbitraryMust match the URL prefix.
Fine-tuning is the way to use your own dataRAG. Fine-tuning is expensive, stale, and gives no citations.
"Answer only from context" is enoughAlso give the model permission to decline — otherwise it fills the gap.

17 — Data API Builder

TrapThe truth
DAB permissions control which rows a user seesThey control actions. Row filtering = RLS + set-session-context.
dab add to modify an entityadd creates, update modifies. Running add twice fails.
Adding a view without key-fieldsMandatory — a view has no PK to discover.
Giving a stored procedure readProcedures take execute only; GraphQL mutation if they write.
Static Web Apps serves DAB at /api/data-api/ — fixed. /api is Functions.
Cosmos DB works with RESTGraphQL only for Cosmos DB.
Being in the role is enoughSend X-MS-API-ROLE too.
A literal connection string is fine in a private repoNever. Managed identity, else @env('...').
Simulator auth provider in productionDevelopment only — it fakes roles.
Leaving host.mode: developmentExposes stack traces and schema explorers. Switch to production.
L1 cache is shared across instancesL1 is per-instance. Cross-instance needs L1L2.
Environment-specific configDAB_ENVIRONMENT.

18 — Projects, build & deploy

TrapThe truth
A SQL project only compiles files you addIt sweeps up every .sql file automatically. Exclusion must be explicit.
SQL70001A non-schema statement got compiled. <Build Remove> + <None Include>.
SQL71501 / 71502Unresolved reference — the object isn't in the project. The model is self-contained.
CREATE OR ALTER in a SQL projectNot allowed — the model can't hold "or".
:r is T-SQLIt's a SQLCMD directive, expanded at build time inside the post-deployment script.
Multiple post-deployment scriptsOne per project. Split with :r.
Export creates a dacpacExport → bacpac (schema + data). Extract → dacpac (schema only).
Publish and Import are interchangeablePublish takes a dacpac; Import takes a bacpac.
Disable BlockOnPossibleDataLoss to make it workIt's a safety net. Almost never the answer.
The GitHub workflow blocks the mergeIt only runs. Blocking needs a required status check. (Azure DevOps: the Build Validation policy does both.)
upload-artifact deploys the dacpacIt's just the courier. azure/sql-action deploys.

19 — Fabric & Copilot

TrapThe truth
Enabling Copilot usage is enoughA non-US / named region also needs cross-geo processing.
"Delegated to capacity admins" means only the capacity settingBoth levels — tenant and capacity. Four switches in total.
Copilot can see everything in the workspaceScope is OPEN or CONNECTED content — including an open CSV export. Not unopened files, raw row data, or environment-variable credentials.
Copilot questions need memorisationThey're the easiest part — answerable by reasoning. Bank the time.

★ The Final Twenty read these on the morning, nothing else

If you read one thing on the morning of the exam

  1. Which phrase, if deleted, would change my answer? Find it before choosing.
  2. N+1 partitions. First-of-period → RANGE RIGHT.
  3. Unique index must contain the partitioning column. Non-aligned index → no SWITCH.
  4. Azure SQL Database = PRIMARY filegroup only. Instance features → MI. OS access → VM.
  5. >4 TB kills GP and BC → Hyperscale. "Continuously allocated" kills serverless.
  6. JSON_VALUE on an array returns NULL silently → JSON_QUERY. Cap is 4,000 chars.
  7. WITHOUT_ARRAY_WRAPPER = single row. INCLUDE_NULL_VALUES = multi-row nulls. FOR JSON is always last.
  8. Regex default flag is 'c' (case-sensitive). REGEXP_LIKE needs compat level 170. Never SARGable.
  9. DENSE_RANK for "distinct levels". ROW_NUMBER for dedup. Say ROWS, never trust the default frame.
  10. LAST_VALUE needs UNBOUNDED FOLLOWING. PERCENTILE_DISC returns a real value.
  11. CONTAINED IN = the row's whole life fit inside. FROM excludes the upper bound, BETWEEN includes it.
  12. The _SIMILARITY functions are 0–100. Distance lower-better, similarity higher-better.
  13. TDE ≠ protection from DBAs. Deterministic = searchable, randomized = opaque, enclaves = ranges.
  14. RLS filter hides reads; BLOCK AFTER INSERT/UPDATE stops writes. DDM is only right when the stem says "not a compliance control".
  15. Managed identity still needs an RBAC role. 401 = who you are · 404 = where you're pointing · 429 = how often.
  16. Server-level audit for future DBs and logins. Log Analytics analyse · Event Hubs stream · Storage retain.
  17. VECTOR_DISTANCE exact / VECTOR_SEARCH approximate. Lower distance = closer. No TOP_N inside.
  18. Chunking: structure → structural · precise+context → hierarchical · drift → semantic.
  19. POST for generation. dotnet build makes the dacpac, SqlPackage Publish applies it. dacpac schema, bacpac data.
  20. Final three questions: independent · must meet the goal completely · no going back. If it's only the first step, the answer is No.