Agent skill

Tsh SQL And Database Understanding

by TheSoftwareHouse in TheSoftwareHouse/copilot-collections

SQL writing and database engineering patterns, standards, and procedures.

MITAuto-check passedDatabases

Install Tsh SQL And Database Understanding

skills CLI
$ npx skills add TheSoftwareHouse/copilot-collections --skill tsh-sql-and-database-understanding -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install TheSoftwareHouse/copilot-collections tsh-sql-and-database-understanding --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/TheSoftwareHouse/copilot-collections.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/tsh-sql-and-database-understanding .claude/skills/tsh-sql-and-database-understanding && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
tsh-sql-and-database-understanding
GitHub stars
284
Token cost
~11k tokens
SKILL.md length
3,426 words
Files
1
Skills in repo
31
Repo updated
First seen
Licence
MIT

At a glance

SQL writing and database engineering patterns, standards, and procedures.

  • Works in 11 steps: Database Schema Architecture Design → Normalisation Strategies → Relationships & Foreign Keys → …
  • Designing database schemas
  • SKILL.md covers Guiding Principles, 1. Database Schema…, 2. Normalisation Strategies and 3. Relationships & Foreign Keys, plus 2 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Tsh SQL And Database Understanding is an agent skill from TheSoftwareHouse/copilot-collections. SQL writing and database engineering patterns, standards, and procedures. Use for designing database schemas, writing performant SQL queries, normalisation strategies, indexing, joins optimisation, locking mechanics, transactions, query debugging with EXPLAIN, and ORM integration. Applies to PostgreSQL, MySQL, MariaDB, SQL Server, and Oracle. Covers ORM usage with TypeORM, Prisma, Doctrine, Eloquent, Entity Framework, Hibernate, and GORM.

Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Databases, covering ORMs and data access, SQL and Database schema design. It works with SQL, MariaDB, Microsoft SQL Server and MySQL. The repository describes itself as: Opinionated AI-enabled workflows for product engineering. The licence is MIT.

When your agent uses it

  • Designing database schemas
  • Writing performant SQL queries
  • Normalisation strategies
  • Joins optimisation

Example prompts

  • “/tsh-sql-and-database-understanding”

Workflow steps

11 steps, taken from the step headings in SKILL.md.

  1. Database Schema Architecture Design
  2. Normalisation Strategies
  3. Relationships & Foreign Keys
  4. Indexes
  5. Writing Performant SQL Queries
  6. Transactions
  7. Locking Mechanics
  8. Query Debugging with EXPLAIN
  9. SQL Writing Best Practices
  10. ORM Integration Guidelines
  11. Database Maintenance & Monitoring

What it can do on your machine

Read from SKILL.md and the folder at commit 2fbe51e. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are sql, typescript, php, csharp, java and go).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Tsh SQL And Database Understanding loads about 11k tokens when it runs. Until then it costs about 119 tokens; SKILL.md has 3,426 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~119
When it runs · the whole SKILL.md, loaded when a task matches
~11k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from TheSoftwareHouse/copilot-collections at commit 2fbe51e, republished under its MIT licence (© TheSoftwareHouse). 3,426 words, ~11,490 tokens.

