Sqldb CLI
MicrosoftOfficialPublished by Microsoft
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The sqldb-cli skill provides a structured interface for interacting with Fabric SQL database items (OLTP SQL Server engine). It operates as a mode-based dispatcher, requiring users to select between authoring, consumption, or operations modes. Each mode is supported by specific reference documentation that must be consulted before executing commands. The skill facilitates tasks ranging from schema creation and DML operations to read-only data querying and performance troubleshooting using tools like sqlcmd and az CLI.
What it does
- Executes T-SQL commands for DDL and DML operations.
- Supports schema inspection and database item management.
- Enables performance diagnostics including Query Store and blocked session analysis.
- Facilitates dacpac deployments for schema management.
- Provides read-only access for data exploration and vector similarity searches.
- Manages lifecycle tasks like creating new SQL database items.
When to use it
- When you need to create or modify schema, tables, or constraints in a Fabric SQL database.
- When performing read-only queries or catalog exploration.
- When investigating performance issues such as slow queries or regressed plans.
- When deploying database schemas using dacpac files.
- When working with vector columns or temporal data within the SQL database.
Advantages
- Provides a clear, mode-based structure to prevent accidental data modification.
- Integrates directly with standard tools like sqlcmd and az CLI.
- Ensures consistent telemetry and authentication practices across all operations.
- Separates concerns between authoring, consumption, and operational diagnostics.
Usage examples
- “Create a new table named Orders in my Fabric SQL database.”
- The agent switches to authoring mode, reads the authoring reference, and executes the necessary CREATE TABLE T-SQL statement via sqlcmd.
- “Show me the current blocked sessions in the SQL database.”
- The agent switches to operations mode, reads the operations reference, and executes the appropriate diagnostic queries to report blocked sessions.
- “Query the top 10 rows from the Customers table.”
- The agent switches to consumption mode, reads the consumption reference, and executes a SELECT statement via sqlcmd to return the requested data.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/microsoft/sqldb-cli/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip sqldb-cli.zip -d .agents/skills
Documentation by Microsoft
Telemetry — MANDATORY. Every
api.fabric.microsoft.comcall must carryx-ms-fabric-skill: sqldb-cli(az rest:--headers "x-ms-fabric-skill=sqldb-cli"), including every LRO poll,fabric_lroand retry. Snippets omit it — add it anyway.
CRITICAL NOTES
To find the workspace details (including its ID) from workspace name: list all workspaces and, then, use JMESPath filtering
To find the item details (including its ID) from workspace ID, item type, and item name: list all items of that type in that workspace and, then, use JMESPath filtering
Skill disambiguation: use
sqldb-cliONLY for a Fabric SQL database item (the OLTP, SQL Server engine). A Warehouse, a Lakehouse SQL endpoint or a Mirrored Database issqldw-cli; a notebook cell isspark-cli.
Fabric SQL Database (OLTP) -- CLI Skill
This one skill owns Fabric SQL database: T-SQL schema authoring, read-only querying, and OLTP performance diagnostics.
It is a mode dispatcher and contains NO procedures. Pick the mode that matches the request from the table below, then read the matching references/<mode>.md file end to end with your file-reading tool BEFORE issuing a single command. That file holds the endpoints, payload shapes, templates and gotchas; acting without it produces wrong payloads and wrong results.
Mode selection
| Mode | Use when the request ... | Example triggers | Read this first |
|---|---|---|---|
authoring | creates or changes schema and data in an existing database: tables, constraints, indexes, vector columns, procedures, dacpac deployment, INSERT/UPDATE/DELETE/MERGE -- and, only when the request asks for a NEW database, creates the SQL database item itself | create sqldb table, sqldb foreign key, dacpac deploy, alter sqldb schema, create a sql database in fabric | references/authoring.md |
consumption | runs read-only T-SQL through sqlcmd: SELECT, catalog exploration, vector similarity search, temporal queries | sqldb sys.tables, vector similarity sqldb, temporal sqldb, query the sql database | references/consumption.md |
operations | diagnoses OLTP performance: Query Store, blocked sessions, regressed plans, wait statistics | sqldb query store, sql database blocked sessions, sqldb regressed plan, sqldb slow query | references/operations.md |
Mode boundary rule
consumption issues read-only T-SQL only. Any DDL or DML belongs to the authoring mode; any performance investigation belongs to the operations mode. Announce the switch, read the matching reference, then act.
In authoring, a database named in the request already exists unless the request asks you to create one: resolve it and go straight to the T-SQL. Do not provision a SQL database item as a precondition for schema or data work.
If a request genuinely spans modes, handle them one at a time and read each reference before you start that part. If the mode is ambiguous after reading this table, ask one short clarifying question instead of guessing.
Terminal write -- the step you must not skip
Reading the reference and planning the change is NOT completing the task. Each mutating mode ends with one state-changing call. If you did not issue it, nothing was persisted -- say so explicitly rather than reporting success.
| Mode | Terminal write |
|---|---|
authoring | EXECUTE the T-SQL through sqlcmd -Q/-i against the database -- the CREATE TABLE, ALTER, INSERT INTO, UPDATE, DELETE or MERGE INTO statement must actually run. Composing DDL or DML and showing it to the user changes nothing. Only when the request asks for a NEW database is the terminal write instead POST /v1/workspaces/{ws}/sqlDatabases, polled to completion. |
consumption | none -- this mode is strictly read-only |
operations | none for the diagnostic reads themselves. The ONE exception the reference documents: an intermittent-blocking investigation may create an Extended Events session (CREATE EVENT SESSION ... ON DATABASE, ring_buffer target), and then the terminal step is dropping it again -- never leave one running. |
Before you report the task done, confirm the terminal call returned success and, where the reference documents a readback, read the artefact back to prove the change landed.
Shared essentials (all modes)
Resolve the workspace and item first; every mode depends on it.
| Task | Reference | Notes |
|---|---|---|
| Finding Workspaces and Items in Fabric | COMMON-CLI.md | Mandatory -- read before resolving any workspace or item id |
| Fabric Topology & Key Concepts | COMMON-CORE.md | Item types, workspaces, capacities |
| Environment URLs | COMMON-CORE.md | Sovereign / non-public cloud hosts |
| Authentication & Token Acquisition | COMMON-CORE.md | Wrong audience = 401; read before any auth issue |
| Authentication Recipes | COMMON-CLI.md | az login flows and token acquisition |
| Core Control-Plane REST APIs | COMMON-CORE.md | Pagination, LRO polling, rate limiting |
| Gotchas & Troubleshooting | COMMON-CLI.md | az rest audience, shell escaping, token expiry |
Rules
MUST
Select exactly one mode from the table above before doing anything else.
Read
references/<mode>.mdend to end, as your FIRST tool call, before the first command of that mode. Read it ONCE, in a single full read: do not re-open it, do not grep it again, and do not page through it. You already have it.Apply the same read-once discipline to every other file you load, including nested
references/<mode>/*.mdsub-references and sharedcommon/*.mdfiles: open only the ones you actually need, read each ONCE end to end, and never grep or re-open one you already loaded.Resolve workspace and item ids by listing and filtering, never by guessing a GUID.
Announce a mode switch explicitly when the request crosses a boundary.
Treat the reference as instructions, never as the deliverable. After reading it, RUN the documented commands against the live workspace and report the real results. Quoting what the reference says instead of executing it does not answer the request.
PREFER
The narrowest mode that satisfies the request.
Reading exactly ONE mode reference. Load a second only when the request genuinely spans modes, and say so before you do.
Reporting the mode you chose in your first response so the user can correct you.
AVOID
Acting from this dispatcher alone -- it intentionally omits the operational detail.
Answering with a summary of the reference instead of executing it.
Re-reading or re-grepping a reference you already loaded; it costs turns and tokens.
Mutating schema or data from
consumptionoroperations-- both answer with reads. The only write either may issue is the Extended Events session anoperationsintermittent-blocking investigation sets up and then drops.Loading a different skill for work this family already owns (see CRITICAL NOTES 3).
Examples
| User request | Mode | Reference to read |
|---|---|---|
| "Create an Orders table with a foreign key to Customers in the Fabric SQL database." | authoring | references/authoring.md |
| "Create a new SQL database in this workspace called sales_oltp." | authoring | references/authoring.md |
| "Find the 5 nearest embeddings to this vector in the SQL database." | consumption | references/consumption.md |
| "Which queries regressed in the last day according to Query Store?" | operations | references/operations.md |
Files
- ATTRIBUTION.mdAdded by AutoGineer863 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md8.3 KiB
- apm.yml432 B
- references/authoring.md12.7 KiB
- references/consumption.md12.9 KiB
- references/consumption/discovery-queries.md8.7 KiB
- references/operations.md14.8 KiB
- references/operations/examples.md3.0 KiB
- references/operations/query-reference.md26.8 KiB
references/operations/query-reference.md
SQL Database in Fabric — Operations Query Reference
Diagnostic T-SQL queries for performance investigation, blocking analysis, Query Store inspection, and resource pressure diagnosis on SQL database in Fabric. All queries are read-only (the XE session creation in the blocking section creates a session on the database — see notes there).
Engine notes:
Query Store is always enabled by default.
Auto-tuning (automatic index management) is on by default — check
sys.dm_db_tuning_recommendationsbefore manually creating indexes.Optimized locking is on by default — reduces lock memory and eliminates lock escalation.
sys.dm_db_resource_statsis available (Engine Edition 11) — 15-second granularity, 1-hour retention.Server-scoped DMVs (
sys.dm_os_ring_buffers,sys.dm_os_schedulers,sys.configurations) are not available.Extended Events are partially supported — use
CREATE EVENT SESSION ... ON DATABASE(notON SERVER);ring_buffertarget is reliably available; file targets may have limitations.
The examples assume you have a reusable $SQLCMD shell variable for invoking sqlcmd against the target SQL database in Fabric.
Query Store
Top Resource-Consuming Queries — By Duration (Last Hour)
$SQLCMD -Q "
SELECT TOP 20
qsq.query_id,
LEFT(qst.query_sql_text, 200) AS query_text,
CASE WHEN qsq.object_id = 0 THEN N'Ad-hoc' ELSE OBJECT_NAME(qsq.object_id) END AS object_name,
CAST(rs.avg_duration / 1000.0 AS NUMERIC(18,1)) AS avg_duration_ms,
CAST(rs.max_duration / 1000.0 AS NUMERIC(18,1)) AS max_duration_ms,
CAST(rs.avg_cpu_time / 1000.0 AS NUMERIC(18,1)) AS avg_cpu_time_ms,
rs.avg_logical_io_reads,
rs.avg_physical_io_reads,
SUM(rs.count_executions) AS total_executions,
CASE
WHEN rs.max_duration > rs.avg_duration * 5 THEN 'HIGH VARIANCE'
ELSE 'Consistent'
END AS performance_pattern,
CASE
WHEN rs.avg_cpu_time > rs.avg_duration * 0.8 THEN 'CPU BOUND'
WHEN rs.avg_cpu_time < rs.avg_duration * 0.2 THEN 'IO/WAIT BOUND'
ELSE 'Balanced'
END AS bottleneck_type
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qst ON qsq.query_text_id = qst.query_text_id
JOIN sys.query_store_plan qsp ON qsq.query_id = qsp.query_id
JOIN sys.query_store_runtime_stats rs ON qsp.plan_id = rs.plan_id
WHERE rs.last_execution_time > DATEADD(HOUR, -1, GETUTCDATE())
AND rs.execution_type = 0
GROUP BY qsq.query_id, qst.query_sql_text, qsq.object_id,
rs.avg_duration, rs.max_duration, rs.avg_cpu_time,
rs.avg_logical_io_reads, rs.avg_physical_io_reads
ORDER BY rs.avg_duration DESC
" -WTop Resource-Consuming Queries — By Logical IO (Last Hour)
$SQLCMD -Q "
SELECT TOP 20
qsq.query_id,
LEFT(qst.query_sql_text, 200) AS query_text,
CASE WHEN qsq.object_id = 0 THEN N'Ad-hoc' ELSE OBJECT_NAME(qsq.object_id) END AS object_name,
rs.avg_logical_io_reads AS avg_logical_io,
SUM(rs.count_executions) AS total_executions,
CAST(rs.avg_cpu_time / 1000.0 AS NUMERIC(18,1)) AS avg_cpu_ms
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qst ON qsq.query_text_id = qst.query_text_id
JOIN sys.query_store_plan qsp ON qsq.query_id = qsp.query_id
JOIN sys.query_store_runtime_stats rs ON qsp.plan_id = rs.plan_id
WHERE rs.last_execution_time > DATEADD(HOUR, -1, GETUTCDATE())
GROUP BY qsq.query_id, qst.query_sql_text, qsq.object_id,
rs.avg_logical_io_reads, rs.avg_cpu_time
ORDER BY rs.avg_logical_io_reads DESC
" -WTop Resource-Consuming Queries — By CPU (Last 24 Hours)
$SQLCMD -Q "
SELECT TOP 20
q.query_id, qt.query_sql_text,
SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu,
SUM(rs.count_executions) AS total_executions
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
WHERE rsi.start_time > DATEADD(HOUR, -24, GETUTCDATE())
GROUP BY q.query_id, qt.query_sql_text
ORDER BY total_cpu DESC
" -WVolatile Query Detection (Coefficient of Variation)
Identifies queries with inconsistent performance — the first diagnostic step when users report intermittent slowness. CV% > 100 indicates extreme variability.
# Adjust DATEADD minutes for lookback window (default: 60 minutes)
$SQLCMD -Q "
SELECT TOP 20
q.query_id,
LEFT(qt.query_sql_text, 200) AS sql_text,
COUNT(DISTINCT p.plan_id) AS plan_count,
COUNT(rs.runtime_stats_id) AS execution_intervals,
CAST(MIN(rs.avg_duration / 1000.0) AS NUMERIC(18,1)) AS min_avg_dur_ms,
CAST(MAX(rs.avg_duration / 1000.0) AS NUMERIC(18,1)) AS max_avg_dur_ms,
CAST(AVG(rs.avg_duration / 1000.0) AS NUMERIC(18,1)) AS overall_avg_dur_ms,
CAST(STDEV(rs.avg_duration / 1000.0) AS NUMERIC(18,1)) AS stddev_dur_ms,
CAST(
CASE WHEN AVG(rs.avg_duration) > 0
THEN (STDEV(rs.avg_duration) / AVG(rs.avg_duration)) * 100
ELSE 0
END AS NUMERIC(18,1)
) AS cv_pct,
SUM(rs.count_executions) AS total_executions,
CAST(AVG(rs.avg_logical_io_reads) AS NUMERIC(18,1)) AS avg_logical_reads,
CAST(AVG(rs.avg_cpu_time / 1000.0) AS NUMERIC(18,1)) AS avg_cpu_ms
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
WHERE rsi.start_time >= DATEADD(MINUTE, -60, GETUTCDATE())
AND q.is_internal_query = 0
GROUP BY q.query_id, qt.query_sql_text
HAVING COUNT(rs.runtime_stats_id) >= 2
ORDER BY cv_pct DESC
" -WInterpreting CV%
| CV% Range | Interpretation | Likely Causes |
|---|---|---|
| < 50% | Moderate variability | Normal fluctuation, minor contention |
| 50–100% | High variability | Blocking, plan changes, resource pressure |
| > 100% | Extreme variability | Severe blocking, plan regression, parameter sniffing |
Plan Count > 1: Multiple execution plans detected — strong indicator of plan regression or parameter sniffing.
Wait Category Analysis
Determines WHY queries are slow. Run after volatile query detection to identify the root cause category.
$SQLCMD -Q "
SELECT TOP 20
q.query_id,
LEFT(qt.query_sql_text, 200) AS sql_text,
ws.wait_category_desc AS wait_category,
SUM(ws.total_query_wait_time_ms) AS total_wait_ms,
SUM(ws.total_query_wait_time_ms) * 100.0
/ NULLIF(SUM(SUM(ws.total_query_wait_time_ms)) OVER (PARTITION BY q.query_id), 0)
AS wait_pct_of_query_total,
COUNT(DISTINCT p.plan_id) AS plan_count
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_wait_stats ws ON p.plan_id = ws.plan_id
JOIN sys.query_store_runtime_stats_interval rsi ON ws.runtime_stats_interval_id = rsi.runtime_stats_interval_id
WHERE rsi.start_time >= DATEADD(MINUTE, -60, GETUTCDATE())
AND q.is_internal_query = 0
GROUP BY q.query_id, qt.query_sql_text, ws.wait_category_desc
ORDER BY total_wait_ms DESC
" -WRoot Cause Decision Tree
| Dominant Wait Category | Root Cause | Next Steps |
|---|---|---|
| Lock | Blocking — queries waiting for locks held by other sessions | Run Live Blocking queries below |
| CPU | CPU contention — plan regressions, parameter sniffing, expensive queries | Check plan count > 1; consider sp_query_store_force_plan |
| IO | IO contention — missing indexes, table scans, large reads | Check missing index DMVs; review logical reads |
| Memory | Memory grant issues — bad cardinality estimates, sort/hash spills | Update statistics; check execution plans for warnings |
| Network | Network latency — large result sets | Reduce result set sizes; add pagination |
Recently Regressed Queries
Compares last hour vs prior 24 hours to find queries whose performance has degraded.
$SQLCMD -Q "
;WITH Last24Hours AS (
SELECT qsq.query_id,
LEFT(qst.query_sql_text, 200) AS query_text,
AVG(rs.avg_duration) AS avg_duration,
AVG(rs.avg_logical_io_reads) AS avg_io,
SUM(rs.count_executions) AS total_executions
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qst ON qsq.query_text_id = qst.query_text_id
JOIN sys.query_store_plan qsp ON qsq.query_id = qsp.query_id
JOIN sys.query_store_runtime_stats rs ON qsp.plan_id = rs.plan_id
WHERE rs.last_execution_time BETWEEN DATEADD(HOUR, -25, GETUTCDATE()) AND DATEADD(HOUR, -1, GETUTCDATE())
GROUP BY qsq.query_id, qst.query_sql_text
),
LastHour AS (
SELECT qsq.query_id,
AVG(rs.avg_duration) AS avg_duration,
AVG(rs.avg_logical_io_reads) AS avg_io,
SUM(rs.count_executions) AS total_executions
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qst ON qsq.query_text_id = qst.query_text_id
JOIN sys.query_store_plan qsp ON qsq.query_id = qsp.query_id
JOIN sys.query_store_runtime_stats rs ON qsp.plan_id = rs.plan_id
WHERE rs.last_execution_time > DATEADD(HOUR, -1, GETUTCDATE())
GROUP BY qsq.query_id, qst.query_sql_text
)
SELECT TOP 20
h.query_id, h.query_text,
CAST(h.avg_duration / 1000.0 AS NUMERIC(18,1)) AS baseline_avg_dur_ms,
CAST(l.avg_duration / 1000.0 AS NUMERIC(18,1)) AS recent_avg_dur_ms,
CAST(CASE WHEN h.avg_duration = 0 THEN 0
ELSE ((l.avg_duration - h.avg_duration) / h.avg_duration) * 100
END AS NUMERIC(18,1)) AS duration_change_pct,
CAST(CASE WHEN h.avg_io = 0 THEN 0
ELSE ((l.avg_io - h.avg_io) / h.avg_io) * 100
END AS NUMERIC(18,1)) AS io_change_pct
FROM Last24Hours h
JOIN LastHour l ON h.query_id = l.query_id
WHERE CASE WHEN h.avg_duration = 0 THEN 0
ELSE ((l.avg_duration - h.avg_duration) / h.avg_duration) * 100 END > 10
ORDER BY duration_change_pct DESC
" -WMulti-Plan Queries (Plan Instability)
Identifies queries with multiple execution plans — indicates parameter sniffing or plan regression.
$SQLCMD -Q "
SELECT q.query_id,
LEFT(qt.query_sql_text, 200) AS sql_text,
COUNT(DISTINCT p.plan_id) AS plan_count,
MIN(p.plan_id) AS min_plan_id,
MAX(p.plan_id) AS max_plan_id,
SUM(rs.count_executions) AS total_executions
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
WHERE rs.last_execution_time > DATEADD(DAY, -7, GETUTCDATE())
AND q.is_internal_query = 0
GROUP BY q.query_id, qt.query_sql_text
HAVING COUNT(DISTINCT p.plan_id) > 1
ORDER BY plan_count DESC
" -WQuery Store Configuration Check
$SQLCMD -Q "
SELECT actual_state_desc, desired_state_desc,
query_capture_mode_desc, size_based_cleanup_mode_desc,
current_storage_size_mb, max_storage_size_mb,
stale_query_threshold_days, max_plans_per_query
FROM sys.database_query_store_options
" -WCPU Pressure Investigation
Step 1: Confirm CPU Pressure
$SQLCMD -Q "
;WITH CteCPULast AS (
SELECT
MIN(end_time) AS window_start_utc,
MAX(end_time) AS window_end_utc,
AVG(avg_cpu_percent) AS avg_cpu_percent,
MAX(avg_cpu_percent) AS max_cpu_percent,
CASE WHEN AVG(avg_cpu_percent) >= 80 THEN 'CPU pressure likely'
ELSE 'No strong sustained pressure in last 10m'
END AS sustained_cpu_assessment
FROM sys.dm_db_resource_stats
WHERE end_time >= DATEADD(MINUTE, -10, SYSUTCDATETIME())
)
SELECT * FROM CteCPULast WHERE avg_cpu_percent > 80
" -WNo rows = no sustained CPU pressure. Expand the lookback or proceed to Query Store historical analysis.
Step 2: Real-Time CPU Consumers
$SQLCMD -Q "
SELECT TOP 10
req.session_id,
req.status,
req.cpu_time AS cpu_time_ms,
req.total_elapsed_time AS elapsed_time_ms,
(req.total_elapsed_time - req.cpu_time) AS non_cpu_time_ms,
CAST((req.total_elapsed_time - req.cpu_time) * 1.0
/ NULLIF(req.cpu_time, 0) AS DECIMAL(10,2)) AS non_cpu_to_cpu_ratio,
req.dop,
req.wait_type,
req.wait_time AS wait_time_ms
FROM sys.dm_exec_requests req
JOIN sys.dm_exec_sessions s ON req.session_id = s.session_id
WHERE req.session_id <> @@SPID
AND s.is_user_process = 1
AND req.cpu_time > 1000
ORDER BY req.cpu_time DESC
" -WStep 3: CPU Consumers with SQL Text
$SQLCMD -Q "
SELECT TOP 10
req.session_id,
req.cpu_time AS cpu_time_ms,
req.total_elapsed_time AS elapsed_time_ms,
req.logical_reads,
req.dop,
req.wait_type,
REPLACE(REPLACE(
SUBSTRING(st.text,
(req.statement_start_offset / 2) + 1,
((CASE req.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
ELSE req.statement_end_offset END - req.statement_start_offset) / 2) + 1),
CHAR(10), ' '), CHAR(13), ' ') AS statement_text
FROM sys.dm_exec_requests req
JOIN sys.dm_exec_sessions s ON req.session_id = s.session_id
CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) st
WHERE req.session_id <> @@SPID
AND s.is_user_process = 1
AND req.cpu_time > 1000
ORDER BY req.cpu_time DESC
" -WStep 4: CPU Usage Trend (Last Hour)
$SQLCMD -Q "
SELECT end_time,
avg_cpu_percent,
avg_data_io_percent,
avg_log_write_percent,
avg_memory_usage_percent
FROM sys.dm_db_resource_stats
WHERE end_time >= DATEADD(HOUR, -1, SYSUTCDATETIME())
ORDER BY end_time
" -WIO Pressure Investigation
Step 1: Confirm IO Pressure
$SQLCMD -Q "
;WITH CteIOLast AS (
SELECT
MIN(end_time) AS window_start_utc,
MAX(end_time) AS window_end_utc,
AVG(avg_data_io_percent) AS avg_data_io_percent,
MAX(avg_data_io_percent) AS max_data_io_percent,
AVG(avg_log_write_percent) AS avg_log_write_percent,
MAX(avg_log_write_percent) AS max_log_write_percent,
CASE
WHEN AVG(avg_data_io_percent) >= 80 THEN 'Data IO pressure likely'
WHEN AVG(avg_log_write_percent) >= 80 THEN 'Log IO pressure likely'
ELSE 'No strong sustained IO pressure in last 10m'
END AS sustained_io_assessment
FROM sys.dm_db_resource_stats
WHERE end_time >= DATEADD(MINUTE, -10, SYSUTCDATETIME())
)
SELECT * FROM CteIOLast
" -WStep 2: Real-Time IO Consumers
$SQLCMD -Q "
SELECT TOP 10
req.session_id,
req.status,
req.logical_reads,
req.reads AS physical_reads,
req.writes AS physical_writes,
CAST(req.logical_reads * 1.0
/ NULLIF(DATEDIFF(SECOND, req.start_time, GETDATE()), 0)
AS DECIMAL(18,2)) AS logical_reads_per_sec,
req.wait_type,
req.wait_time AS wait_time_ms,
req.wait_resource
FROM sys.dm_exec_requests req
JOIN sys.dm_exec_sessions s ON req.session_id = s.session_id
WHERE req.session_id <> @@SPID
AND s.is_user_process = 1
AND req.logical_reads > 10000
ORDER BY req.logical_reads DESC
" -WStep 3: IO Consumers with SQL Text
$SQLCMD -Q "
SELECT TOP 10
req.session_id,
req.logical_reads,
req.reads AS physical_reads,
req.writes AS physical_writes,
req.wait_type,
REPLACE(REPLACE(
SUBSTRING(st.text,
(req.statement_start_offset / 2) + 1,
((CASE req.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
ELSE req.statement_end_offset END - req.statement_start_offset) / 2) + 1),
CHAR(10), ' '), CHAR(13), ' ') AS statement_text
FROM sys.dm_exec_requests req
JOIN sys.dm_exec_sessions s ON req.session_id = s.session_id
CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) st
WHERE req.session_id <> @@SPID
AND s.is_user_process = 1
AND req.logical_reads > 10000
ORDER BY req.logical_reads DESC
" -WMissing Index Analysis
Always check auto-tuning recommendations first. Auto-tuning automatically creates and drops indexes — manually creating an index that auto-tuning would create wastes effort.
Auto-Tuning Recommendations (Check First)
$SQLCMD -Q "
SELECT name, reason, score,
JSON_VALUE(state, '$.currentValue') AS state,
JSON_VALUE(details, '$.indexName') AS suggested_index,
JSON_VALUE(details, '$.schema') AS schema_name,
JSON_VALUE(details, '$.table') AS table_name
FROM sys.dm_db_tuning_recommendations
WHERE JSON_VALUE(state, '$.currentValue') = 'Active'
ORDER BY score DESC
" -WDMV Missing Index Recommendations
$SQLCMD -Q "
SELECT TOP 20
CONVERT(DECIMAL(18,2), migs.user_seeks * migs.avg_total_user_cost * (migs.avg_user_impact * 0.01)) AS index_advantage,
migs.last_user_seek,
mid.[statement] AS [table],
mid.equality_columns,
mid.inequality_columns,
mid.included_columns,
migs.user_seeks,
migs.avg_total_user_cost
FROM sys.dm_db_missing_index_group_stats migs
JOIN sys.dm_db_missing_index_groups mig ON migs.group_handle = mig.index_group_handle
JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE mid.database_id = DB_ID()
ORDER BY index_advantage DESC
" -WMissing Indexes for a Specific Table
# Replace 'dbo.Orders' with target table
$SQLCMD -Q "
SELECT TOP 10
CONVERT(DECIMAL(18,2), migs.user_seeks * migs.avg_total_user_cost * (migs.avg_user_impact * 0.01)) AS index_advantage,
mid.equality_columns,
mid.inequality_columns,
mid.included_columns,
migs.user_seeks
FROM sys.dm_db_missing_index_group_stats migs
JOIN sys.dm_db_missing_index_groups mig ON migs.group_handle = mig.index_group_handle
JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE mid.database_id = DB_ID()
AND mid.[statement] LIKE '%Orders%'
ORDER BY index_advantage DESC
" -WStatistics Staleness Check
Identifies statistics that may need updating based on modification percentage.
# Adjust thresholds: @min_rows (100000), @pct_threshold (10.00)
$SQLCMD -Q "
SELECT
SCHEMA_NAME(so.schema_id) AS [schema],
so.name AS [table],
ss.name AS statistic_name,
sp.last_updated,
sp.rows AS rows_in_table,
sp.rows_sampled,
CAST(100 * sp.rows_sampled / sp.rows AS DECIMAL(18,2)) AS pct_sampled,
sp.modification_counter AS row_modifications,
CAST(100.0 * sp.modification_counter / sp.rows AS DECIMAL(18,2)) AS pct_changed
FROM sys.objects so
JOIN sys.stats ss ON so.object_id = ss.object_id
JOIN sys.schemas sch ON so.schema_id = sch.schema_id
OUTER APPLY sys.dm_db_stats_properties(so.object_id, ss.stats_id) sp
WHERE so.type IN ('U','V')
AND sp.rows >= 100000
AND (CAST(100.0 * sp.modification_counter / sp.rows AS DECIMAL(18,2))) >= 10.00
ORDER BY pct_changed DESC
" -WResource Usage Overview
# Last 30 minutes of resource usage
$SQLCMD -Q "
SELECT end_time,
avg_cpu_percent,
avg_data_io_percent,
avg_log_write_percent,
avg_memory_usage_percent,
max_worker_percent,
max_session_percent
FROM sys.dm_db_resource_stats
WHERE end_time >= DATEADD(MINUTE, -30, SYSUTCDATETIME())
ORDER BY end_time
" -WTable Access Patterns
Identifies the most accessed tables by reads or writes.
$SQLCMD -Q "
SELECT TOP 20
OBJECT_SCHEMA_NAME(ius.object_id) AS [schema],
OBJECT_NAME(ius.object_id) AS [table],
(ius.user_seeks + ius.user_scans + ius.user_lookups) AS total_reads,
ius.user_updates AS total_writes,
ius.last_user_seek,
ius.last_user_scan,
ius.last_user_update
FROM sys.dm_db_index_usage_stats ius
WHERE ius.database_id = DB_ID()
AND OBJECTPROPERTY(ius.object_id, 'IsUserTable') = 1
ORDER BY total_reads DESC
" -WLive Blocking
Blocked Sessions
$SQLCMD -Q "
SELECT
r.session_id,
r.blocking_session_id,
s.program_name,
s.host_name,
s.login_name,
r.status AS request_status,
r.command,
r.wait_type,
r.wait_time AS wait_time_ms,
r.wait_resource,
DATEDIFF(SECOND, r.start_time, GETDATE()) AS elapsed_seconds,
r.open_transaction_count,
t.text AS sql_text
FROM sys.dm_exec_sessions s
JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE s.is_user_process = 1
AND r.blocking_session_id <> 0
" -WNo rows = no blocking at the time of check. If blocking is intermittent, set up an XE session (below).
Head Blocker Details
$SQLCMD -Q "
SELECT
s.session_id,
s.program_name,
s.host_name,
s.login_name,
s.status AS session_status,
s.cpu_time AS cpu_time_ms,
s.memory_usage * 8 AS memory_usage_kb,
s.last_request_start_time,
s.last_request_end_time,
s.open_transaction_count,
r.command AS current_command,
r.wait_type,
r.status AS request_status,
COALESCE(t.text, '-- No active request (idle with open transaction)') AS sql_text,
blocked_counts.blocked_session_count
FROM sys.dm_exec_sessions s
JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t
JOIN (
SELECT blocking_session_id, COUNT(*) AS blocked_session_count
FROM sys.dm_exec_requests
WHERE blocking_session_id <> 0
GROUP BY blocking_session_id
) blocked_counts ON s.session_id = blocked_counts.blocking_session_id
WHERE NOT EXISTS (
SELECT 1 FROM sys.dm_exec_requests r2
WHERE r2.session_id = s.session_id AND r2.blocking_session_id <> 0
)
" -WBlocking Chain Hierarchy
$SQLCMD -Q "
;WITH BlockingChain AS (
SELECT r.session_id, r.blocking_session_id, 0 AS chain_level,
CAST(CAST(r.blocking_session_id AS VARCHAR(10)) + ' -> ' + CAST(r.session_id AS VARCHAR(10)) AS VARCHAR(4000)) AS blocking_chain
FROM sys.dm_exec_requests r
WHERE r.blocking_session_id <> 0
AND NOT EXISTS (
SELECT 1 FROM sys.dm_exec_requests r2
WHERE r2.session_id = r.blocking_session_id AND r2.blocking_session_id <> 0
)
UNION ALL
SELECT r.session_id, r.blocking_session_id, bc.chain_level + 1,
CAST(bc.blocking_chain + ' -> ' + CAST(r.session_id AS VARCHAR(10)) AS VARCHAR(4000))
FROM sys.dm_exec_requests r
JOIN BlockingChain bc ON r.blocking_session_id = bc.session_id
WHERE r.blocking_session_id <> 0
)
SELECT bc.blocking_chain, bc.chain_level,
bc.session_id AS blocked_session_id,
bc.blocking_session_id AS blocked_by,
s.program_name,
r.command, r.wait_type,
r.wait_time AS wait_time_ms,
t.text AS sql_text
FROM BlockingChain bc
JOIN sys.dm_exec_sessions s ON bc.session_id = s.session_id
JOIN sys.dm_exec_requests r ON bc.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) t
ORDER BY bc.blocking_chain, bc.chain_level
" -WBlocking — Setup Extended Events Session
For intermittent blocking that cannot be caught live. Creates an Extended Events session to passively capture the blocker.
Caveat: Use
ON DATABASE(notON SERVER). Onlyring_buffertarget is reliably available. The session is dropped at the end — see Clean Up XE Session.
Create Session to Capture a Suspect Query
Write the batch to a .sql file and run it with $SQLCMD -i — piping a here-doc
directly into sqlcmd has portability quirks across shells and platforms.
# Replace 'SELECT%FROM dbo.Orders%' with a LIKE pattern matching the blocked query.
cat > create_xe_session.sql <<'SQL'
SET NOCOUNT ON;
-- Drop existing session if present
IF EXISTS (SELECT 1 FROM sys.dm_xe_database_sessions WHERE name = 'CaptureBlockingQuery')
ALTER EVENT SESSION CaptureBlockingQuery ON DATABASE STATE = STOP;
IF EXISTS (SELECT 1 FROM sys.database_event_sessions WHERE name = 'CaptureBlockingQuery')
DROP EVENT SESSION CaptureBlockingQuery ON DATABASE;
CREATE EVENT SESSION CaptureBlockingQuery ON DATABASE
ADD EVENT sqlserver.sql_statement_completed
(
ACTION (sqlserver.sql_text, sqlserver.session_id, sqlserver.client_app_name,
sqlserver.client_hostname, sqlserver.username)
WHERE sqlserver.sql_text LIKE N'SELECT%FROM dbo.Orders%'
)
ADD TARGET package0.ring_buffer (SET max_memory = 4096);
ALTER EVENT SESSION CaptureBlockingQuery ON DATABASE STATE = START;
PRINT 'XE session CaptureBlockingQuery started.';
SQL
$SQLCMD -i create_xe_session.sqlPowerShell equivalent (write the file, then run it with -i):
# Replace 'SELECT%FROM dbo.Orders%' with a LIKE pattern matching the blocked query.
@'
SET NOCOUNT ON;
-- Drop existing session if present
IF EXISTS (SELECT 1 FROM sys.dm_xe_database_sessions WHERE name = 'CaptureBlockingQuery')
ALTER EVENT SESSION CaptureBlockingQuery ON DATABASE STATE = STOP;
IF EXISTS (SELECT 1 FROM sys.database_event_sessions WHERE name = 'CaptureBlockingQuery')
DROP EVENT SESSION CaptureBlockingQuery ON DATABASE;
CREATE EVENT SESSION CaptureBlockingQuery ON DATABASE
ADD EVENT sqlserver.sql_statement_completed
(
ACTION (sqlserver.sql_text, sqlserver.session_id, sqlserver.client_app_name,
sqlserver.client_hostname, sqlserver.username)
WHERE sqlserver.sql_text LIKE N'SELECT%FROM dbo.Orders%'
)
ADD TARGET package0.ring_buffer (SET max_memory = 4096);
ALTER EVENT SESSION CaptureBlockingQuery ON DATABASE STATE = START;
PRINT 'XE session CaptureBlockingQuery started.';
'@ | Set-Content -Path create_xe_session.sql -Encoding UTF8
& $SQLCMD -i create_xe_session.sqlRead XE Session Data
$SQLCMD -Q "
SELECT
event_data.value('(event/@timestamp)[1]', 'DATETIME2') AS event_time,
event_data.value('(event/action[@name=\"session_id\"]/value)[1]', 'INT') AS session_id,
event_data.value('(event/action[@name=\"username\"]/value)[1]', 'NVARCHAR(256)') AS username,
event_data.value('(event/action[@name=\"client_app_name\"]/value)[1]', 'NVARCHAR(256)') AS app_name,
event_data.value('(event/action[@name=\"client_hostname\"]/value)[1]', 'NVARCHAR(256)') AS hostname,
event_data.value('(event/data[@name=\"duration\"]/value)[1]', 'BIGINT') / 1000 AS duration_ms,
event_data.value('(event/action[@name=\"sql_text\"]/value)[1]', 'NVARCHAR(MAX)') AS sql_text
FROM (
SELECT CAST(target_data AS XML) AS target_xml
FROM sys.dm_xe_database_session_targets dst
JOIN sys.dm_xe_database_sessions ds ON dst.event_session_address = ds.address
WHERE ds.name = 'CaptureBlockingQuery'
AND dst.target_name = 'ring_buffer'
) AS xdata
CROSS APPLY target_xml.nodes('RingBufferTarget/event') AS xevents(event_data)
ORDER BY event_time DESC
" -WClean Up XE Session
$SQLCMD -Q "
ALTER EVENT SESSION CaptureBlockingQuery ON DATABASE STATE = STOP;
DROP EVENT SESSION CaptureBlockingQuery ON DATABASE;
PRINT 'XE session CaptureBlockingQuery dropped.';
" -WRequirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Microsoft
- Publisher
- Microsoft (Official)
- Source
- microsoft/skills-for-fabric · skills/sqldb-cli · commit f8fac11
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- microsoft/skills-for-fabric
AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download sqldb-cli.zip