SQL Reference

CSharpDB implements its own documented SQL dialect with a tokenizer, parser, query planner, and expression evaluator.

SQL documentation. This page summarizes supported SQL syntax. For the complete datatype matrix, full grammar, current limitations, and examples, see the SQL Source Reference.

Data Definition (DDL)

CREATE TABLE

CREATE TABLE Employees (
    Id        INTEGER PRIMARY KEY,
    Name      TEXT NOT NULL,
    DeptId    INTEGER,
    Salary    REAL,
    HireDate  TEXT,
    Photo     BLOB
);

Temporary Tables

CREATE TEMP TABLE StageEmployees (
    Id   INTEGER PRIMARY KEY IDENTITY,
    Name TEXT
);

INSERT INTO StageEmployees (Name) VALUES ('Ada');
PERSIST TEMP TABLE StageEmployees AS ImportedEmployees;
DROP TEMP TABLE IF EXISTS StageEmployees;

Temporary tables are in-memory and session-scoped. They resolve before durable tables, are hidden from sys.tables and backups, and appear only in sys.temp_tables/sys_temp_tables and sys.temp_columns/sys_temp_columns. Stateless HTTP/gRPC SQL execution rejects temp commands; use transaction sessions for remote temp workflows.

CREATE INDEX

CREATE INDEX idx_emp_dept ON Employees (DeptId);
CREATE UNIQUE INDEX idx_emp_name ON Employees (Name);

CREATE VIEW

CREATE VIEW HighEarners AS
    SELECT Name, Salary FROM Employees WHERE Salary > 100000;

CREATE TRIGGER

CREATE TRIGGER log_insert
    AFTER INSERT ON Employees
    BEGIN
        INSERT INTO AuditLog VALUES ('INSERT', NEW.Name);
    END;

ALTER TABLE & DROP

ALTER TABLE Employees ADD COLUMN Title TEXT;
ALTER TABLE Employees DROP CONSTRAINT fk_employees_deptid_a1b2c3d4;
ALTER TABLE Employees RENAME INDEX idx_emp_dept TO idx_employees_department;
DROP VIEW HighEarners;
DROP INDEX idx_employees_department;
DROP TABLE Employees;

Data Manipulation (DML)

EXPLAIN and profile output

EXPLAIN SELECT Name FROM Employees WHERE Id = 1;
EXPLAIN ANALYZE SELECT Name FROM Employees WHERE Id = 1;
EXPLAIN ANALYZE UPDATE Employees SET Salary = Salary + 1000 WHERE Id = 1;

EXPLAIN returns the selected physical operator tree without opening it. EXPLAIN ANALYZE executes exactly once and reports actual rows, loops, and elapsed time; DML profile statements therefore mutate under normal transaction semantics. Estimated costs use stable relative row-work units for comparing choices, not elapsed time; row estimates remain nullable when cardinality is unavailable. Output is a bounded structural rowset available through direct, ADO.NET, HTTP, and gRPC clients; its 256 KiB limit is an inline content budget rather than an exact serialized transport size. Predicate values are redacted. Cancellation and execution errors remain failures, with a bounded, redacted partial-profile summary attached to failure diagnostics where safe. See the full contract and current non-executing planning restrictions.

INSERT

INSERT INTO Employees (Id, Name, DeptId, Salary)
    VALUES (1, 'Alice', 10, 95000.0);

INSERT INTO Employees VALUES
    (2, 'Bob', 20, 88000.0, '2024-01-15', NULL);

SELECT

-- Basic query with filtering and ordering
SELECT Name, Salary FROM Employees
    WHERE Salary > 80000 ORDER BY Salary DESC;

-- DISTINCT, LIMIT, OFFSET
SELECT DISTINCT DeptId FROM Employees LIMIT 10 OFFSET 5;

-- JOINs
SELECT e.Name, d.DeptName
    FROM Employees e
    INNER JOIN Departments d ON e.DeptId = d.Id;

SELECT e.Name, d.DeptName
    FROM Employees e
    LEFT JOIN Departments d ON e.DeptId = d.Id;

-- Aggregates and GROUP BY
SELECT DeptId, COUNT(*), AVG(Salary), MAX(Salary)
    FROM Employees
    GROUP BY DeptId
    HAVING COUNT(*) > 3;

-- CTEs (Common Table Expressions)
WITH TopEarners AS (
    SELECT Name, Salary FROM Employees
    WHERE Salary > 100000
)
SELECT * FROM TopEarners ORDER BY Salary DESC;

-- Subqueries
SELECT Name FROM Employees
    WHERE DeptId IN (SELECT Id FROM Departments WHERE Region = 'West');

SELECT Name FROM Employees e
    WHERE EXISTS (SELECT 1 FROM Reviews r WHERE r.EmpId = e.Id);

-- Set operations
SELECT Name FROM Employees WHERE DeptId = 10
UNION
SELECT Name FROM Contractors WHERE Active = 1;

UPDATE & DELETE

UPDATE Employees SET Salary = Salary * 1.1 WHERE DeptId = 10;
DELETE FROM Employees WHERE Id = 99;

Data Hygiene

Use SQL-first hygiene commands to preview duplicates, deduplicate or merge rows transactionally, store audit-only validation rules, and find orphaned child values.

-- Preview duplicate groups without changing data
FIND DUPLICATES IN Customers ON Email COLLATE NOCASE;