Download SKILL.mdSave it as .claude/skills/tsh-sql-and-database-understanding/SKILL.md (or your agent's skills folder).
name
tsh-sql-and-database-understanding
description
SQL writing and database engineering patterns, standards, and procedures. Use for designing database schemas, writing performant SQL queries, normalisation strategies, indexing, joins optimisation, locking mechanics, transactions, query debugging with EXPLAIN, and ORM integration. Applies to PostgreSQL, MySQL, MariaDB, SQL Server, and Oracle. Covers ORM usage with TypeORM, Prisma, Doctrine, Eloquent, Entity Framework, Hibernate, and GORM.
user-invocable
false

SQL & Database Engineering

This skill provides standards, patterns, and procedures for database schema design, writing performant SQL, query debugging, and database engineering best practices. It is database-engine-agnostic with notes on engine-specific behavior where critical.

Guiding Principles

PrincipleApplication
Data Integrity FirstEnforce constraints at the database level (NOT NULL, UNIQUE, FK, CHECK). Never rely solely on application-level validation.
Least PrivilegeDatabase users/roles should have only the permissions they need. Application connections should never use the superuser account.
Explicit Over ImplicitAlways specify column lists in SELECT, INSERT, and JOIN clauses. Avoid SELECT * in production code.
Measure Before OptimisingUse EXPLAIN ANALYZE to identify actual bottlenecks before adding indexes or restructuring queries.
Schema as CodeAll schema changes go through versioned migration files. Never modify production schemas manually.

1. Database Schema Architecture Design

Naming Conventions
ElementConventionExample
Tablessnake_case, plural nounsorder_items, user_addresses
Columnssnake_casefirst_name, created_at
Primary keysid (preferred) or <table_singular>_idid, user_id
Foreign keys<referenced_table_singular>_iduser_id, order_id
Indexesidx_<table>_<columns>idx_orders_user_id_status
Unique constraintsuq_<table>_<columns>uq_users_email
Check constraintschk_<table>_<description>chk_orders_total_positive
Junction/pivot tables<table1>_<table2> (alphabetical)products_tags, roles_users
Standard Columns

Every table MUST include:

sql
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- or BIGSERIAL for auto-increment
    -- ... domain columns ...
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

When soft deletes are required by business rules:

sql
deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL
Primary Key Strategy
StrategyWhen to UseTrade-offs
UUID v4 / UUID v7Distributed systems, microservices, public-facing IDsLarger storage, random UUIDs cause index fragmentation (prefer UUID v7 for ordered inserts)
ULIDWhen you need sortable unique IDs with good index localityNot natively supported in all databases
BIGSERIAL / IDENTITYSingle-database monoliths, internal IDsSequential, predictable (security concern if exposed), not portable across DB instances

Rule: Never expose auto-increment IDs in public APIs. Use UUIDs or ULIDs for external identifiers.

Data Types — Choose Precisely
NeedUseAvoid
Monetary valuesNUMERIC(19,4) or DECIMAL(19,4)FLOAT, DOUBLE (precision loss)
TimestampsTIMESTAMP WITH TIME ZONETIMESTAMP without timezone
Boolean flagsBOOLEANTINYINT, CHAR(1)
Short text (name, email)VARCHAR(n) with appropriate limitUnbounded TEXT for structured fields
Long text (descriptions)TEXTVARCHAR(10000)
EnumsVARCHAR with CHECK constraint or native ENUMMagic integers
JSON/semi-structuredJSONB (PostgreSQL) or JSONStoring relational data as JSON
IP addressesINET (PostgreSQL) or VARCHAR(45)VARCHAR(15) (IPv6 won't fit)
Enum Handling

Prefer CHECK constraints over native ENUM types for portability and ease of migration:

sql
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    status VARCHAR(20) NOT NULL DEFAULT 'pending'
        CONSTRAINT chk_orders_status CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled')),
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

Adding a new enum value is a simple ALTER TABLE ... DROP CONSTRAINT + ADD CONSTRAINT, no table rewrite required.

Schema Design Checklist
Schema design checklist:
- [ ] Every table has a primary key (UUID or BIGSERIAL)
- [ ] Every table has created_at and updated_at columns
- [ ] All foreign keys have corresponding indexes
- [ ] All columns have appropriate NOT NULL constraints
- [ ] Unique constraints are defined where business rules require uniqueness
- [ ] CHECK constraints enforce valid value ranges and enums
- [ ] Naming follows snake_case conventions consistently
- [ ] Data types are chosen precisely (no FLOAT for money, no TEXT for emails)
- [ ] Soft delete (deleted_at) is used only where business rules require it
- [ ] No business logic is embedded in database triggers or stored procedures (keep logic in the application layer)

2. Normalisation Strategies

Normal Forms Reference
Normal FormRuleViolation ExampleFix
1NFEvery column holds atomic (indivisible) values. No repeating groups.tags: "php,sql,go" in a single columnCreate a separate tags table with a junction table
2NF1NF + every non-key column depends on the entire primary key (relevant for composite keys)order_items(order_id, product_id, product_name) — product_name depends only on product_idMove product_name to the products table
3NF2NF + no transitive dependencies (non-key column depends on another non-key column)employees(id, department_id, department_name) — department_name depends on department_id, not on idMove department_name to a departments table
BCNF3NF + every determinant is a candidate keyRare in practice; address when composite keys create functional dependency issuesDecompose the table so every determinant is a key
When to Use Each Level

Target 3NF by default. This eliminates redundancy while keeping the schema manageable.

Use 2NF only as an intermediate step when refactoring legacy schemas — never as a design target.

Use BCNF when you have composite primary keys with overlapping candidate keys (uncommon in application databases).

Practical Normalisation Example

Unnormalised (0NF):

orders:
| order_id | customer_name | customer_email      | items                          |
|----------|---------------|---------------------|--------------------------------|
| 1        | John Doe      | john@example.com    | Widget x2, Gadget x1           |

1NF — Atomic values, no repeating groups:

sql
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_name VARCHAR(100) NOT NULL,
    customer_email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

CREATE TABLE order_items (
    id BIGSERIAL PRIMARY KEY,
    order_id BIGINT NOT NULL REFERENCES orders(id),
    product_name VARCHAR(100) NOT NULL,
    quantity INT NOT NULL CHECK (quantity > 0)
);

2NF — Remove partial dependencies (product details depend on product, not order):

sql
CREATE TABLE products (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price NUMERIC(19,4) NOT NULL
);

CREATE TABLE order_items (
    id BIGSERIAL PRIMARY KEY,
    order_id BIGINT NOT NULL REFERENCES orders(id),
    product_id BIGINT NOT NULL REFERENCES products(id),
    quantity INT NOT NULL CHECK (quantity > 0),
    unit_price NUMERIC(19,4) NOT NULL  -- snapshot of price at order time
);

3NF — Remove transitive dependencies (customer depends on customer, not order):

sql
CREATE TABLE customers (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_id BIGINT NOT NULL REFERENCES customers(id),
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
Strategic Denormalisation

Denormalise only when you have measured performance data (via EXPLAIN ANALYZE) proving that joins are the bottleneck. Common valid cases:

PatternWhen to UseExample
Materialised/cached columnsFrequently read aggregate that is expensive to compute on every queryorders.total_amount computed from order_items and stored on the order row
Snapshot columnsValues that must be preserved at a point in time even if the source changesorder_items.unit_price (price at time of purchase, not current product price)
Read-optimised viewsReporting or analytics queries that span many tablesMaterialised views refreshed periodically
Search/filter columnsColumns derived from related tables used heavily in WHERE clausesorders.customer_country duplicated from customers.country for filtering

Rules for denormalisation:

  • Always document why the denormalisation exists (code comment + migration description).
  • Ensure the denormalised data is kept in sync (application-level updates, triggers as last resort, or materialised view refresh).
  • Treat denormalised data as a cache — the normalised source remains the source of truth.

3. Relationships & Foreign Keys

Relationship Types

One-to-Many (most common):

sql
-- A customer has many orders
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_id BIGINT NOT NULL REFERENCES customers(id) ON DELETE RESTRICT,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orders_customer_id ON orders(customer_id);

Many-to-Many (junction table):

sql
-- Products can have many tags, tags can belong to many products
CREATE TABLE products_tags (
    product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
    tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    PRIMARY KEY (product_id, tag_id)
);

CREATE INDEX idx_products_tags_tag_id ON products_tags(tag_id);

One-to-One:

sql
-- Each user has exactly one profile
CREATE TABLE user_profiles (
    user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
    bio TEXT,
    avatar_url VARCHAR(500)
);
ON DELETE / ON UPDATE Actions
ActionMeaningWhen to Use
RESTRICT (default)Prevent deletion if referenced rows existDefault for most business entities (prevent accidental data loss)
CASCADEDelete/update child rows when parent is deleted/updatedJunction tables, dependent child records that have no meaning without parent
SET NULLSet FK to NULL when parent is deletedOptional relationships where the child can exist independently
SET DEFAULTSet FK to its default valueRare — use when a fallback reference makes business sense
NO ACTIONSame as RESTRICT but checked at end of transactionWhen deferred constraint checking is needed

Rule: Default to RESTRICT. Use CASCADE only on junction tables and tightly coupled child tables. Always explicitly specify the action — never rely on implicit defaults.

Self-Referencing Relationships
sql
-- Categories with parent-child hierarchy
CREATE TABLE categories (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    parent_id BIGINT REFERENCES categories(id) ON DELETE SET NULL
);

CREATE INDEX idx_categories_parent_id ON categories(parent_id);

For deep hierarchies (trees), consider:

  • Adjacency list (above) — simple, good for shallow trees
  • Materialised path — path VARCHAR(500) storing /1/5/12/ — good for read-heavy trees
  • Nested sets — complex to maintain but fast for subtree queries
  • Closure table — separate table storing all ancestor-descendant pairs — balanced read/write performance

4. Indexes

Index Types
TypeSyntax (PostgreSQL)Use Case
B-tree (default)CREATE INDEX idx ON table(column)Equality and range queries (=, <, >, BETWEEN, ORDER BY)
HashCREATE INDEX idx ON table USING hash(column)Equality-only lookups (rare — B-tree covers this equally well)
GINCREATE INDEX idx ON table USING gin(column)Full-text search, JSONB containment, array operations
GiSTCREATE INDEX idx ON table USING gist(column)Geometric/spatial data, range types, nearest-neighbor queries
BRINCREATE INDEX idx ON table USING brin(column)Very large tables with naturally ordered data (timestamps, sequential IDs)
Index Strategy Rules

Always index:

  • Foreign key columns (not auto-indexed in PostgreSQL/MySQL InnoDB)
  • Columns frequently used in WHERE clauses
  • Columns used in ORDER BY on large tables
  • Columns used in JOIN conditions

Consider composite indexes for:

  • Queries that filter on multiple columns together
  • Covering indexes that satisfy a query entirely from the index

Avoid over-indexing:

  • Each index adds overhead to INSERT, UPDATE, and DELETE operations
  • Indexes consume storage
  • Review and remove unused indexes periodically
Composite Index Column Order

The order of columns in a composite index matters. Place columns in this priority:

  1. Equality conditions first (WHERE status = 'active')
  2. Range conditions second (WHERE created_at > '2025-01-01')
  3. Sort columns last (ORDER BY created_at DESC)
sql
-- Query: WHERE status = 'active' AND created_at > '2025-01-01' ORDER BY created_at DESC
-- Optimal index:
CREATE INDEX idx_orders_status_created_at ON orders(status, created_at DESC);
Partial Indexes

Index only the rows you actually query:

sql
-- Only index active orders (if most queries filter for active)
CREATE INDEX idx_orders_active ON orders(customer_id, created_at)
    WHERE deleted_at IS NULL;
Unique Indexes

Use unique indexes to enforce business constraints:

sql
-- Email must be unique, but only for non-deleted users
CREATE UNIQUE INDEX uq_users_email_active ON users(email)
    WHERE deleted_at IS NULL;
Index Monitoring

Periodically check for unused indexes:

sql
-- PostgreSQL: find unused indexes
SELECT
    schemaname, relname AS table_name, indexrelname AS index_name,
    idx_scan AS times_used, pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
Non-Blocking Index Operations (CONCURRENTLY)

Standard CREATE INDEX and DROP INDEX acquire locks that block writes on the table for the duration of the operation. On large or heavily-used tables this can cause downtime.

Always use CONCURRENTLY when creating or dropping indexes on tables that serve live traffic:

sql
-- Creating an index without blocking writes
CREATE INDEX CONCURRENTLY idx_orders_customer_id ON orders(customer_id);

-- Dropping an index without blocking writes
DROP INDEX CONCURRENTLY idx_orders_customer_id;

Key constraints and caveats:

AspectDetail
Database supportCONCURRENTLY is PostgreSQL-specific. Other engines have their own online indexing alternatives: MySQL/MariaDB use ALGORITHM=INPLACE, LOCK=NONE; SQL Server uses WITH (ONLINE = ON); Oracle uses ONLINE keyword. Consult engine-specific docs for constraints and limitations
Transaction blocksCREATE INDEX CONCURRENTLY and DROP INDEX CONCURRENTLY cannot run inside a transaction block — avoid wrapping them in BEGIN/COMMIT
Migration frameworksMost migration tools wrap statements in a transaction by default. Disable this for concurrent index operations (e.g., disable_ddl_transaction! in Rails, atomic = False in Django, separate migration step in Flyway)
Failed buildsIf CREATE INDEX CONCURRENTLY fails, it leaves behind an invalid index. Check with \d table_name and drop the invalid index before retrying
Unique indexesCREATE UNIQUE INDEX CONCURRENTLY performs an extra table scan — takes longer but still avoids blocking writes
Build timeConcurrent builds are slower than regular ones because they require multiple table passes

When to skip CONCURRENTLY:

  • Initial schema setup or empty tables (no live traffic to block)
  • During a maintenance window with no active connections
  • Test/development environments

5. Writing Performant SQL Queries

Query Writing Rules
RuleDoDon't
Specify columnsSELECT id, name, email FROM usersSELECT * FROM users
Use parameterised queriesWHERE id = $1 / WHERE id = ?WHERE id = ' + userId + ' (SQL injection risk)
Limit resultsLIMIT 100 or paginateUnbounded SELECT on large tables
Filter earlyWHERE clause narrows rows before joinsJoining full tables then filtering
Use EXISTS over IN for subqueriesWHERE EXISTS (SELECT 1 FROM ...)WHERE id IN (SELECT id FROM ...) on large sets
Avoid functions on indexed columnsWHERE created_at >= '2025-01-01'WHERE DATE(created_at) = '2025-01-01' (kills index)
Use UNION ALL over UNIONUNION ALL when duplicates are acceptableUNION (forces sort + dedup)
JOIN Optimisation
Join Types and When to Use
Join TypeReturnsUse When
INNER JOINOnly matching rows from both tablesYou need data that exists in both tables
LEFT JOINAll rows from left table + matches from right (NULL if no match)You need all records from the primary table regardless of match
RIGHT JOINAll rows from right table + matches from leftRarely used — rewrite as LEFT JOIN for readability
CROSS JOINCartesian product of both tablesGenerating combinations (e.g., all products × all regions)
LATERAL JOINCorrelated subquery for each rowTop-N per group, complex per-row calculations
Join Performance Guidelines
sql
-- GOOD: Join on indexed columns, filter early
SELECT o.id, o.created_at, c.name
FROM orders o
INNER JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'active'
  AND o.created_at >= '2025-01-01'
ORDER BY o.created_at DESC
LIMIT 50;

-- BAD: Joining on expressions, filtering late
SELECT *
FROM orders o
INNER JOIN customers c ON LOWER(c.email) = LOWER(o.customer_email)
WHERE YEAR(o.created_at) = 2025;

Rules:

  • Always join on indexed columns (typically primary keys and foreign keys).
  • Place the most restrictive WHERE conditions on the driving table to reduce the row set early.
  • Avoid joining on computed expressions — create a persisted computed column or a functional index if needed.
  • When joining many tables, consider whether a subquery or CTE might be clearer and equally performant.
Pagination Strategies

Offset-based pagination (simple but degrades on large offsets):

sql
SELECT id, name, email
FROM users
WHERE deleted_at IS NULL
ORDER BY created_at DESC
LIMIT 20 OFFSET 1000;
-- Database must scan and discard 1000 rows

Keyset/cursor-based pagination (performant at any depth):

sql
-- First page
SELECT id, name, email, created_at
FROM users
WHERE deleted_at IS NULL
ORDER BY created_at DESC, id DESC
LIMIT 20;

-- Next page (use last row's values as cursor)
SELECT id, name, email, created_at
FROM users
WHERE deleted_at IS NULL
  AND (created_at, id) < ('2025-02-01T10:00:00Z', '550e8400-e29b-41d4-a716-446655440000')
ORDER BY created_at DESC, id DESC
LIMIT 20;

Rule: Use offset-based pagination for admin/back-office UIs with moderate data volumes (< 100k rows). Use keyset pagination for APIs, infinite scrolling, and large datasets.

Aggregation Best Practices
sql
-- GOOD: Filter before aggregating
SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS total_spent
FROM orders
WHERE status = 'completed'
  AND created_at >= '2025-01-01'
GROUP BY customer_id
HAVING COUNT(*) > 5
ORDER BY total_spent DESC;

-- BAD: Aggregating everything then filtering in application code
SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS total_spent
FROM orders
GROUP BY customer_id;
-- Then filtering in PHP/Node/Java... wasted database work
Common Table Expressions (CTEs)

Use CTEs for readability. Be aware that in some databases (PostgreSQL < 12), CTEs act as optimisation fences:

sql
-- Readable multi-step query
WITH active_customers AS (
    SELECT id, name
    FROM customers
    WHERE status = 'active'
      AND deleted_at IS NULL
),
recent_orders AS (
    SELECT customer_id, COUNT(*) AS order_count
    FROM orders
    WHERE created_at >= NOW() - INTERVAL '30 days'
    GROUP BY customer_id
)
SELECT ac.id, ac.name, COALESCE(ro.order_count, 0) AS recent_orders
FROM active_customers ac
LEFT JOIN recent_orders ro ON ro.customer_id = ac.id
ORDER BY recent_orders DESC;
Window Functions

Use window functions for ranking, running totals, and per-group calculations without collapsing rows:

sql
-- Rank orders by total within each customer
SELECT
    customer_id,
    id AS order_id,
    total,
    ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rank,
    SUM(total) OVER (PARTITION BY customer_id) AS customer_total
FROM orders
WHERE status = 'completed';
Batch Operations

For large data modifications, batch to avoid long locks and transaction log bloat:

sql
-- Instead of one massive DELETE:
-- DELETE FROM logs WHERE created_at < '2024-01-01';  -- locks millions of rows

-- Batch delete:
DO $$
DECLARE
    rows_deleted INT;
BEGIN
    LOOP
        DELETE FROM logs
        WHERE id IN (
            SELECT id FROM logs
            WHERE created_at < '2024-01-01'
            LIMIT 5000
        );
        GET DIAGNOSTICS rows_deleted = ROW_COUNT;
        EXIT WHEN rows_deleted = 0;
        COMMIT;
    END LOOP;
END $$;

6. Transactions

ACID Properties
PropertyMeaningPractical Impact
AtomicityAll operations in a transaction succeed or all are rolled backPartial updates never reach the database
ConsistencyTransaction moves the database from one valid state to anotherConstraints are enforced at commit time
IsolationConcurrent transactions don't interfere with each otherDetermined by the isolation level
DurabilityCommitted data survives system failuresData is persisted to disk on commit
Transaction Isolation Levels
LevelDirty ReadsNon-Repeatable ReadsPhantom ReadsUse Case
READ UNCOMMITTEDYesYesYesAlmost never — only for dirty analytics on non-critical data
READ COMMITTEDNoYesYesDefault for PostgreSQL. Good for most OLTP workloads
REPEATABLE READNoNoYes*Financial calculations, inventory checks within a transaction
SERIALIZABLENoNoNoCritical financial operations, booking systems with strict consistency

*PostgreSQL's REPEATABLE READ also prevents phantom reads (it uses snapshot isolation internally).

Rule: Use READ COMMITTED by default. Escalate to REPEATABLE READ or SERIALIZABLE only for specific operations that require stronger guarantees, and handle serialisation failures with retry logic.

Transaction Best Practices
sql
-- GOOD: Short, focused transaction
BEGIN;
    UPDATE accounts SET balance = balance - 100.00 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100.00 WHERE id = 2;
    INSERT INTO transactions (from_id, to_id, amount) VALUES (1, 2, 100.00);
COMMIT;

Rules:

  • Keep transactions as short as possible. Long transactions hold locks and block other operations.
  • Never perform external I/O (HTTP calls, file operations) inside a transaction.
  • Always handle transaction failures: catch exceptions, rollback, and optionally retry.
  • Use SAVEPOINT for partial rollbacks within a larger transaction when needed.
  • Acquire locks in a consistent order across all transactions to prevent deadlocks (e.g., always lock by ascending ID).
Savepoints
sql
BEGIN;
    INSERT INTO orders (customer_id, total) VALUES (1, 250.00);
    SAVEPOINT before_items;

    INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 99, 1);
    -- If this fails:
    ROLLBACK TO SAVEPOINT before_items;
    -- Order is still inserted, items are rolled back

COMMIT;

7. Locking Mechanics

Lock Types
Lock TypeSQLScopeBehaviour
Row-level shared (FOR SHARE)SELECT ... FOR SHARERowOther transactions can read but not modify the row
Row-level exclusive (FOR UPDATE)SELECT ... FOR UPDATERowOther transactions cannot read (with FOR UPDATE) or modify the row
Table-levelLOCK TABLE ... IN <mode> MODETableApplies to the entire table — use sparingly
Advisory lockspg_advisory_lock(key)Application-definedApplication-level coordination, not tied to specific rows
Show full SKILL.md (1,386 more words)Show less
Optimistic Locking

Optimistic locking assumes conflicts are rare. It checks for conflicts at write time using a version column:

sql
-- Schema
ALTER TABLE products ADD COLUMN version INT NOT NULL DEFAULT 1;

-- Read: fetch current version
SELECT id, name, price, stock, version FROM products WHERE id = 42;
-- Application receives: version = 3

-- Update: include version check
UPDATE products
SET stock = stock - 1, version = version + 1, updated_at = NOW()
WHERE id = 42 AND version = 3;

-- If 0 rows affected → another transaction modified the row → handle conflict (retry or error)

When to use:

  • Low contention (concurrent writes to the same row are rare)
  • Read-heavy workloads
  • User-facing forms where edits happen seconds/minutes apart
  • Distributed systems where holding database locks across requests is impractical

ORM support:

ORMImplementation
TypeORM@VersionColumn() decorator
PrismaManual implementation via version field and conditional update
Doctrine (PHP)@ORM\Version annotation on an integer or datetime column
Eloquent (Laravel)Manual implementation or laravel-optimistic-locking package
Entity Framework (.NET)[Timestamp] attribute or IsRowVersion() in Fluent API
Hibernate (Java)@Version annotation
GORM (Go)Built-in optimistic locking with gorm:"column:version" tag
Pessimistic Locking

Pessimistic locking assumes conflicts are likely. It acquires locks at read time to prevent concurrent modification:

sql
-- Lock the row immediately — other transactions block until this transaction commits
BEGIN;
    SELECT * FROM inventory WHERE product_id = 42 FOR UPDATE;
    -- ... perform business logic ...
    UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 42;
COMMIT;

Variants:

sql
-- FOR UPDATE SKIP LOCKED — skip already-locked rows (useful for job queues)
SELECT id, payload
FROM job_queue
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;

-- FOR UPDATE NOWAIT — fail immediately if the row is locked (instead of blocking)
SELECT * FROM inventory WHERE product_id = 42 FOR UPDATE NOWAIT;
-- Throws an error immediately if the row is locked by another transaction

When to use:

  • High contention (concurrent writes to the same row are likely)
  • Critical sections that must not have conflicts (inventory decrements, booking seats)
  • Short-lived transactions where holding a lock briefly is acceptable
  • Job queues and task processing (SKIP LOCKED)
Optimistic vs Pessimistic — Decision Guide
FactorOptimisticPessimistic
Conflict frequencyLow (< 1% of operations)High or unpredictable
Read/write ratioRead-heavyWrite-heavy on contested resources
Transaction durationCan be long (no locks held)Must be short (locks block others)
Failure handlingRetry on conflictBlock until lock is released
Distributed systemsPreferred (no lock coordination needed)Difficult (requires sticky sessions or distributed locks)
User experienceMay show "conflict, please retry"May show loading/waiting if contention is high
Deadlock Prevention
  1. Consistent lock ordering: Always acquire locks in the same order (e.g., by ascending primary key).
  2. Short transactions: Minimise the time locks are held.
  3. Lock timeout: Set lock_timeout to avoid indefinite blocking.
  4. Detect and retry: Handle deadlock exceptions and retry the transaction.
sql
-- PostgreSQL: set a lock timeout
SET lock_timeout = '5s';

-- MySQL: set innodb_lock_wait_timeout per session
SET innodb_lock_wait_timeout = 5;

8. Query Debugging with EXPLAIN

EXPLAIN Basics
sql
-- Show the query plan (does not execute the query)
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;

-- Show the query plan AND execute the query (shows actual timings)
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42;

-- Full diagnostic output (PostgreSQL)
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'active'
ORDER BY o.created_at DESC
LIMIT 20;
Reading EXPLAIN Output

Key fields to examine:

FieldWhat It Tells You
Seq ScanFull table scan — often a sign of missing index
Index ScanUsing an index to find rows — this is what you want
Index Only ScanData served entirely from the index (covering index) — best case
Bitmap Index ScanIndex used to build a bitmap, then table scanned — common for OR conditions / multiple indexes
Nested LoopFor each row in outer table, scan inner table — efficient for small outer sets
Hash JoinBuild hash table from one side, probe with the other — efficient for large equijoins
Merge JoinBoth sides sorted, then merged — efficient when both inputs are pre-sorted
SortExplicit sort operation — check if an index could eliminate this
Rows (estimated)Planner's estimate of rows processed at this step
Actual Rows (ANALYZE only)Real number of rows processed — compare with estimated to find planner misestimates
Buffers (ANALYZE + BUFFERS)Shared/local buffers hit (cache) vs read (disk) — high reads = cache miss, consider prewarming or more memory
CostArbitrary units comparing relative cost of plan steps — lower is better
Common Performance Problems and Fixes
EXPLAIN SymptomLikely CauseFix
Seq Scan on large tableMissing indexAdd appropriate index
Seq Scan despite index existingQuery uses a function on the indexed columnRewrite query or create a functional index
Estimated rows ≠ actual rows (off by 10x+)Stale statisticsRun ANALYZE table_name;
Sort with high costNo index matching ORDER BYAdd index covering sort columns
Nested Loop with large inner tablePlanner chose wrong join strategyVerify indexes on join columns; consider increasing work_mem
Hash Join spilling to diskwork_mem too small for the hash tableIncrease work_mem for the session or globally
High Buffers: read countData not in cacheIncrease shared_buffers or ensure table fits in memory; check for bloated tables
Iterative Query Optimisation Process
Query optimisation process:
1. [ ] Identify the slow query (application logs, pg_stat_statements, slow query log)
2. [ ] Run EXPLAIN ANALYZE on the query
3. [ ] Identify the most expensive node in the plan
4. [ ] Determine root cause (missing index, bad statistics, inefficient join, sort)
5. [ ] Apply ONE fix at a time
6. [ ] Re-run EXPLAIN ANALYZE and compare
7. [ ] Repeat until acceptable performance is reached
8. [ ] Verify the fix doesn't degrade other queries
MySQL-Specific EXPLAIN Notes
sql
-- MySQL uses a different EXPLAIN format
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;

-- Key MySQL EXPLAIN columns:
-- type: ALL (full scan) → index → range → ref → eq_ref → const → system (best to worst)
-- possible_keys: indexes the optimiser considered
-- key: index actually used
-- rows: estimated rows scanned
-- Extra: "Using filesort", "Using temporary" are red flags

-- MySQL 8.0+ supports EXPLAIN ANALYZE:
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42;

9. SQL Writing Best Practices

Formatting Standards
sql
-- GOOD: Readable, consistent formatting
SELECT
    u.id,
    u.email,
    u.first_name,
    u.last_name,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.total), 0) AS lifetime_value
FROM users u
LEFT JOIN orders o ON o.customer_id = u.id
    AND o.status = 'completed'
WHERE u.deleted_at IS NULL
  AND u.created_at >= '2025-01-01'
GROUP BY u.id, u.email, u.first_name, u.last_name
HAVING COUNT(o.id) > 0
ORDER BY lifetime_value DESC
LIMIT 50;

Formatting rules:

  • Keywords in UPPERCASE: SELECT, FROM, WHERE, JOIN, ORDER BY, GROUP BY
  • One column per line in SELECT list for queries with more than 3 columns
  • AND/OR at the beginning of the line, indented
  • Table aliases: short but meaningful (u for users, o for orders)
  • Align JOIN ... ON conditions consistently
  • Always terminate statements with ;
Security — Preventing SQL Injection

Never concatenate user input into SQL strings. Always use parameterised queries:

sql
-- GOOD: Parameterised (prepared statement)
-- PostgreSQL / Node.js
SELECT * FROM users WHERE email = $1;

-- MySQL / PHP
SELECT * FROM users WHERE email = ?;

ORM equivalents (all safe by default):

typescript
// TypeORM — safe
const user = await userRepository.findOne({ where: { email: userInput } });

// Prisma — safe
const user = await prisma.user.findUnique({ where: { email: userInput } });
php
// Doctrine — safe
$user = $repository->findOneBy(['email' => $userInput]);

// Eloquent — safe
$user = User::where('email', $userInput)->first();
csharp
// Entity Framework — safe
var user = await context.Users.FirstOrDefaultAsync(u => u.Email == userInput);
java
// Hibernate — safe
User user = session.createQuery("FROM User u WHERE u.email = :email", User.class)
    .setParameter("email", userInput)
    .uniqueResult();
go
// GORM — safe
var user User
db.Where("email = ?", userInput).First(&user)

DANGER: Raw queries with string concatenation:

typescript
// TypeORM — DANGEROUS if not parameterised
const users = await dataSource.query(`SELECT * FROM users WHERE email = '${userInput}'`); // SQL INJECTION!

// TypeORM — SAFE raw query
const users = await dataSource.query('SELECT * FROM users WHERE email = $1', [userInput]);
NULL Handling
sql
-- WRONG: = NULL never matches
SELECT * FROM users WHERE deleted_at = NULL;

-- CORRECT: Use IS NULL / IS NOT NULL
SELECT * FROM users WHERE deleted_at IS NULL;

-- Use COALESCE for default values
SELECT COALESCE(u.nickname, u.first_name, 'Anonymous') AS display_name
FROM users u;

-- NULL-safe comparison (when comparing two nullable columns)
-- PostgreSQL:
SELECT * FROM t1 JOIN t2 ON t1.val IS NOT DISTINCT FROM t2.val;
-- MySQL:
SELECT * FROM t1 JOIN t2 ON t1.val <=> t2.val;
UPSERT Patterns
sql
-- PostgreSQL: INSERT ... ON CONFLICT
INSERT INTO products (sku, name, price, updated_at)
VALUES ('WIDGET-001', 'Widget', 19.99, NOW())
ON CONFLICT (sku)
DO UPDATE SET
    name = EXCLUDED.name,
    price = EXCLUDED.price,
    updated_at = NOW();

-- MySQL: INSERT ... ON DUPLICATE KEY UPDATE
INSERT INTO products (sku, name, price, updated_at)
VALUES ('WIDGET-001', 'Widget', 19.99, NOW())
ON DUPLICATE KEY UPDATE
    name = VALUES(name),
    price = VALUES(price),
    updated_at = NOW();
Avoiding Common Anti-Patterns
Anti-PatternProblemFix
SELECT *Fetches unnecessary columns, breaks when schema changesExplicitly list needed columns
N+1 queries1 query to fetch parents + N queries for childrenUse JOIN or batch loading (WHERE id IN (...))
WHERE column != value with index!= / <> cannot use index efficientlyRestructure as positive condition or check if full scan is acceptable
LIKE '%value%'Leading wildcard prevents index useUse full-text search (tsvector in PostgreSQL, FULLTEXT in MySQL)
ORDER BY RAND()Scans and sorts entire tableUse a sampled approach: WHERE id >= (SELECT FLOOR(RANDOM() * MAX(id)) FROM table) LIMIT 1
Implicit type conversionWHERE varchar_col = 123 may skip indexMatch types explicitly: WHERE varchar_col = '123'
Correlated subquery in SELECTExecutes subquery for every rowRewrite as JOIN or window function
Missing LIMIT on exploratory queriesAccidentally fetching millions of rowsAlways add LIMIT when exploring data

10. ORM Integration Guidelines

ORM Selection by Stack
StackPrimary ORMAlternative
PHP (Symfony)Doctrine ORM—
PHP (Laravel)Eloquent ORM—
Node.js (NestJS)TypeORM or MikroORMPrisma
Node.js (General)PrismaTypeORM, Drizzle
.NETEntity Framework CoreDapper (micro-ORM for raw SQL)
Java (Spring)Hibernate / Spring Data JPAjOOQ (for SQL-first approach)
GoGORMsqlx (for raw SQL), Ent
ORM Best Practices

1. Always review generated SQL. Enable query logging in development to inspect what the ORM generates. Inefficient ORM usage can produce catastrophic queries.

typescript
// TypeORM: enable query logging
const dataSource = new DataSource({
    logging: ['query', 'error'],
    // ...
});

// Prisma: enable query logging
const prisma = new PrismaClient({
    log: ['query', 'warn', 'error'],
});
php
// Doctrine: enable SQL logger
$configuration->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());

