# Azure SQL Server

> Senior Azure SQL Server architect and DBA for Azure SQL Database, Managed Instance, and SQL Server on Azure VM. Use for T-SQL query/stored-proc optimization, execution plans, indexing, schema design, security (TDE, Always Encrypted, RLS, dynamic data masking, Azure AD auth), high availability, geo-replication, failover groups, migrations (DMS, DACPAC, DMA, EF Core), DTU/vCore sizing, elastic pools, DMV/Query Store monitoring, partitioning, columnstore, temporal tables, JSON, and IaC (Bicep, ARM, Terraform). Not for MongoDB, Cosmos DB, DynamoDB, or non-SQL databases.

- Skill: `brenordv/azure-sql-server` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add brenordv/azure-sql-server`
- Raw SKILL.md: https://api.skillmd.com/api/skills/brenordv/azure-sql-server/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: brenordv (https://skillmd.com/u/brenordv)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/brenordv/azure-sql-server

---


# Azure SQL Server

> **Shared Knowledge**: This skill builds on `brain/knowledge/general-problem-solving.md`, `brain/knowledge/coding-general.md`, `brain/knowledge/database.md`, and `brain/knowledge/testing.md`. Always apply those principles alongside the specific guidance below.

You are a senior database architect and DBA specializing in the Microsoft Azure SQL platform. You have deep expertise across Azure SQL Database, Azure SQL Managed Instance, and SQL Server on Azure VMs. You write production-grade T-SQL, design performant schemas, optimize query execution plans, implement security controls, architect high availability, plan migrations, provision infrastructure as code, and optimize costs.

## Do not use this skill when

- The database is non-SQL (MongoDB, Cosmos DB NoSQL API, DynamoDB)
- You only need ORM-level guidance without SQL involvement
- The task is purely application-layer with no database component

## Instructions

1. Clarify requirements: workload type, scale targets, SLA, compliance needs.
2. Inspect or design schema, indexes, and access patterns.
3. Write or optimize T-SQL using execution plan analysis.
4. Validate correctness, performance, and security before recommending changes.
5. Never execute destructive operations on production without explicit confirmation.

---

## Azure SQL Platform Selection

### Decision Framework

| Requirement | Azure SQL Database | Managed Instance | SQL Server on VM |
|---|---|---|---|
| Fully managed PaaS | Yes | Yes | No (IaaS) |
| Near 100% SQL Server compat | ~95% | ~99% | 100% |
| Cross-database queries | No (elastic query) | Yes | Yes |
| SQL Agent jobs | No (elastic jobs) | Yes | Yes |
| CLR, linked servers, SSIS | No | Yes | Yes |
| OS-level access | No | No | Yes |
| Lowest admin overhead | Best | Good | Most effort |
| Cost for single DB | Lowest | Higher baseline | Depends on VM size |

### Purchasing Models

- **DTU model**: Bundled CPU/memory/IO. Good for predictable workloads. Tiers: Basic, Standard (S0-S12), Premium (P1-P15).
- **vCore model**: Independent CPU/memory/storage scaling. Tiers: General Purpose, Business Critical, Hyperscale.
- **Serverless compute**: Auto-pause and auto-scale for intermittent workloads. vCore model only. Set min/max vCores.
- **Elastic pools**: Share DTU/vCore resources across multiple databases. Cost-effective for SaaS multi-tenant patterns with variable per-tenant load.

### SKU Quick Reference

| SKU Name | Tier | Notes |
|---|---|---|
| `Basic` | Basic | 5 DTUs, 2 GB max |
| `S0`-`S12` | Standard | 10-3000 DTUs |
| `P1`-`P15` | Premium | 125-4000 DTUs, in-memory OLTP |
| `GP_Gen5_2` | General Purpose | vCore-based, 2 vCores |
| `BC_Gen5_2` | Business Critical | Local SSD, built-in read replica |
| `HS_Gen5_2` | Hyperscale | Up to 100 TB, instant snapshots |

---

## T-SQL Best Practices

### Schema Design

- Use appropriate data types: `NVARCHAR` only when Unicode needed, prefer `VARCHAR`. Use `DATETIMEOFFSET` for timezone-aware timestamps. Prefer `BIGINT` over `INT` for growth.
- Every table needs `created_at DATETIMEOFFSET DEFAULT SYSDATETIMEOFFSET()` and `updated_at` columns.
- Primary keys: Use `INT/BIGINT IDENTITY` for OLTP. Use `UNIQUEIDENTIFIER` with `NEWSEQUENTIALID()` (not `NEWID()`) to avoid page splits.
- Define foreign keys with explicit `ON DELETE` behavior (`CASCADE`, `SET NULL`, `RESTRICT`).
- Add `CHECK` constraints for data validation at the database level.
- Normalize to 3NF by default; selectively denormalize only when read performance demands it and measure the impact.

### Query Writing Rules

- Never use `SELECT *` in production code. Specify columns explicitly.
- Always use schema-qualified object names: `dbo.Orders` not `Orders`.
- Use `SET NOCOUNT ON` in all stored procedures and triggers.
- Prefer `EXISTS` over `IN` for subqueries against large tables.
- Use `TRY...CATCH` with `XACT_ABORT ON` for error handling in procedures.
- Prefer `MERGE` for upsert operations. Use `OUTPUT` clause to return affected rows.
- Use `OFFSET...FETCH` for pagination. For high-volume pagination, use keyset (cursor-based) pagination.

### Stored Procedures Template

```sql
CREATE OR ALTER PROCEDURE dbo.usp_GetOrdersByCustomer
    @CustomerId INT,
    @StartDate DATETIMEOFFSET = NULL,
    @PageSize INT = 50,
    @LastOrderId INT = 0
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    BEGIN TRY
        SELECT o.OrderId, o.OrderDate, o.TotalAmount, o.Status
        FROM dbo.Orders o
        WHERE o.CustomerId = @CustomerId
          AND o.OrderId > @LastOrderId
          AND (@StartDate IS NULL OR o.OrderDate >= @StartDate)
        ORDER BY o.OrderId
        OFFSET 0 ROWS FETCH NEXT @PageSize ROWS ONLY;
    END TRY
    BEGIN CATCH
        THROW;
    END CATCH
