docs/sql-reference.md. For the shorter curated page, see SQL Reference.SQL Reference
Complete reference for the SQL dialect supported by CSharpDB.
Data Types
CSharpDB supports 25 declared logical type kinds plus the generated ROWVERSION column declaration. Canonical declarations are returned by typed schema metadata; aliases parse to the same logical descriptor.
| Canonical declaration | Accepted aliases | Semantics | ADO.NET value |
|---|---|---|---|
BOOLEAN |
BOOL, bare BIT |
Logical Boolean; stored canonically as 0 or 1 | bool |
TINYINT |
— | Unsigned 8-bit integer, 0 through 255 | byte |
SMALLINT |
— | Signed 16-bit integer | short |
INTEGER |
INT |
32-bit signed integer; checked range -2,147,483,648 through 2,147,483,647 | int |
BIGINT |
— | 64-bit signed integer | long |
REAL |
— | Finite floating point using the stable binary64 engine carrier; EF Core maps float here |
double |
DOUBLE PRECISION |
DOUBLE, FLOAT |
Finite IEEE 754 binary64 floating point | double |
DECIMAL, DECIMAL(p), DECIMAL(p,s) |
Corresponding NUMERIC forms |
Exact fixed-scale decimal; precision 1–18 and scale 0–precision | decimal |
CHAR, CHAR(n) |
CHARACTER, NCHAR |
Unicode text; the faceted form is fixed length and space-padded | string |
VARCHAR, VARCHAR(n) |
CHARACTER VARYING, NVARCHAR |
Unicode text with an optional maximum character count | string |
TEXT |
CLOB |
Unfaceted Unicode text | string |
BINARY, BINARY(n) |
— | Bytes; the faceted form is fixed length and zero-padded | byte[] |
VARBINARY, VARBINARY(n) |
— | Bytes with an optional maximum length | byte[] |
BLOB |
— | Unfaceted binary data | byte[] |
UUID |
GUID, UNIQUEIDENTIFIER |
Canonical UUID stored as exactly 16 bytes | Guid |
DATE |
— | Calendar date in canonical yyyy-MM-dd form |
DateOnly |
TIME, TIME(p) |
— | Time of day with optional fractional-seconds precision | TimeOnly |
DATETIME2, DATETIME2(p) |
DATETIME (without a facet) |
Date and time without an offset | DateTime |
DATETIMEOFFSET, DATETIMEOFFSET(p) |
TIMESTAMP WITH TIME ZONE, TIMESTAMP(p) WITH TIME ZONE |
Date, time, and offset; values are normalized to UTC | DateTimeOffset |
INTERVAL YEAR TO MONTH |
— | Calendar interval rendered canonically as signed years and months | string |
INTERVAL DAY TO SECOND, INTERVAL DAY TO SECOND(p) |
— | Duration with optional fractional-seconds precision | TimeSpan |
JSON |
— | Validated, compact canonical JSON text | string |
XML |
— | Validated, canonical XML text | string |
BIT(n) |
— | Fixed-length bit string; n is required and bare BIT means Boolean |
SqlBitString |
BIT VARYING, BIT VARYING(n) |
VARBIT, VARBIT(n) |
Variable-length bit string with an optional maximum length | SqlBitString |
ROWVERSION |
bare TIMESTAMP |
Generated, non-nullable eight-byte token from the database-wide rowversion counter | byte[] |
NULL is a value and runtime tag, not a declarable SQL column type. Ordinary types may be nullable unless constrained with NOT NULL; rowversion is always generated and non-nullable.
Facet rules
- Length
nmust be positive. Character lengths count Unicode scalar values, binary lengths count bytes, and bit-string lengths count bits. DECIMALdefaults toDECIMAL(18,2);DECIMAL(p)meansDECIMAL(p,0). Assignment is exact and does not round excess fractional digits.- Fractional-seconds precision
pranges from 0 through 7 forTIME,DATETIME2,DATETIMEOFFSET, andINTERVAL DAY TO SECOND.DATETIME(p)is rejected; useDATETIME2(p). TIMESTAMP(p) WITH TIME ZONEis temporal. BareTIMESTAMPis a rowversion;TIMESTAMP(p)withoutWITH TIME ZONEis rejected.
Logical and physical types
Declared SQL types share six compact runtime tags—Null, Integer, Real, Decimal, Text, and Blob—without sharing logical rules. Boolean and integer widths use the Integer carrier; REAL and DOUBLE PRECISION use Real; character, temporal, interval, JSON, and XML use Text; binary, UUID, bit strings, and rowversion use Blob; exact decimal uses Decimal. Typed metadata and APIs preserve the logical descriptor.
INTEGER arithmetic is checked and remains INTEGER; an operation involving BIGINT produces BIGINT, while TINYINT and SMALLINT arithmetic widens to BIGINT. Small integer literals infer as INTEGER and larger literals as BIGINT. Boolean values are not ordinary numeric operands.
ROWVERSION and bare TIMESTAMP are generated column declarations, not cast or ALTER COLUMN TYPE targets. A table may contain one rowversion. It cannot be assigned, nullable, defaulted, collated, used as an identity, or included in a key, foreign key, or index. The legacy BLOB ROWVERSION NOT NULL declaration remains accepted.
Identifiers
SQL identifiers are limited to 128 UTF-16 code units and cannot contain NUL.
| Form | Rules | Example |
|---|---|---|
| Unquoted | Starts with a letter recognized by .NET or underscore; remaining characters are letters, decimal digits, or underscores. Reserved keywords must be quoted. | customer_orders, _staging2 |
| Double-quoted | May contain reserved words, whitespace, and other characters. Escape an embedded double quote by doubling it. | "select", "display name", "say ""hello""" |
Catalog lookup is ordinal case-insensitive for quoted and unquoted identifiers. Quoting preserves the identifier text but does not create case-sensitive lookup semantics.
CREATE TABLE "order details" (
"order id" INTEGER PRIMARY KEY,
"display ""name""" TEXT
);
Statements
CREATE TABLE
CREATE TABLE [IF NOT EXISTS] table_name (
column_name type [PRIMARY KEY] [IDENTITY | AUTOINCREMENT] [NOT NULL]
[COLLATE collation_name]
[DEFAULT literal]
[[CONSTRAINT name] CHECK (row_local_expression)]
[REFERENCES other_table(column)
[MATCH SIMPLE]
[ON DELETE RESTRICT | NO ACTION | CASCADE | SET NULL | SET DEFAULT]
[ON UPDATE RESTRICT | NO ACTION | CASCADE | SET NULL | SET DEFAULT]],
...,
[[CONSTRAINT name] CHECK (row_local_expression)],
[[CONSTRAINT name] PRIMARY KEY (column1 [, column2, ...])],
[[CONSTRAINT name] UNIQUE (column1 [, column2, ...])],
[[CONSTRAINT name] FOREIGN KEY (column1 [, column2, ...])
REFERENCES other_table(column1 [, column2, ...])
[MATCH SIMPLE]
[ON DELETE RESTRICT | NO ACTION | CASCADE | SET NULL | SET DEFAULT]
[ON UPDATE RESTRICT | NO ACTION | CASCADE | SET NULL | SET DEFAULT]]
);
Constraints:
| Constraint | Scope | Description |
|---|---|---|
PRIMARY KEY |
Column or table | A single INTEGER primary key retains row-identity generation. New table-level and composite logical primary keys use INTEGER/TEXT components and enforce ordered uniqueness and NOT NULL without implicitly generating integer components. |
UNIQUE |
Table | Enforces an ordered INTEGER/TEXT logical candidate key, including composite keys. Tuples containing NULL follow the current SQL-style nullable-unique behavior. |
IDENTITY / AUTOINCREMENT |
Column | Auto-incrementing integer primary key |
NOT NULL |
Column | Rejects NULL values on insert/update |
DEFAULT |
Column | Applies a persisted literal when the column is omitted or explicitly uses DEFAULT |
CHECK |
Column or table | Rejects writes when a deterministic row-local expression is false; NULL/UNKNOWN passes |
COLLATE |
Column | Sets collation for TEXT comparisons (see Collations) |
REFERENCES / FOREIGN KEY |
Column or table | Declares a scalar or ordered composite INTEGER/TEXT foreign key. The referenced tuple must be an enforced primary or unique candidate key with matching types and collations. MATCH SIMPLE may be written explicitly and is also the default; any NULL component satisfies a nullable composite child key. |
ON DELETE CASCADE |
Foreign key | Deletes child rows when parent is deleted |
ON DELETE RESTRICT |
Foreign key | Prevents deletion of parent row while children exist |
ON DELETE NO ACTION |
Foreign key | Preserves distinct metadata and uses the same immediate restrictive behavior as RESTRICT |
ON DELETE SET NULL |
Foreign key | Sets every child-key column to NULL when the parent is deleted; all child-key columns must be nullable and outside the primary key |
ON DELETE SET DEFAULT |
Foreign key | Sets every child-key column to its persisted literal default when the parent is deleted. A column without an explicit default uses NULL, so it must be nullable and outside the primary key. The resulting tuple is revalidated immediately; a fully non-NULL tuple must match a parent, while any NULL component satisfies MATCH SIMPLE. |
ON UPDATE RESTRICT / NO ACTION |
Foreign key | Prevents a referenced parent key from changing while matching children exist; NO ACTION remains metadata-distinct |
ON UPDATE CASCADE |
Foreign key | Copies the ordered new parent-key tuple into matching child rows |
ON UPDATE SET NULL |
Foreign key | Sets every child-key column to NULL when the referenced parent key changes; all child-key columns must be nullable and outside the primary key |
ON UPDATE SET DEFAULT |
Foreign key | Sets every child-key column to its persisted literal default when the referenced parent key changes. A column without an explicit default uses NULL, so it must be nullable and outside the primary key. The resulting tuple is revalidated immediately; a fully non-NULL tuple must match a parent, while any NULL component satisfies MATCH SIMPLE. |
Temporary Tables
CREATE TEMP TABLE [IF NOT EXISTS] temp_name (
column_name type [PRIMARY KEY] [IDENTITY | AUTOINCREMENT] [NOT NULL]
[COLLATE collation_name],
...
);
CREATE TEMPORARY TABLE [IF NOT EXISTS] temp_name (...);
DROP TEMP TABLE [IF EXISTS] temp_name;
PERSIST TEMP TABLE temp_name AS durable_name;
Temporary tables are session-scoped and backed by in-memory storage. Unqualified table names resolve to temporary tables first, then durable tables/views/external tables, so a temporary table can shadow a durable table for the current session. DROP TABLE name drops the temporary table first when such a shadow exists.
SELECT, INSERT, UPDATE, DELETE, and joins work against temporary tables through the normal SQL execution path. V1 supports columns, nullability, collation, integer primary key/identity behavior, and rowid fallback. V1 rejects temporary foreign keys, triggers, secondary indexes, external tables, validation rules, full-text indexes, ALTER TABLE, ANALYZE, and data hygiene operations.
Temporary tables do not appear in sys.tables, sys.objects, backups, checkpoints, or SaveToFileAsync. Current-session metadata is exposed through sys.temp_tables / sys_temp_tables and sys.temp_columns / sys_temp_columns.
PERSIST TEMP TABLE temp_name AS durable_name explicitly creates a new durable table using the temporary table schema and copies current rows through the normal durable mutation path. The durable target must not already exist. The command returns temp_table, target_table, and rows_persisted.
For embedded and ADO.NET connections, temporary tables live for the connection or database handle lifetime and are cleared when the session is disposed. Stateless HTTP/gRPC ExecuteSqlAsync rejects temporary table commands; use BeginTransaction plus ExecuteInTransaction for remote temporary workflows.
ALTER TABLE
ALTER TABLE table_name ADD COLUMN column_name type [constraints];
ALTER TABLE table_name ADD CONSTRAINT constraint_name CHECK (expression);
ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (column_name [, ...]);
ALTER TABLE table_name ADD CONSTRAINT constraint_name PRIMARY KEY (column_name [, ...]);
ALTER TABLE table_name ADD CONSTRAINT constraint_name FOREIGN KEY (column_name [, ...]) REFERENCES parent_table (column_name [, ...]) [MATCH SIMPLE] [ON DELETE RESTRICT | NO ACTION | CASCADE | SET NULL | SET DEFAULT] [ON UPDATE RESTRICT | NO ACTION | CASCADE | SET NULL | SET DEFAULT];
ALTER TABLE table_name DROP COLUMN column_name;
ALTER TABLE table_name DROP CONSTRAINT constraint_name;
ALTER TABLE table_name DROP PRIMARY KEY;
ALTER TABLE table_name ALTER COLUMN column_name SET DEFAULT literal;
ALTER TABLE table_name ALTER COLUMN column_name DROP DEFAULT;
ALTER TABLE table_name ALTER COLUMN column_name SET NOT NULL;
ALTER TABLE table_name ALTER COLUMN column_name DROP NOT NULL;
ALTER TABLE table_name ALTER COLUMN column_name TYPE INTEGER;
ALTER TABLE table_name ALTER COLUMN column_name TYPE REAL;
ALTER TABLE table_name ALTER COLUMN column_name TYPE TEXT;
ALTER TABLE table_name ALTER COLUMN column_name TYPE BLOB;
ALTER TABLE table_name ALTER COLUMN column_name SET COLLATION collation_name;
ALTER TABLE table_name ALTER COLUMN column_name DROP COLLATION;
ALTER TABLE table_name RENAME TO new_name;
ALTER TABLE table_name RENAME COLUMN old_name TO new_name;
ALTER TABLE table_name RENAME INDEX old_name TO new_name;
Default changes affect future writes; SET NOT NULL and named CHECK, UNIQUE, PRIMARY KEY, and FOREIGN KEY additions validate existing rows before changing metadata. A logical single-column or composite primary key is added with an engine-owned unique backing index and makes every participating column NOT NULL. A single INTEGER primary key uses the physical row key. On populated tables, validated non-NULL unique column values become the new row IDs while every ready ordinary/unique SQL, constraint-internal, and foreign-key-internal index is rebuilt and swapped atomically with the table. Complete ready full-text families are supported too: all five owned metadata, term, posting, posting-chunk, and document-statistics stores are rebuilt from the new row IDs in the same atomic swap, while the logical full-text owner and its options remain unchanged. Collection indexes, incomplete or non-ready full-text families, other index kinds, and non-ready indexes reject the physical rekey before mutation.
DROP CONSTRAINT constraint_name requires the stored name and is the form generated by EF Core. DROP PRIMARY KEY targets the current key without requiring its name, including a legacy unnamed key. Either form preserves NOT NULL on former key columns. A primary or unique candidate key cannot be removed while a foreign key depends on it unless an equivalent ordered UNIQUE candidate remains.
Index rename preserves the physical index and its uniqueness behavior while updating durable catalog metadata. Unconstrained DROP COLUMN uses a transactional shadow B+tree rewrite and preserves row ids and unaffected indexes. Adding a column with a typed literal default backfills populated tables through the same rewrite path. DROP COLUMN rejects indexed/key/FK/check-dependent columns and stored-view, trigger, or validation-rule dependencies rather than weakening them.
Numeric type rewrites are bounded and exact. BIGINT-to-REAL accepts only values in the inclusive range -253 through 253; every INTEGER value is exactly representable. REAL-to-INTEGER accepts only finite, integral signed 32-bit values, while REAL-to-BIGINT uses the signed 64-bit range. NULL remains NULL. Affected ready ordinary and unique SQL indexes, including composite indexes, are rebuilt and swapped atomically with the table; unrelated index roots remain unchanged. An incompatible stored value, default, or uniqueness result rejects the operation and restores every original root.
TYPE BLOB on a TEXT column encodes each non-NULL value as UTF-8 and clears its TEXT collation metadata. TYPE TEXT on a BLOB column decodes strict UTF-8; any invalid byte sequence rejects the rewrite without changing the table. The resulting TEXT column starts with the default BINARY collation unless a later SET COLLATION selects another supported collation. These TEXT/BLOB changes currently require a dependency-free column, so even a ready SQL index on the column blocks the rewrite.
Defaults are validated against the target type rather than converted automatically. Use DROP DEFAULT before TYPE and SET DEFAULT afterward when the old literal is not valid for the target. EF Core emits that order for both Up and Down migrations, applies a requested BLOB-to-TEXT collation after the type change, and lets TYPE BLOB clear an old TEXT collation. EF migrations run the compound default/type/collation/nullability sequence in their surrounding migration transaction; manual replay must do the same so a later failure restores every facet.
SET COLLATION and DROP COLLATION are limited to TEXT columns. Type and collation changes stream rows through transactional shadow roots, preserve physical row ids, revalidate CHECK constraints, and persist across reopen. For collation changes, ready ordinary and unique SQL indexes that inherit the column collation are rebuilt and swapped atomically with the table; explicit-collation and unrelated index roots remain unchanged. A newly colliding unique value rejects the rewrite and restores every original root. Primary/unique key constraints, incoming/outgoing foreign keys, full-text or collection dependencies, and non-ready indexes remain unsupported. The conservative preflight also rejects any view that references the table, any trigger owned by the table, a cross-table trigger that references the column, and an applicable validation rule. Indexed TEXT/BLOB changes and broader dependency rewriting remain unsupported.
DROP TABLE
DROP TABLE [IF EXISTS] table_name;
CREATE INDEX
CREATE [UNIQUE] INDEX [IF NOT EXISTS] index_name
ON table_name (column1 [, column2, ...]);
SQL indexes accept INTEGER, TEXT, and REAL columns. REAL components use a canonical hashed equality path: equality predicates can use the index, including exact INTEGER/REAL numeric matches, but ordered scans, range pushdown, and using a REAL index to satisfy ORDER BY are not supported. An INTEGER-tag value stored in a REAL-indexed column must be within the exactly representable ±253 range; index creation, rewrite backfill, or a later write rejects an out-of-range value instead of rounding it. COLLATE remains valid only for TEXT index columns.
DROP INDEX
DROP INDEX [IF EXISTS] index_name;
CREATE VIEW
CREATE VIEW [IF NOT EXISTS] view_name AS select_statement;
DROP VIEW
DROP VIEW [IF EXISTS] view_name;
CREATE TRIGGER
CREATE TRIGGER [IF NOT EXISTS] trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON table_name
[FOR EACH ROW]
BEGIN
statement1;
[statement2;]
...
END;
Triggers can reference NEW and OLD row aliases in their body:
INSERTtriggers:NEWis availableDELETEtriggers:OLDis availableUPDATEtriggers: bothNEWandOLDare available
Unsupported: Trigger
WHENconditions are rejected with a stableSyntaxErrorbefore trigger metadata is persisted. Put supported predicate logic in the trigger body until conditional triggers are implemented.
DROP TRIGGER
DROP TRIGGER [IF EXISTS] trigger_name;
ANALYZE
ANALYZE table_name;
Collects per-column statistics (distinct count, min/max, frequency histograms, quantile buckets) and index prefix statistics used by the query planner for cardinality estimation and operator selection. See Query Execution Pipeline for details on how statistics influence planning.
EXPLAIN and EXPLAIN ANALYZE
EXPLAIN [FOR] SELECT ...;
EXPLAIN [FOR] INSERT ...;
EXPLAIN [FOR] UPDATE ...;
EXPLAIN [FOR] DELETE ...;
EXPLAIN ANALYZE [FOR] SELECT ...;
EXPLAIN ANALYZE [FOR] INSERT ...;
EXPLAIN ANALYZE [FOR] UPDATE ...;
EXPLAIN ANALYZE [FOR] DELETE ...;
EXPLAIN ESTIMATE FOR SELECT ...; -- legacy cardinality diagnostic
EXPLAIN binds the statement and returns the finalized physical operator structure without opening it. EXPLAIN ANALYZE executes the statement exactly once and adds per-operator actual rows, open loops, and inclusive elapsed time. Profiled DML performs the insert, update, or delete and uses the same auto-commit or explicit-transaction semantics as normal execution; rolling back the surrounding transaction rolls back the profiled mutation.
EXPLAIN when execution or mutation is not intended. Cancellation and execution errors remain failures and are not converted into successful plan rowsets. Where it is safe to do so, EXPLAIN ANALYZE attaches a bounded, redacted partial-profile summary to the failure diagnostics without changing the failure outcome.The physical plan is a stable structural rowset rather than formatted prose. It is available through direct execution, ADO.NET, HTTP, and gRPC using the normal SQL result path.
| Column | Meaning |
|---|---|
node_id, parent_node_id | Deterministic parent-first tree identity. |
operator_type | Stable physical operator name such as table_scan, primary_key_lookup, hash_join, or sort. |
estimated_rows, estimated_cost | estimated_rows is nullable when the planner has no cardinality estimate. Structural operator nodes receive a stable relative row-work estimated_cost for comparing plan choices; it is not a duration or a substitute for elapsed_microseconds. Diagnostic rows may leave both fields NULL. Estimated values are never substituted with runtime values. |
actual_rows, actual_loops, elapsed_microseconds | Runtime values for EXPLAIN ANALYZE; NULL for ordinary EXPLAIN. |
access_path, object_name, index_name, join_type, predicate | Chosen access, object, index, join, and redacted predicate-shape metadata. |
status, diagnostic_code | Per-node execution state and stable diagnostic code when applicable. |
Plan output is bounded to 500 rows and a 256 KiB inline content budget, with text fields limited to 512 characters. The content budget limits the plan data before transport framing and serialization; it is not an exact HTTP, gRPC, or other serialized message-size guarantee. If a bound is reached, the final row is a diagnostic node with plan_truncated. Literal and prepared-parameter values are represented by placeholders in predicate metadata, including bounded partial-profile summaries attached to failures where safe.
To guarantee that ordinary EXPLAIN never triggers eager work, statements whose current planning path materializes data are rejected with a stable diagnostic. This currently includes WITH, subqueries, views, and duplicate-eliminating compound queries. Use EXPLAIN ANALYZE when those statements should be executed and profiled. Duplicate-preserving UNION ALL can be explained without execution.
Data Manipulation
INSERT
INSERT INTO table_name [(column1, column2, ...)]
VALUES (value_or_DEFAULT1, value_or_DEFAULT2, ...);
INSERT INTO table_name DEFAULT VALUES;
Column list is optional when providing values for all columns in declaration order. Omitted columns and explicit DEFAULT markers use their literal column defaults; explicit NULL remains NULL.
UPDATE
UPDATE table_name
SET column1 = expression1 [, column2 = expression2, ...]
[WHERE condition];
DELETE
DELETE FROM table_name
[WHERE condition];
SELECT
SELECT [DISTINCT] column_list
FROM table_reference
[JOIN ...]
[WHERE condition]
[GROUP BY column1 [, column2, ...]]
[HAVING condition]
[ORDER BY column1 [ASC | DESC] [, ...]]
[LIMIT count]
[OFFSET skip];
Column List
SELECT * -- all columns
SELECT column_name -- single column
SELECT column_name AS alias -- aliased column
SELECT table.column_name -- qualified column
SELECT expression -- computed value
SELECT aggregate_function(...) -- aggregate
FROM and JOIN
FROM table_name [AS alias]
-- Join types
INNER JOIN table_name ON condition
LEFT JOIN table_name ON condition
RIGHT JOIN table_name ON condition
CROSS JOIN table_name
All join types except CROSS JOIN require an ON condition.
Subqueries
-- Scalar subquery (must return a single value)
SELECT (SELECT MAX(age) FROM users) AS max_age;
-- IN subquery
SELECT id FROM users WHERE id IN (SELECT id FROM other_table);
SELECT id FROM users WHERE id NOT IN (SELECT id FROM other_table);
-- EXISTS subquery
SELECT id FROM users
WHERE EXISTS (SELECT 1 FROM other_table WHERE other_table.id = users.id);
Data Hygiene
CSharpDB includes SQL-first data hygiene commands for duplicate cleanup,
audit-only validation rules, and relationship auditing. These commands return
normal query-shaped results through ExecuteSqlAsync, ADO.NET, Admin query tabs,
HTTP, gRPC, and the CLI.
FIND DUPLICATES
FIND DUPLICATES IN table_name ON expression [, expression ...];
Scans the target table, evaluates the ON expressions for each row, and returns
one row per duplicate group. Text keys use existing column or expression
collation behavior, so COLLATE NOCASE can be used directly in the key list.
Result columns:
| Column | Description |
|---|---|
key_values | Display text for the evaluated duplicate key values |
group_size | Number of rows in the duplicate group |
winner_rowid | Deterministic survivor rowid using KEEP FIRST semantics |
winner_primary_key | Survivor primary-key value, or NULL when no primary key exists |
duplicate_rowids | Comma-separated rowids that are not the survivor |
duplicate_primary_keys | Comma-separated duplicate primary-key values, or NULL when no primary key exists |
FIND DUPLICATES IN Customers ON Email COLLATE NOCASE;
FIND DUPLICATES IN Contacts ON FirstName, LastName, Phone;
DEDUP
DEDUP table_name ON expression [, expression ...] KEEP FIRST | LAST;
Deletes non-winner rows for each duplicate group. KEEP FIRST keeps the lowest
primary-key value when the table has a primary key, otherwise the lowest rowid.
KEEP LAST keeps the highest primary-key value, otherwise the highest rowid.
Deletes run through the normal table mutation path, including indexes, foreign
keys, triggers, WAL, and transaction rollback.
Result columns: table_name, duplicate_group_count, rows_deleted,
rows_kept.
DEDUP Customers ON Email COLLATE NOCASE KEEP FIRST;
MERGE DUPLICATES
MERGE DUPLICATES table_name ON expression [, expression ...];
Selects the same deterministic winner as KEEP FIRST, fills only NULL winner
columns when exactly one non-null duplicate value is available, reports merge
conflicts when multiple different values are found, and then deletes the
duplicate rows through the normal mutation path.
Result columns: table_name, duplicate_group_count, rows_updated,
rows_deleted, merge_conflict_count, merge_conflicts.
MERGE DUPLICATES Customers ON Email COLLATE NOCASE;
CREATE VALIDATION RULE
CREATE VALIDATION RULE rule_name
ON table_name[.column_name]
AS expression
MESSAGE 'message text';
Validation rules are stored as database metadata and evaluated only when
VALIDATE TABLE is executed. V1 rules are audit-only; they do not block
INSERT or UPDATE.
Rules are stored in the hidden internal table __validation_rules. That table
is hidden from normal table/object listings and exposed through
sys.validation_rules and sys_validation_rules.
CREATE VALIDATION RULE ValidEmail
ON Customers.Email
AS Email LIKE '%@%'
MESSAGE 'Email must contain @';
SELECT rule_name, table_name, column_name, expression_sql, message
FROM sys.validation_rules;
VALIDATE TABLE
VALIDATE TABLE table_name;
Evaluates enabled validation rules for the table and returns one row per
violation. A rule fails when the expression returns false or NULL.
Result columns: rule_name, table_name, column_name, rowid,
primary_key, message.
FIND ORPHANS
FIND ORPHANS IN child_table;
FIND ORPHANS IN child_table.child_column REFERENCES parent_table.parent_column;
Without an explicit REFERENCES clause, CSharpDB uses declared foreign-key
metadata for the child table. With explicit references, both tables and columns
are validated before running the check. NULL child values are ignored.
Result columns: constraint_name, child_table, child_column, child_rowid,
child_value, parent_table, parent_column.
FIND ORPHANS IN Bookings;
FIND ORPHANS IN Bookings.BookId REFERENCES Books.Id;
Performance is proportional to the requested hygiene work: duplicate detection scans the target table and groups keys in memory; validation is table rows times enabled rules; orphan detection uses parent index lookups when available or a parent value set built from one scan.
Common Table Expressions (CTEs)
WITH cte_name [(column1, column2, ...)] AS (
select_statement
)
[, another_cte AS (...)]
SELECT ... FROM cte_name ...;
Multiple CTEs can be chained with commas. Optional column name lists rename the CTE's output columns.
Note:
WITH RECURSIVEis rejected because recursive CTE execution is not yet implemented.
Set Operations
select_statement UNION select_statement
select_statement UNION ALL select_statement
select_statement INTERSECT select_statement
select_statement EXCEPT select_statement
UNION removes duplicates while UNION ALL preserves duplicates and NULL rows. Compound queries support trailing ORDER BY, LIMIT, and OFFSET applied to the combined result.
Expressions and Operators
Arithmetic
| Operator | Description |
|---|---|
+ |
Addition |
- |
Subtraction (also unary negation) |
* |
Multiplication |
/ |
Division (error on division by zero) |
Comparison
| Operator | Description |
|---|---|
= |
Equal |
<> or != |
Not equal |
< |
Less than |
> |
Greater than |
<= |
Less than or equal |
>= |
Greater than or equal |
Logical
Comparisons with NULL produce UNKNOWN (represented as NULL). AND, OR, and NOT apply SQL three-valued truth tables.
| Operator | Description |
|---|---|
AND |
Logical conjunction |
OR |
Logical disjunction |
NOT |
Logical negation |
Special Expressions
| Expression | Example |
|---|---|
BETWEEN ... AND ... |
WHERE age BETWEEN 18 AND 65 |
IN (...) |
WHERE status IN ('active', 'pending') |
NOT IN (...) |
WHERE id NOT IN (1, 2, 3) |
LIKE |
WHERE name LIKE 'J%' |
LIKE ... ESCAPE |
WHERE code LIKE '100\%%' ESCAPE '\' |
IS NULL |
WHERE email IS NULL |
IS NOT NULL |
WHERE email IS NOT NULL |
LIKE wildcards:
| Wildcard | Matches |
|---|---|
% |
Zero or more characters |
_ |
Exactly one character |
Functions
Aggregate Functions
Used with or without GROUP BY. All except COUNT(*) ignore NULL values.
| Function | Description | Supports DISTINCT |
|---|---|---|
COUNT(*) |
Number of rows | — |
COUNT(expr) |
Number of non-NULL values | Yes |
SUM(expr) |
Sum of numeric values | Yes |
AVG(expr) |
Average of numeric values | Yes |
MIN(expr) |
Minimum value | — |
MAX(expr) |
Maximum value | — |
SELECT COUNT(DISTINCT status), AVG(age) FROM users;
Scalar Functions
| Function | Arguments | Returns | Description |
|---|---|---|---|
TEXT(expr) |
1 | TEXT | Converts any value to its text representation |
ORDINAL_STARTS_WITH(text, prefix) |
2 | INTEGER or NULL | Case-sensitive ordinal prefix test; pattern characters are literal |
ORDINAL_ENDS_WITH(text, suffix) |
2 | INTEGER or NULL | Case-sensitive ordinal suffix test; pattern characters are literal |
ORDINAL_CONTAINS(text, search) |
2 | INTEGER or NULL | Case-sensitive ordinal substring test; pattern characters are literal |
XML_EXISTS(xml, xpath [, namespace_json])XMLEXISTS(...) |
2 or 3 | BOOLEAN (1 or 0) or NULL | Returns the XPath 1.0 effective boolean value; XMLEXISTS is an alias |
XML_VALUE(xml, xpath [, namespace_json]) |
2 or 3 | TEXT or NULL | Returns one XPath value as text; an empty node-set returns NULL |
The three ordinal search functions return 1 for true, 0 for false, and NULL when either argument is NULL. They use .NET ordinal UTF-16 code-unit semantics and do not apply collation, wildcard, or escape rules.
XML_EXISTS and XML_VALUE accept an XML or text document and an XPath 1.0 expression. Their optional third argument is a JSON object mapping XPath prefixes to namespace URIs, for example XML_VALUE(payload, '/o:order/@id', '{"o":"urn:orders"}'). Every supplied argument is NULL-propagating.
When XML_VALUE evaluates to a node-set, no matching nodes returns NULL, exactly one node returns its XPath string value, and more than one matching node reports a type mismatch. XPath scalar results use XPath string conversion. Use an explicitly singular expression such as (/root/item)[1] when appropriate.
XML documents are parsed with bounded, secure settings; DTD declarations and external entities are prohibited. The supported syntax is the function-style API above. Standard SQL XMLEXISTS(... PASSING ...), XML_TABLE, and XML path indexes are not currently supported.
Window Functions
Window functions are a completed, bounded in-memory SQL slice. Supported ranking functions are ROW_NUMBER(), RANK(), and DENSE_RANK(). Supported aggregate windows are non-distinct COUNT, SUM, AVG, MIN, and MAX. Navigation and value functions include LAG(value[, offset[, default]]), LEAD(value[, offset[, default]]), FIRST_VALUE(value), and LAST_VALUE(value).
An explicit ROWS frame may use either ROWS frame_bound (ending at CURRENT ROW) or ROWS BETWEEN frame_bound AND frame_bound. A bound may be UNBOUNDED PRECEDING, n PRECEDING, CURRENT ROW, n FOLLOWING, or UNBOUNDED FOLLOWING; offset n must be a nonnegative integer literal, and invalid start/end combinations are rejected. Ranking functions and LAG/LEAD ignore the frame; aggregate windows and FIRST_VALUE/LAST_VALUE evaluate against it. Without an explicit frame, an ordered window uses the peer-aware default through the current peer group, while a window without ORDER BY uses the whole partition.
SELECT
department,
salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS position,
AVG(salary) OVER (
PARTITION BY department
ORDER BY salary DESC
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_average,
LAG(salary, 1, 0) OVER pay_order AS previous_salary
FROM employees
WINDOW pay_order AS (
PARTITION BY department
ORDER BY salary DESC
);
Named definitions use WINDOW name AS (...) and are referenced with OVER name; names are matched case-insensitively. Window inheritance and extensions such as OVER (name ...) are not supported. Expressions whose resolved windows have exactly the same PARTITION BY and ORDER BY specifications can share one execution stage even when their frames differ. Incompatible partitioning or ordering specifications are rejected explicitly instead of sharing an incorrect sort order.
Ascending window order places NULL values first and descending order places them last. Explicit NULLS FIRST/NULLS LAST syntax is not supported. Window queries support prepared execution and poll cancellation during buffering, sorting, and evaluation. See WindowExecution for the finite partition and stage-buffer limits; exceeding either limit returns ResourceLimitExceeded.
Parameters
Named parameters are supported in value positions using the @ prefix:
SELECT * FROM users WHERE name = @name AND age > @minAge;
INSERT INTO users (name, age) VALUES (@name, @age);
UPDATE users SET name = @name WHERE id = @id;
DELETE FROM users WHERE id = @id;
Parameters cannot be used in identifier positions (table names, column names).
Collations
Collations control how TEXT values are compared and sorted. They can be specified at the
column level in CREATE TABLE or at the expression level using the COLLATE operator.
| Collation | Description |
|---|---|
BINARY |
Byte-for-byte comparison (default) |
NOCASE |
Case-insensitive comparison |
NOCASE_AI |
Case-insensitive and accent-insensitive comparison |
ICU:<locale> |
Unicode ICU-based comparison with locale support |
-- Column-level collation
CREATE TABLE products (
name TEXT COLLATE NOCASE
);
-- Expression-level collation
SELECT * FROM products ORDER BY name COLLATE NOCASE_AI;
Limitations
Window execution is deliberately in-memory and bounded. Disk spilling is deferred and does not block completion of this SQL slice because configured limits fail predictably with ResourceLimitExceeded. RANGE, GROUPS, EXCLUDE, DISTINCT window aggregates, IGNORE NULLS/RESPECT NULLS, window inheritance, incompatible window specifications, and window queries mixed with ordinary aggregates, GROUP BY, HAVING, or subqueries are not supported.
Built-in scalar and aggregate metadata is queryable through sys.functions (also sys_functions). Logical primary/unique key columns are exposed through sys.key_constraints. The SQL overview lists the currently registered built-in families.
The following SQL features are not currently supported:
CASE/WHENexpressionsCASTexpressions (implicit coercion only)RETURNINGclause on INSERT/UPDATE/DELETEUPSERT/ON CONFLICT/INSERT OR REPLACEINTERSECT ALLandEXCEPT ALL(onlyUNION ALLis supported)- Recursive CTE execution (
WITH RECURSIVEis rejected) - Unregistered vendor-specific functions such as
STRFTIME,CEIL, andPOWER - Advanced window forms beyond the bounded slice described above
- SQL
CREATE PROCEDUREandCALLstatements; use the supported client procedure catalog/API instead - Deferred foreign-key constraints and
MATCH FULL/MATCH PARTIAL - Trigger
WHENconditions
Current DEFAULT support is limited to typed literals and NULL. Immediate referential mutations use the normal UPDATE pipeline, including rowversion, triggers, checks, other foreign keys, unique indexes, and transactional rollback. Under MATCH SIMPLE, a SET DEFAULT result with any NULL component satisfies the foreign key; a fully non-NULL result without a matching parent fails without partial changes. CHECK expressions must be deterministic and row-local; parameters, functions, subqueries, qualified references, and cross-column references from a column-scoped CHECK are rejected.