// Laravel: enable query log
DB::enableQueryLog();
// ... run queries ...
dd(DB::getQueryLog());

2. Solve N+1 queries with eager loading.

typescript
// TypeORM — eager loading with relations
const orders = await orderRepository.find({
    relations: ['customer', 'items', 'items.product'],
    where: { status: 'active' },
});

// Prisma — include related data
const orders = await prisma.order.findMany({
    where: { status: 'active' },
    include: {
        customer: true,
        items: { include: { product: true } },
    },
});
php
// Eloquent — eager loading
$orders = Order::with(['customer', 'items.product'])
    ->where('status', 'active')
    ->get();

// Doctrine — DQL with joins
$orders = $em->createQuery('
    SELECT o, c, i, p
    FROM App\Entity\Order o
    JOIN o.customer c
    JOIN o.items i
    JOIN i.product p
    WHERE o.status = :status
')->setParameter('status', 'active')
  ->getResult();
csharp
// Entity Framework — eager loading
var orders = await context.Orders
    .Include(o => o.Customer)
    .Include(o => o.Items)
        .ThenInclude(i => i.Product)
    .Where(o => o.Status == "active")
    .ToListAsync();
java
// Hibernate / Spring Data JPA — fetch join
@Query("SELECT o FROM Order o " +
       "JOIN FETCH o.customer " +
       "JOIN FETCH o.items i " +
       "JOIN FETCH i.product " +
       "WHERE o.status = :status")
List<Order> findActiveWithDetails(@Param("status") String status);
go
// GORM — preload
var orders []Order
db.Preload("Customer").Preload("Items.Product").
    Where("status = ?", "active").
    Find(&orders)

3. Use raw SQL for complex queries. When ORM abstractions produce inefficient SQL or cannot express the query you need, drop to raw SQL:

typescript
// TypeORM — raw query
const results = await dataSource.query(`
    SELECT u.id, u.email, COUNT(o.id) AS order_count
    FROM users u
    LEFT JOIN orders o ON o.customer_id = u.id AND o.status = 'completed'
    WHERE u.deleted_at IS NULL
    GROUP BY u.id, u.email
    HAVING COUNT(o.id) > 5
    ORDER BY order_count DESC
    LIMIT $1
`, [50]);

// Prisma — raw query
const results = await prisma.$queryRaw`
    SELECT u.id, u.email, COUNT(o.id) AS order_count
    FROM users u
    LEFT JOIN orders o ON o.customer_id = u.id AND o.status = 'completed'
    WHERE u.deleted_at IS NULL
    GROUP BY u.id, u.email
    HAVING COUNT(o.id) > 5
    ORDER BY order_count DESC
    LIMIT ${50}
`;

4. ORM Migration Workflow.

  • Generate migrations from entity/model changes, never edit the database directly.
  • Review generated migration SQL before applying — ORMs sometimes generate suboptimal DDL.
  • Test migrations against a copy of production data to catch issues (data truncation, long-running locks).

5. Repository Pattern with ORMs. Keep all database queries in repository classes. Services should never directly use the ORM's query builder or entity manager:

Service → Repository Interface → Repository Implementation (uses ORM)

This keeps business logic decoupled from the data access mechanism and makes testing easier (mock the repository interface).


11. Database Maintenance & Monitoring

Statistics and Vacuuming (PostgreSQL)
sql
-- Update table statistics for the planner
ANALYZE orders;

-- Update statistics for all tables
ANALYZE;

-- Check for table bloat (dead tuples)
SELECT relname, n_dead_tup, n_live_tup,
       ROUND(n_dead_tup::NUMERIC / NULLIF(n_live_tup, 0) * 100, 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
Slow Query Identification
sql
-- PostgreSQL: enable pg_stat_statements extension
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Find slowest queries by total time
SELECT
    calls,
    ROUND(total_exec_time::NUMERIC, 2) AS total_ms,
    ROUND(mean_exec_time::NUMERIC, 2) AS mean_ms,
    ROUND(max_exec_time::NUMERIC, 2) AS max_ms,
    LEFT(query, 200) AS query_preview
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
sql
-- MySQL: enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- queries taking > 1 second
Connection Pooling
  • Always use connection pooling in production. Direct connections are expensive to create.
  • PostgreSQL: use PgBouncer or built-in pooling in the ORM/driver.
  • MySQL: use ProxySQL or driver-level pooling.
  • Set pool size based on: pool_size = (core_count * 2) + effective_spindle_count (formula from PostgreSQL wiki).
  • Monitor for connection exhaustion — set max_connections appropriately and alert when pool utilisation exceeds 80%.

Implementation Procedure

When writing SQL or designing a database schema, follow this workflow:

Implementation progress:
- [ ] Step 1: Understand the data requirements
- [ ] Step 2: Design the schema (entities, relationships, constraints)
- [ ] Step 3: Choose normalisation level and document any denormalisation decisions
- [ ] Step 4: Define indexes based on expected query patterns
- [ ] Step 5: Write the migration(s)
- [ ] Step 6: Write the queries (raw SQL or ORM)
- [ ] Step 7: Run EXPLAIN ANALYZE on critical queries
- [ ] Step 8: Implement appropriate locking and transaction strategy
- [ ] Step 9: Review ORM-generated SQL (if applicable)
- [ ] Step 10: Test with realistic data volumes

Step 1: Understand the data requirements Read the task description and acceptance criteria. Identify the entities, their attributes, and the relationships between them. Clarify cardinality (one-to-many, many-to-many).

Step 2: Design the schema Create tables following the naming conventions and standard columns defined in this skill. Define relationships with appropriate foreign keys and ON DELETE actions.

Step 3: Choose normalisation level Target 3NF by default. Document any intentional denormalisation with a clear justification tied to measured performance needs or snapshot requirements.

Step 4: Define indexes Identify the primary query patterns and create indexes to support them. Index all foreign keys. Create composite indexes for multi-column filters and sorts.

Step 5: Write the migration(s) Generate migration files with both up and down methods. Review the generated DDL before applying.

Step 6: Write the queries Write queries following the performance and formatting rules in this skill. Use parameterised queries. Avoid SELECT *.

Step 7: Run EXPLAIN ANALYZE Verify critical query plans. Look for sequential scans on large tables, missing indexes, and planner misestimates.

Step 8: Implement locking and transaction strategy Choose optimistic or pessimistic locking based on the contention profile. Define transaction boundaries and appropriate isolation levels.

Step 9: Review ORM-generated SQL Enable query logging and inspect generated queries for N+1 problems, unnecessary joins, or missing eager loading.

Step 10: Test with realistic data volumes Seed the development database with production-scale data. Verify that queries perform within acceptable time limits under load.

Connected Skills

  • tsh-architecture-designing — for designing data models as part of broader system architecture
  • tsh-code-reviewing — for validating SQL quality, index coverage, and query performance
  • tsh-technical-context-discovering — for establishing database conventions and existing patterns before designing

© TheSoftwareHouse, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .github/skills/tsh-sql-and-database-understanding of TheSoftwareHouse/copilot-collections.

Open the folder on GitHubat commit 2fbe51e

Compare with similar skills

Tsh SQL And Database Understanding next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Tsh SQL And Database Understanding compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Tsh SQL And Database Understanding this skillTheSoftwareHouse/copilot-collections284—~11kAutomated safety check: PassMIT
Database FundamentalsDanielPodolsky/ownyourcode2901 repos~1.6kAutomated safety check: PassMIT
Golang Databaseunxed/f42402 repos~2.9kAutomated safety check: PassMIT
SQL ProJeffallan/claude-skills12k—~1.3kAutomated safety check: PassMIT
DB Review312362115/claude107—~1.8kAutomated safety check: PassMIT
SQL Database Assistantalirezarezvani/claude-skills28k—~4kAutomated safety check: PassMIT

Similar skills

  • Database Fundamentals

    DanielPodolsky/ownyourcode

    Reviews schema design, SQL queries, ORM patterns. An agent skill from DanielPodolsky/ownyourcode.

    290 GitHub starsUsed in 1 repo~1.6k tokens
    DatabasesAuto-check passed
  • Comprehensive guide for Go database access — parameterized queries, struct scanning, NULLable columns, transactions, isolation levels, SELECT FOR UPDATE, connection pool, batch processing, context…

    240 GitHub starsUsed in 2 repos~2.9k tokens
    DatabasesAuto-check passed
  • SQL Pro

    Jeffallan/claude-skills

    Optimizes SQL queries and designs schemas using CTEs, window functions, covering indexes and EXPLAIN ANALYZE, with notes on dialect differences between major databases.

    12k GitHub stars~1.3k tokensUpdated 4 days ago
    DatabasesAuto-check passed
  • DB Review

    312362115/claude

    数据库代码审查 + Migration 安全检查. An agent skill from 312362115/claude.

    107 GitHub stars~1.8k tokensUpdated 4 mo ago
    DatabasesAuto-check passed
  • SQL Database Assistant

    alirezarezvani/claude-skills

    A skill your agent uses when the user asks to write SQL queries, optimize database performance, generate migrations, explore database schemas, or work with ORMs like Prisma, Drizzle, TypeORM, or…

    28k GitHub stars~4k tokensUpdated 1 mo ago
    DatabasesAuto-check passed
  • Agent SQL Pro

    xiaoyuge886/aigc

    Expert SQL developer specializing in complex query optimization, database design, and performance tuning across PostgreSQL, MySQL, SQL Server, and Oracle.

    198 GitHub stars~315 tokensUpdated 2 mo ago
    DatabasesAuto-check passed

More from TheSoftwareHouse/copilot-collections

All 31 skills in this repo
  • Tsh Creating Skills

    TheSoftwareHouse/copilot-collections

    Create new skills (SKILL.md) for GitHub Copilot. An agent skill from TheSoftwareHouse/copilot-collections.

    284 GitHub stars~4.1k tokensUpdated 2 days ago
    Auto-check passed
  • Tsh Implementing Frontend

    TheSoftwareHouse/copilot-collections

    Frontend component patterns, composition, design token integration, barrel file organization, error handling, and Figma-to-code workflow.

    284 GitHub stars~2.6k tokensUpdated 2 days ago
    Auto-check passed
  • Tsh Implementing Terraform Modules

    TheSoftwareHouse/copilot-collections

    Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices.

    284 GitHub stars~1.6k tokensUpdated 2 days ago
    Auto-check passed
  • Tsh Optimizing Frontend

    TheSoftwareHouse/copilot-collections

    Frontend rendering optimization, code splitting, memoization strategies, bundle size control, asset optimization, and memory management.

    284 GitHub stars~4.1k tokensUpdated 2 days ago
    Auto-check passed
  • Tsh Reviewing Frontend

    TheSoftwareHouse/copilot-collections

    Frontend-specific code review criteria, component anti-patterns, hooks quality, rendering correctness, accessibility and performance spot-checks, and module organization issues.

    284 GitHub stars~4.4k tokensUpdated 2 days ago
    Auto-check passed
  • Tsh Writing Hooks

    TheSoftwareHouse/copilot-collections

    Custom hook and composable patterns — naming, composition, stable return shapes, lifecycle cleanup, and testing strategies.

    284 GitHub stars~3k tokensUpdated 2 days ago
    Auto-check passed

Categories

Questions about Tsh SQL And Database Understanding

What does Tsh SQL And Database Understanding do?

SQL writing and database engineering patterns, standards, and procedures. Tsh SQL And Database Understanding is an agent skill from TheSoftwareHouse/copilot-collections. SQL writing and database engineering patterns, standards, and procedures.

When should I use Tsh SQL And Database Understanding?

Tsh SQL And Database Understanding fits situations like: designing database schemas; writing performant SQL queries; normalisation strategies; joins optimisation.

How do I install Tsh SQL And Database Understanding in Claude Code?

Run `npx skills add TheSoftwareHouse/copilot-collections --skill tsh-sql-and-database-understanding -a claude-code`. Or copy the skill folder (.github/skills/tsh-sql-and-database-understanding in TheSoftwareHouse/copilot-collections) into .claude/skills/tsh-sql-and-database-understanding in your project. Claude Code loads it when a task matches its description.

How do I install Tsh SQL And Database Understanding in Codex?

Run `npx skills add TheSoftwareHouse/copilot-collections --skill tsh-sql-and-database-understanding -a codex`. Or copy the skill folder (.github/skills/tsh-sql-and-database-understanding in TheSoftwareHouse/copilot-collections) into .agents/skills/tsh-sql-and-database-understanding in your project. Codex loads it when a task matches its description.

Can I use Tsh SQL And Database Understanding in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add TheSoftwareHouse/copilot-collections --skill tsh-sql-and-database-understanding -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/tsh-sql-and-database-understanding, .gemini/skills/tsh-sql-and-database-understanding, .github/skills/tsh-sql-and-database-understanding and .opencode/skills/tsh-sql-and-database-understanding in your project.

What does Tsh SQL And Database Understanding need to run?

SKILL.md names no scripts, command-line tools or credentials: Tsh SQL And Database Understanding is instructions for the agent only.

Does Tsh SQL And Database Understanding access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Tsh SQL And Database Understanding safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Tsh SQL And Database Understanding use?

Tsh SQL And Database Understanding is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Tsh SQL And Database Understanding use?

About 11k tokens (SKILL.md is roughly 46k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Tsh SQL And Database Understanding?

Skills that share tags, products or a category with Tsh SQL And Database Understanding: Database Fundamentals (DanielPodolsky/ownyourcode, 290 stars), Golang Database (unxed/f4, 240 stars), SQL Pro (Jeffallan/claude-skills, 12k stars) and DB Review (312362115/claude, 107 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Tsh SQL And Database Understanding?

TheSoftwareHouse (a GitHub organization) maintains it in TheSoftwareHouse/copilot-collections, which has 284 GitHub stars. The repository holds 31 skills in this directory. The repository was last updated on October 5, 2026.

Source: TheSoftwareHouse/copilot-collections on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.