{
 "id": "sql-server-liquibase",
 "kind": "skill",
 "name": "SQL Server and Liquibase",
 "description": "Write T-SQL and SQL Server DBA queries, query databases read-only, and manage schema changes with Liquibase changelogs (also Azure SQL Managed Instance).",
 "version": "1.0.0",
 "author": "Hexa Hub",
 "files": {
  "SKILL.md": "---\nname: sql-server-liquibase\ndescription: Write T-SQL and SQL Server DBA queries, query databases read-only, and manage schema changes with Liquibase changelogs (also Azure SQL Managed Instance).\ntitle: SQL Server and Liquibase\nicon: tabler:database\ncategory: Databases\ntriggers: sql, t-sql, tsql, sql server, sqlcmd, liquibase, changelog, changeset, migration, stored procedure, index, query plan, azure sql, managed instance, cdc, deadlock\nmarkers: sqlcmd, liquibase\nrecipes: sql.query, sql.execute, liquibase.validate, liquibase.status, liquibase.update-sql, liquibase.update\nrelated: azure-admin, laravel-php\n---\n\n# SQL Server and Liquibase\n\n## Reading data from a database\nUse `sql.query` (SQL Server), `mysql.query` or `psql.query`. The login is a **secret handle** from the vault, for example `{{secret:shop-test-reader}}`: put the handle in `password`, never a real password. Each query asks the user because it uses a secret.\n- Ask which server, database and user; use a **read-only login**. Look at the handles listed in the prompt.\n- Only `SELECT`/`WITH` is accepted. Always limit the rows (`SELECT TOP 100 ...`) and name the columns; never `SELECT *` on big tables.\n- Results may contain personal data: show only what the question needs and do not copy rows into files.\n\nExample: `run_recipe {\"recipe\":\"sql.query\",\"args\":{\"server\":\"sql-test.corp.local\",\"database\":\"Shop\",\"user\":\"reader\",\"password\":\"{{secret:shop-test-reader}}\",\"trust_server_certificate\":\"-C\",\"query\":\"SELECT TOP 20 name, create_date FROM sys.tables ORDER BY create_date DESC\"}}`\n\nUseful read-only DBA queries: `sys.dm_exec_requests` (what runs now), `sys.dm_os_wait_stats` (what the server waits for), `sys.dm_db_index_usage_stats`, `sys.dm_exec_query_stats` with `sys.dm_exec_sql_text`, `sys.databases` (state, recovery model), `msdb.dbo.backupset` (last backups).\n\n## Changing a database: always Liquibase\nNever change a shared database with `sql.execute` unless the user explicitly asks for that one statement. Schema changes are changesets:\n1. Find the master changelog and how existing changesets are written (SQL-formatted or YAML/XML) and numbered. Copy that style.\n2. Add a **new** changeset; one change per changeset.\n   ```sql\n   --liquibase formatted sql\n   --changeset derek:2026-10-add-customer-email\n   ALTER TABLE dbo.Customer ADD Email nvarchar(256) NULL;\n   --rollback ALTER TABLE dbo.Customer DROP COLUMN Email;\n   ```\n3. `liquibase.validate`, then `liquibase.status` (what is pending), then `liquibase.update-sql` (the exact SQL). **Show the user the SQL.**\n4. Only if asked: `liquibase.update` (asks; right after the update-sql, blocked on prod).\n\nRules that prevent disasters:\n- **Never edit or delete a changeset that has been applied anywhere**: its checksum is stored and Liquibase refuses to continue. Add a new changeset that fixes it.\n- Every changeset has a unique `id` + `author` (+ file). Add a `--rollback` or `<rollback>` for each. Use `runOnChange:true` only for views, procedures and functions (written as `CREATE OR ALTER`).\n- Preconditions (`--preconditions onFail:MARK_RAN`) when the object might exist already. Use contexts or labels for data that belongs only to one environment.\n- Connection details live in the `liquibase.properties` the user keeps; do not write passwords into it or into the changelog.\n\n## T-SQL habits\n- Schema-qualify objects (`dbo.Customer`); `SET NOCOUNT ON;` at the top of procedures; `TRY/CATCH` with `THROW`; parameters, never string-concatenated SQL (`sp_executesql` with parameters if dynamic SQL is unavoidable).\n- Set-based code over cursors and loops; `EXISTS` over `COUNT(*) > 0`; avoid functions on indexed columns in `WHERE` (not sargable); explicit column lists in `INSERT`.\n- Indexes: look at the actual plan and the missing-index DMVs as hints, not orders; an index helps reads and costs writes.\n- Use `datetime2` and `nvarchar` for new columns; always `ORDER BY` when order matters; mind implicit conversions (a varchar parameter against an nvarchar column).\n- Destructive statements (`DROP`, `TRUNCATE`, `DELETE` without `WHERE`) need the user's explicit confirmation and a backup or rollback plan.\n\n## Azure SQL Managed Instance notes\nConnection is over the instance's private endpoint or public endpoint on port 3342; permissions are server logins and roles as in SQL Server; some instance-level features differ from on-premises. For CDC and mirroring: the capture jobs need SQL Agent, and schema changes on captured tables must be coordinated (add columns to the capture instance deliberately). Say when you are unsure and point to the official documentation instead of guessing.\n"
 }
}
