CSharpDB ships a full SQL engine — not a SQL-like query language, but proper SQL with JOINs, aggregates, CTEs, subqueries, and set operations. This post walks through the advanced features with practical examples.
Setup: Sample Schema
We'll use a simple e-commerce schema throughout this guide:
await db.ExecuteAsync(@"
CREATE TABLE Customers (
Id INTEGER PRIMARY KEY,
Name TEXT NOT NULL,
Email TEXT NOT NULL
);
CREATE TABLE Orders (
Id INTEGER PRIMARY KEY,
CustomerId INTEGER REFERENCES Customers(Id),
Total REAL NOT NULL,
CreatedAt TEXT NOT NULL
);
CREATE TABLE OrderItems (
Id INTEGER PRIMARY KEY,
OrderId INTEGER REFERENCES Orders(Id),
Product TEXT NOT NULL,
Quantity INTEGER NOT NULL,
UnitPrice REAL NOT NULL
)");
JOINs
CSharpDB supports INNER, LEFT, RIGHT, and CROSS JOINs:
await using var results = await db.ExecuteAsync(@"
SELECT c.Name, o.Total, o.CreatedAt
FROM Customers c
INNER JOIN Orders o ON o.CustomerId = c.Id
WHERE o.Total > 100
ORDER BY o.Total DESC");
LEFT JOIN for Optional Relationships
Find customers who may or may not have orders:
await using var results = await db.ExecuteAsync(@"
SELECT c.Name, COUNT(o.Id) AS OrderCount
FROM Customers c
LEFT JOIN Orders o ON o.CustomerId = c.Id
GROUP BY c.Id, c.Name
ORDER BY OrderCount DESC");
Multi-Table JOINs
Join three tables to get full order details:
await using var results = await db.ExecuteAsync(@"
SELECT c.Name, oi.Product, oi.Quantity, oi.UnitPrice,
oi.Quantity * oi.UnitPrice AS LineTotal
FROM Customers c
INNER JOIN Orders o ON o.CustomerId = c.Id
INNER JOIN OrderItems oi ON oi.OrderId = o.Id
ORDER BY c.Name, oi.Product");
Aggregates and GROUP BY
Standard aggregate functions — COUNT, SUM, AVG, MIN, MAX — work with GROUP BY and HAVING:
await using var results = await db.ExecuteAsync(@"
SELECT c.Name,
COUNT(o.Id) AS TotalOrders,
SUM(o.Total) AS TotalSpent,
AVG(o.Total) AS AvgOrder
FROM Customers c
INNER JOIN Orders o ON o.CustomerId = c.Id
GROUP BY c.Id, c.Name
HAVING SUM(o.Total) > 500
ORDER BY TotalSpent DESC");
Common Table Expressions (CTEs)
CTEs let you break complex queries into readable, named steps:
await using var results = await db.ExecuteAsync(@"
WITH CustomerTotals AS (
SELECT CustomerId,
SUM(Total) AS LifetimeSpend,
COUNT(*) AS OrderCount
FROM Orders
GROUP BY CustomerId
)
SELECT c.Name, ct.LifetimeSpend, ct.OrderCount
FROM Customers c
INNER JOIN CustomerTotals ct ON ct.CustomerId = c.Id
WHERE ct.LifetimeSpend > 1000
ORDER BY ct.LifetimeSpend DESC");
Subqueries
CSharpDB supports scalar subqueries, IN subqueries, and EXISTS — including correlated subqueries:
Scalar Subquery
await using var results = await db.ExecuteAsync(@"
SELECT Name, Email,
(SELECT COUNT(*) FROM Orders o WHERE o.CustomerId = c.Id) AS OrderCount
FROM Customers c");
IN Subquery
-- Customers who ordered products over $50
await using var results = await db.ExecuteAsync(@"
SELECT Name FROM Customers
WHERE Id IN (
SELECT DISTINCT CustomerId FROM Orders WHERE Total > 50
)");
EXISTS Subquery
-- Customers with at least one order
await using var results = await db.ExecuteAsync(@"
SELECT Name FROM Customers c
WHERE EXISTS (
SELECT 1 FROM Orders o WHERE o.CustomerId = c.Id
)");
Set Operations
Combine results from multiple queries with UNION, INTERSECT, and EXCEPT:
-- All product names from orders, deduplicated
await using var results = await db.ExecuteAsync(@"
SELECT Product FROM OrderItems WHERE UnitPrice > 50
UNION
SELECT Product FROM OrderItems WHERE Quantity > 10");
DISTINCT
Deduplicate rows in any SELECT, including with aggregates:
-- Unique products ordered
await using var results = await db.ExecuteAsync(
"SELECT DISTINCT Product FROM OrderItems ORDER BY Product");
-- Count of distinct customers who placed orders
await using var count = await db.ExecuteAsync(
"SELECT COUNT(DISTINCT CustomerId) FROM Orders");
Views and Triggers
Encapsulate complex queries as views and react to data changes with triggers:
-- Create a reusable view
await db.ExecuteAsync(@"
CREATE VIEW CustomerSummary AS
SELECT c.Id, c.Name, c.Email,
COUNT(o.Id) AS OrderCount,
COALESCE(SUM(o.Total), 0) AS TotalSpent
FROM Customers c
LEFT JOIN Orders o ON o.CustomerId = c.Id
GROUP BY c.Id, c.Name, c.Email");
-- Query the view like a table
await using var results = await db.ExecuteAsync(
"SELECT * FROM CustomerSummary WHERE TotalSpent > 500");
Performance Tips
- Create indexes on JOIN columns.
CREATE INDEX idx_orders_customer ON Orders(CustomerId)dramatically speeds up JOINs. - Use composite indexes for multi-column filters. A single composite index can cover both the WHERE and ORDER BY.
- Run ANALYZE periodically. CSharpDB uses table statistics to pick better query plans.
- Use parameterized queries. The prepared statement cache avoids re-parsing identical SQL.