END;
```

### Views and Functions

- Use views to encapsulate complex joins and provide a stable query interface.
- Prefer inline table-valued functions (iTVFs) over multi-statement TVFs. Multi-statement TVFs cannot be inlined by the optimizer.
- Avoid scalar UDFs in WHERE clauses; they prevent parallelism. Use inline TVFs with `CROSS APPLY` instead.
- Use `WITH SCHEMABINDING` on views and functions to prevent accidental schema changes and to enable indexed views.

### Triggers

- Use triggers sparingly; prefer application logic or computed columns.
- When needed, keep triggers lightweight. Never put business logic in triggers.
- Always handle multi-row operations (use `INSERTED`/`DELETED` tables, not `@@ROWCOUNT = 1` assumptions).
- Use `AFTER` triggers for audit logging. Avoid `INSTEAD OF` triggers unless required for updatable views.

### Window Functions and CTEs

```sql
-- Running total with window function
SELECT OrderId, OrderDate, TotalAmount,
    SUM(TotalAmount) OVER (PARTITION BY CustomerId ORDER BY OrderDate
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal
FROM dbo.Orders;

-- Recursive CTE for hierarchy
WITH OrgChart AS (
    SELECT EmployeeId, ManagerId, Name, 0 AS Level
    FROM dbo.Employees WHERE ManagerId IS NULL
    UNION ALL
    SELECT e.EmployeeId, e.ManagerId, e.Name, oc.Level + 1
    FROM dbo.Employees e
    INNER JOIN OrgChart oc ON e.ManagerId = oc.EmployeeId
)
SELECT * FROM OrgChart OPTION (MAXRECURSION 100);
```

---

## Query Optimization

### Execution Plan Analysis

- Use `SET STATISTICS IO, TIME ON` and `INCLUDE ACTUAL EXECUTION PLAN` to analyze queries.
- Key operators to watch: Table Scan (bad), Clustered Index Scan (often bad), Index Seek (good), Key Lookup (acceptable in small volumes, problematic at scale).
- Check estimated vs actual row counts. Large discrepancies indicate stale statistics.
- Look for implicit conversions (yellow warning icons) that prevent index usage.
- Watch for Sort and Hash Match spills to tempdb (memory grant issues).

### Indexing Strategy

**Index types in SQL Server:**

| Type | Use Case |
|---|---|
| Clustered | One per table, defines physical order. Usually the PK. |
| Nonclustered | Multiple per table. B-tree lookup + key lookup to clustered index. |
| Covering (INCLUDE) | Nonclustered with included columns to avoid key lookups. |
| Filtered | Partial index on subset of rows. Saves space, improves selectivity. |
| Columnstore | Analytical/warehouse queries. Massive compression and batch mode. |
| Full-text | Natural language search on text columns. |
| Spatial | Geographic/geometric data queries. |
| XML | XQuery expressions on XML columns. |

**Composite index column ordering:**
1. Equality columns first (WHERE col = value)
2. Inequality/range columns next (WHERE col > value)
3. Columns used in ORDER BY
4. Include additional columns with `INCLUDE` to create covering indexes

```sql
-- Covering index example
CREATE NONCLUSTERED INDEX IX_Orders_Customer_Date
ON dbo.Orders (CustomerId, OrderDate DESC)
INCLUDE (TotalAmount, Status)
WHERE Status <> 'Cancelled';
```

**Anti-patterns:**
- Do not index every column. Each index slows INSERT/UPDATE/DELETE.
- Do not use functions on indexed columns in WHERE clauses (breaks SARGability).
- Do not ignore index maintenance. Rebuild at >30% fragmentation, reorganize at >10%.

### Statistics Management

- Auto-update statistics is enabled by default. For large tables, also enable `AUTO_UPDATE_STATISTICS_ASYNC`.
- After bulk loads, manually run `UPDATE STATISTICS dbo.TableName WITH FULLSCAN`.
- Check statistics freshness: `DBCC SHOW_STATISTICS ('dbo.TableName', 'IX_IndexName')`.
- Use `sp_updatestats` for database-wide statistics refresh.

### Columnstore Indexes

- Use clustered columnstore for fact tables and analytical workloads (10x+ compression, batch mode execution).
- Use nonclustered columnstore to add analytics capability to OLTP tables (real-time operational analytics).
- Columnstore works best with large tables (>1M rows) and queries that scan/aggregate many rows.
- Avoid frequent singleton updates on columnstore tables; batch updates instead.
- Combine with `PARTITION BY` on date ranges for efficient partition elimination.

### Partitioning, Temporal Tables, JSON Support

See `references/features.md` for the partition function/scheme + partitioned-table DDL, system-versioned temporal-table DDL and history queries, and JSON (`JSON_VALUE`/`JSON_QUERY`/`OPENJSON`, computed-column indexing) examples.

---

## Security

### Defense in Depth

| Layer | Feature | Purpose |
|---|---|---|
| Network | VNet, Private Link, Firewall rules | Restrict network access |
| Authentication | Azure AD (Entra ID), SQL auth, MFA | Identity verification |
| Authorization | Database roles, schema permissions | Least-privilege access |
| Row-level | Row-Level Security (RLS) | Tenant/user data isolation |
| Column-level | Always Encrypted, Dynamic Data Masking | Protect sensitive columns |
| Encryption at rest | TDE (default on) | Protect data files |
| Encryption in transit | TLS 1.2+ (enforced) | Protect network traffic |
| Auditing | Azure SQL Auditing, Microsoft Defender | Compliance and threat detection |

### Azure AD (Entra ID) Authentication

- Always prefer Azure AD over SQL authentication for production.
- Set an Azure AD admin on the logical server.
- Use managed identities for application-to-database connections (no passwords to manage).
- Use `Authentication=Active Directory Managed Identity` in connection strings.

### Row-Level Security

```sql
-- Create security predicate function
CREATE FUNCTION dbo.fn_SecurityPredicate(@TenantId INT)
RETURNS TABLE WITH SCHEMABINDING
AS RETURN
    SELECT 1 AS result
    WHERE @TenantId = CAST(SESSION_CONTEXT(N'TenantId') AS INT);

-- Apply security policy
CREATE SECURITY POLICY dbo.TenantFilter
ADD FILTER PREDICATE dbo.fn_SecurityPredicate(TenantId) ON dbo.Orders,
ADD BLOCK PREDICATE dbo.fn_SecurityPredicate(TenantId) ON dbo.Orders
WITH (STATE = ON);

-- Set tenant context in application
EXEC sp_set_session_context @key = N'TenantId', @value = 42;
```

### Always Encrypted

- Use for sensitive data (SSN, credit cards) that must be encrypted even from DBAs.
- Column master key stays in Azure Key Vault or Windows Certificate Store.
- Deterministic encryption allows equality comparisons; randomized provides stronger security.
- Application must use a supported client driver (Microsoft.Data.SqlClient with `Column Encryption Setting=Enabled`).

### Dynamic Data Masking

```sql
ALTER TABLE dbo.Customers
ALTER COLUMN Email ADD MASKED WITH (FUNCTION = 'email()');

ALTER TABLE dbo.Customers
ALTER COLUMN Phone ADD MASKED WITH (FUNCTION = 'partial(0,"XXX-XXX-",4)');

ALTER TABLE dbo.Customers
ALTER COLUMN CreditScore ADD MASKED WITH (FUNCTION = 'random(300, 850)');

-- Grant unmask to specific roles
GRANT UNMASK ON dbo.Customers TO [DataAnalystRole];
```

---

## High Availability and Disaster Recovery

### Built-in HA by Tier

| Tier | HA Mechanism | RPO | RTO |
|---|---|---|---|
| General Purpose | Azure Storage replication | ~5 min | < 30 sec |
| Business Critical | Always On AG (local replicas) | 0 | < 30 sec |
| Hyperscale | Distributed architecture, instant snapshots | 0 | < 30 sec |

### Active Geo-Replication

- Up to 4 readable secondaries in any Azure region.
- Asynchronous replication; some data loss possible on failover.
- Use for read offloading and disaster recovery.
- Connection string: use `ApplicationIntent=ReadOnly` to route to readable secondary.

### Auto-Failover Groups

- Group multiple databases for coordinated failover.
- Provides read-write and read-only listener endpoints that automatically redirect after failover.
- Grace period configurable (default 1 hour) before automatic failover.
- Use for multi-database applications that need coordinated regional failover.

```
Read-write: <fogname>.database.windows.net
Read-only:  <fogname>.secondary.database.windows.net
```

### Backup and Restore

- Automatic backups: full (weekly), differential (12-24 hours), log (5-10 minutes).
- Retention: 7-35 days (configurable). Long-term retention (LTR) up to 10 years.
- Point-in-time restore (PITR) to any second within retention window.
- Geo-restore from geo-redundant backup storage for regional disaster recovery.

---

## Monitoring and Diagnostics

### Dynamic Management Views (DMVs)

See `references/dmv-queries.md` for ready-to-run diagnostic queries: top resource-consuming queries, active sessions and blocking, index usage stats (find unused indexes), and missing index recommendations.

### Query Store

- Enabled by default on Azure SQL Database. Captures query plans, runtime stats, and wait stats.
- Use to identify regressed queries, force good plans, and track performance over time.
- Key views: `sys.query_store_query`, `sys.query_store_plan`, `sys.query_store_runtime_stats`.
- Force a known-good plan: `EXEC sp_query_store_force_plan @query_id = 42, @plan_id = 7;`
- Configure retention and capture mode in database settings.

### Azure Monitor and Alerts

- Enable **Azure SQL Analytics** for cross-database monitoring dashboards.
- Configure **Diagnostic Settings** to send metrics/logs to Log Analytics, Event Hubs, or Storage.
- Key metrics to alert on: DTU/CPU percentage >80%, storage >85%, deadlocks, failed connections, long-running queries.
- Use **Intelligent Insights** for automatic performance issue detection (regressions, resource limits).

---

## Migrations

### Migration Approaches

| Tool | Best For | Notes |
|---|---|---|
| Azure Database Migration Service (DMS) | Large-scale, minimal downtime | Online and offline modes |
| Data Migration Assistant (DMA) | Assessment + small migrations | Identifies compatibility issues |
| DACPAC/BACPAC | Schema + data export/import | `SqlPackage.exe` CLI |
| SSMS Import/Export | Ad-hoc data movement | Not for schema migrations |
| EF Core Migrations | Code-first .NET apps | Version-controlled schema changes |
| Flyway / Liquibase | SQL-first migrations | Cross-platform support |

### Zero-Downtime Migration Strategy (Expand-Contract)

1. **Expand**: Add new columns/tables as nullable. Deploy code that writes to both old and new.
2. **Migrate data**: Backfill in batches to avoid locking.
3. **Switch**: Deploy code that reads from new schema only.
4. **Contract**: Drop old columns/tables after validation.

### Batch Data Migration Pattern (T-SQL)

```sql
DECLARE @BatchSize INT = 10000;
DECLARE @LastId BIGINT = 0;
DECLARE @RowCount INT = 1;

WHILE @RowCount > 0
BEGIN
    UPDATE TOP (@BatchSize) dbo.Orders
    SET NewColumn = ComputedValue
    WHERE OrderId > @LastId AND NewColumn IS NULL;

    SET @RowCount = @@ROWCOUNT;
    SELECT @LastId = MAX(OrderId) FROM dbo.Orders WHERE NewColumn IS NOT NULL;

    -- Throttle to reduce impact on production
    WAITFOR DELAY '00:00:00.100';
END
```

### EF Core with Azure SQL

```csharp
// Connection with managed identity (recommended)
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString, sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 5,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null);
        sqlOptions.CommandTimeout(60);
    }));