-- Fill NULL winner fields when exactly one duplicate value is available
MERGE DUPLICATES Customers ON Email;

-- Delete non-winners through normal index/FK/trigger mutation paths
DEDUP Customers ON Email KEEP FIRST;

-- Store and evaluate audit-only SQL validation rules
CREATE VALIDATION RULE ValidEmail
    ON Customers.Email
    AS Email LIKE '%@%'
    MESSAGE 'Email must contain @';

VALIDATE TABLE Customers;

-- Check declared FKs or an explicit child/parent relationship
FIND ORPHANS IN Bookings;
FIND ORPHANS IN Bookings.BookId REFERENCES Books.Id;

Hygiene commands return normal query rows, so they work through embedded SQL, ADO.NET, Admin query tabs, the CLI, HTTP, and gRPC. Validation rules are stored in a hidden internal table and exposed through sys.validation_rules and sys_validation_rules.

Built-in Functions

String Functions

FunctionDescription
UPPER(text)Convert to uppercase
LOWER(text)Convert to lowercase
LENGTH(text)String length
SUBSTR(text, start, len)Substring extraction
TRIM(text)Remove leading/trailing whitespace
REPLACE(text, from, to)String replacement
INSTR(text, search)Find position of substring
ORDINAL_STARTS_WITH(text, prefix)Case-sensitive ordinal prefix test; returns 1, 0, or NULL
ORDINAL_ENDS_WITH(text, suffix)Case-sensitive ordinal suffix test; returns 1, 0, or NULL
ORDINAL_CONTAINS(text, search)Case-sensitive ordinal substring test; returns 1, 0, or NULL

The ordinal search functions use .NET ordinal UTF-16 code-unit semantics and do not apply SQL collation, wildcard, or escape rules. In particular, %, _, and backslash are ordinary characters.

XML Functions (XPath 1.0)

FunctionDescription
XML_EXISTS(xml, xpath [, namespace_json])Evaluate the XPath effective boolean value; returns 1, 0, or NULL. XMLEXISTS is an alias.
XML_VALUE(xml, xpath [, namespace_json])Return one XPath value as text, or NULL when a node-set is empty.

Both functions accept an XML or text document and an XPath 1.0 expression. The optional third argument is a JSON object that maps XPath prefixes to namespace URIs, for example XML_VALUE(payload, '/o:order/@id', '{"o":"urn:orders"}'). If any supplied argument is NULL, the result is NULL.

For a node-set, XML_VALUE returns the XPath string value of its single node, returns NULL for no nodes, and reports a type mismatch when more than one node matches. XPath scalar results are converted using XPath string semantics; select one node explicitly, such as (/root/item)[1], when multiple matches are possible.

XML parsing is bounded and secure: DTD declarations and external entities are prohibited. This release supports the function-style APIs shown above; standard SQL XMLEXISTS(... PASSING ...) syntax, XML_TABLE, and XML path indexes are not yet supported.

Math Functions

FunctionDescription
ABS(value)Absolute value
ROUND(value, digits)Round to N decimal places
SGN(value)Sign of a number (-1, 0, 1)
INT(value) / FLOOR(value)Floor a numeric value
FIX(value)Truncate a numeric value toward zero

Conditional & Type Functions

FunctionDescription
TEXT(value)Convert a value to CSharpDB display text
NZ(value [, fallback])Replace NULL/empty input using Access-style semantics
COALESCE(a, b, ...)First non-null value
NULLIF(a, b)NULL if a equals b
IFNULL(a, b)b if a is null
IIF(cond, then, else)Conditional expression

Aggregate Functions

FunctionDescription
COUNT(*) / COUNT(col)Count rows or non-null values
SUM(col)Sum of values
AVG(col)Average of values
MIN(col)Minimum value
MAX(col)Maximum value

System Catalog

Query database metadata through built-in system views:

ViewDescription
sys.tablesAll tables and their root page numbers
sys.columnsColumn definitions for all tables
sys.indexesIndex definitions and backing B+tree info
sys.check_constraintsNamed and unnamed CHECK expressions and original column/table scope
sys.key_constraintsOrdered logical primary/unique key columns and backing-index ownership
sys.foreign_keysOrdered child/parent column pairs, delete actions, and support-index ownership for scalar and composite foreign keys
sys.functionsBuilt-in aliases/signatures/semantics plus registered scalar callback metadata
sys.viewsView definitions and SQL
sys.triggersTrigger metadata
sys.validation_rulesDatabase-owned audit validation rules and their SQL expressions
sys.temp_tablesCurrent-session temporary table metadata
sys.temp_columnsCurrent-session temporary column metadata
sys.table_statsRow count and page usage per table
sys.column_statsCardinality, NULL count, min/max per column

Stored Procedures

CSharpDB supports stored procedures with typed parameters, persisted in the database catalog.

// Create a procedure
await client.CreateProcedureAsync(new ProcedureDefinition
{
    Name = "GetEmployeesByDept",
    BodySql = "SELECT * FROM Employees WHERE DeptId = @deptId",
    Parameters = [
        new ProcedureParameterDefinition { Name = "deptId", Type = DbType.Integer }
    ]
});

// Execute
var result = await client.ExecuteProcedureAsync(
    "GetEmployeesByDept",
    new Dictionary<string, object?> { ["deptId"] = 10 });