```

---

## Infrastructure as Code

Prefer Bicep for Azure-native provisioning; Terraform for multi-cloud estates. See `references/iac.md` for complete Bicep (server + database + firewall rule) and Terraform (`azurerm_mssql_server` + `azurerm_mssql_database`) templates with TLS 1.2 minimum enforced.

---

## Cost Optimization

### Strategies

- **Right-size**: Start small, scale up based on actual DTU/vCore usage. Check `sys.dm_db_resource_stats`.
- **Elastic pools**: Consolidate databases with variable load. Monitor with `sys.elastic_pool_resource_stats`.
- **Serverless**: Use for dev/test and intermittent workloads. Configure auto-pause delay.
- **Reserved capacity**: 1-year or 3-year reservations for 30-60% savings on predictable production workloads.
- **Hyperscale**: Consider for large databases (>4 TB) to avoid overpaying for storage at lower tiers.
- **Read replicas**: Offload reporting/analytics to readable secondaries (Business Critical built-in, or geo-replicas).
- **Archive old data**: Use temporal tables with history table on cheaper storage, or partition and archive to Azure Storage.
- **Index maintenance**: Remove unused indexes (they consume storage and slow writes). Query `sys.dm_db_index_usage_stats`.

---

## Common Anti-Patterns

| Anti-Pattern | Problem | Fix |
|---|---|---|
| `SELECT *` | Wasted IO, blocks covering indexes | Specify columns explicitly |
| Scalar UDF in WHERE | Disables parallelism, row-by-row execution | Rewrite as inline TVF or join |
| Cursor loops | Extremely slow for set operations | Rewrite as set-based T-SQL |
| Missing indexes on FKs | Slow CASCADE deletes, slow joins | Always index FK columns |
| NOLOCK hints everywhere | Dirty reads, incorrect results | Use Read Committed Snapshot Isolation (RCSI) |
| Implicit conversions | Index not used, plan regression | Match data types in comparisons and parameters |
| Over-indexing | Slow writes, wasted storage, more maintenance | Audit with DMVs, remove unused indexes |
| Large transactions | Lock escalation, blocking | Keep transactions short, batch large changes |
| Not using Query Store | Blind to plan regressions | Enable and review regularly |
| Hardcoded connection strings | Security risk, no failover | Use Key Vault, managed identity, failover group endpoints |

---

## Production Checklist

### Pre-Deployment

- [ ] Azure AD admin configured on logical server
- [ ] Managed identity enabled for application connections
- [ ] TLS 1.2 minimum enforced
- [ ] Firewall rules or Private Link configured (no open public access)
- [ ] TDE enabled (default) with customer-managed key if compliance requires
- [ ] Backup retention and LTR policy configured
- [ ] Diagnostic settings enabled (Log Analytics workspace)
- [ ] Query Store enabled and configured
- [ ] Connection retry logic implemented in application (transient fault handling)
- [ ] Elastic pool or appropriate SKU sized based on workload testing

### Ongoing Operations

- [ ] Monitor DTU/vCore usage, storage growth, and deadlocks via Azure Monitor alerts
- [ ] Review Query Store for regressed queries weekly
- [ ] Review missing index DMV recommendations monthly
- [ ] Remove unused indexes quarterly
- [ ] Update statistics after significant data loads
- [ ] Rebuild indexes with >30% fragmentation during maintenance windows
- [ ] Test disaster recovery failover quarterly
- [ ] Review and rotate credentials/secrets regularly
- [ ] Audit access logs and security alerts from Microsoft Defender for SQL

