Introduction
Azure SQL Database is a fully managed PaaS service from Microsoft Azure, providing a highly scalable, secure, and serverless relational SQL Server database. Unlike an on-premises SQL Server instance, it automatically handles backups, patching, and high availability, so you can focus on your code.
Why use it in 2026? With the rise of AI and data-intensive apps, Azure SQL excels in auto-scaling (up to 100 TB), Hyperscale integration for OLTP/OLAP, and native TDE encryption. This intermediate tutorial guides you step by step: CLI deployment, firewall setup, table creation with indexes, optimized queries, and monitoring. By the end, you'll have a functional DB ready to connect to any app (Node.js, .NET, etc.).
Real value: Save 50-70% on costs vs. SQL on VMs thanks to serverless compute. Tested in the France Central region.
Prerequisites
- Active Azure account with a paid subscription (free credits insufficient for SQL).
- Azure CLI 2.65+ installed (
az --versionto check). - Basics of T-SQL and Azure administration (intermediate level).
- SQL tool like Azure Data Studio or sqlcmd to test queries.
- Environment variable
AZURE_SUBSCRIPTION_IDset (az account show --query id -o tsv).
Create the resource group
#!/bin/bash
RESOURCE_GROUP="rg-sql-tutorial-2026"
LOCATION="francecentral"
az group create \
--name $RESOURCE_GROUP \
--location $LOCATION \
--tags project=tutorial environment=dev
# Verify the creation
echo "Resource group créé : $(az group show --name $RESOURCE_GROUP --query 'name' -o tsv)"
# Variables for later steps
export RESOURCE_GROUP=$RESOURCE_GROUP
export LOCATION=$LOCATIONThis script creates a dedicated resource group, isolated for your tests. Use francecentral for EU data sovereignty. Tags make cost tracking easier via Azure Cost Management. Pitfall: Don't forget to export the vars for reuse.
Deploy the logical SQL server
A logical SQL server is the container for your databases. It handles authentication and firewall rules. Choose a strong admin password and enable Microsoft Entra ID auth for MFA.
Create the SQL server
#!/bin/bash
SQL_SERVER_NAME="sqlserver-tutorial-$(date +%s)"
ADMIN_LOGIN="sqladmin2026"
ADMIN_PASSWORD="MonP@ssw0rdTr3sS3cur1s2026!"
az sql server create \
--name $SQL_SERVER_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--admin-user $ADMIN_LOGIN \
--admin-password $ADMIN_PASSWORD \
--enable-public-network true \
--minimal-tls-version 1.2 \
--version 16.0
# Enable Microsoft Entra admin (optional, requires Entra ID)
# az sql server ad-admin create --server-name $SQL_SERVER_NAME --resource-group $RESOURCE_GROUP --display-name "azuread-admin" --object-id "your-object-id"
export SQL_SERVER_NAME
# Full server name for connection
echo "Serveur SQL : ${SQL_SERVER_NAME}.database.windows.net"Creates a SQL Server v16 (2022) with minimum TLS 1.2 for security. The name must be globally unique (timestamp added). Major pitfall: Password must be 8+ chars with uppercase/lowercase/digit/symbol. Export the var for later steps.
Create the serverless database
#!/bin/bash
DB_NAME="sqldb-tutorial"
az sql db create \
--resource-group $RESOURCE_GROUP \
--server $SQL_SERVER_NAME \
--name $DB_NAME \
--service-objective S0 \
--compute-model Serverless \
--min-capacity 0.5 \
--max-size 32GB \
--sample-name AdventureWorksLT \
--edition GeneralPurpose
export DB_NAME
# Verify
az sql db show --resource-group $RESOURCE_GROUP --server $SQL_SERVER_NAME --name $DB_NAME --query '{name:name, status:status}' -o tableDeploys a GeneralPurpose serverless DB (auto-pauses at 0.5 vCore). AdventureWorksLT populates it with sample data for immediate testing. Scaling: min 0.5/max 4 vCore auto. Pitfall: Serverless pauses after 1 hour of inactivity, stopping billing.
Configure firewall and security
Access is blocked by default. Open it for your IP or use Managed Identity for apps. Enable Auditing for compliance.
Add firewall rule
#!/bin/bash
# Rule for your current IP (found via curl ifconfig.me)
CLIENT_IP=$(curl -s ifconfig.me)
az sql server firewall-rule create \
--resource-group $RESOURCE_GROUP \
--server $SQL_SERVER_NAME \
--name AllowClientIP \
--start-ip-address $CLIENT_IP \
--end-ip-address $CLIENT_IP
# Rule for Azure services (e.g., App Service)
az sql server firewall-rule create \
--resource-group $RESOURCE_GROUP \
--server $SQL_SERVER_NAME \
--name AllowAzureServices \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
# Enable auditing
az sql server update \
--resource-group $RESOURCE_GROUP \
--name $SQL_SERVER_NAME \
--set securityPolicy.state=Enabled securityPolicy.auditingPolicy.auditActionsAndGroups='SCHEMA_OBJECT_CHANGE_GROUP'Opens access for client IP and all Azure services. Security: Always use precise rules; delete after testing (az sql server firewall-rule delete). Auditing logs object changes to Log Analytics.
Create table, index, and data
-- Connect via sqlcmd or Azure Data Studio:
-- sqlcmd -S ${SQL_SERVER_NAME}.database.windows.net -d ${DB_NAME} -U ${ADMIN_LOGIN} -P ${ADMIN_PASSWORD} -G
USE [sqldb-tutorial];
-- Create Users table with clustered index
CREATE TABLE Users (
Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
Email NVARCHAR(255) NOT NULL UNIQUE,
Name NVARCHAR(100) NOT NULL,
CreatedAt DATETIME2 DEFAULT SYSDATETIMEOFFSET(),
IsActive BIT DEFAULT 1
);
-- Non-clustered index for Email queries
CREATE NONCLUSTERED INDEX IX_Users_Email ON Users(Email) INCLUDE (Name);
-- Insert test data
INSERT INTO Users (Email, Name) VALUES
('user1@example.com', 'Alice Johnson'),
('user2@example.com', 'Bob Smith'),
('user3@example.com', 'Charlie Wilson');
-- Optimized query
SELECT Id, Email, Name FROM Users WHERE Email = 'user1@example.com' AND IsActive = 1;
-- Check count
SELECT COUNT(*) AS TotalUsers FROM Users;Complete script: table with GUID PK (better than INT for distributed systems), index for query performance. Analogy: Clustered index = physical page order (like a sorted book). Test with sqlcmd to validate. Pitfall: Don't forget USE DB.
Scaling and monitoring
#!/bin/bash
# Scale to Serverless 2 vCore max
az sql db update \
--resource-group $RESOURCE_GROUP \
--server $SQL_SERVER_NAME \
--name $DB_NAME \
--service-objective GP_Serveless \
--max-size 100GB
# Enable auto-scaling (serverless handles it automatically)
# Monitoring: Query performance via DMV (run in SQL)
# SELECT * FROM sys.dm_db_resource_stats WHERE end_time > DATEADD(minute, -15, SYSDATETIME());
# CPU >80% alerts
az monitor metrics alert create \
--name HighCPUAlert \
--resource-group $RESOURCE_GROUP \
--scopes "/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Sql/servers/$SQL_SERVER_NAME/databases/$DB_NAME" \
--condition "avg Percentage_CPU > 80" \
--description "High CPU alert on tutorial DB"
# Cleanup (run at the end)
# az group delete --name $RESOURCE_GROUP --yes --no-waitDynamic scaling for traffic spikes. DMV sys.dm_db_resource_stats = free performance dashboard. Alerts via email/Action Group. Pitfall: Subscription ID required for cross-RG monitoring.
Best practices
- Serverless first: Ideal for variable workloads; auto-pause saves 60% on costs.
- Use Microsoft Entra ID for passwordless auth (Managed Identities).
- Indexes + Query Store: Enable (
ALTER DATABASE SET QUERY_STORE = ON) for auto-tuning. - Private Endpoint: For production, avoid public internet via VNet.
- Geo-replication:
az sql db create --create-mode Replicafor disaster recovery.
Common errors to avoid
- Firewall blocked: Always add client IP or
AllowAzureServicesbefore connecting. - Non-unique server name: Add timestamp or UUID.
- Hidden costs: Serverless pause is fine, but backups (7-day retention) are billed; monitor via Cost Analysis.
- Outdated TLS: Force 1.2+; otherwise, modern clients will refuse connections.
Next steps
- Official docs: Azure SQL Database.
- Advanced: Hyperscale, Ledger for blockchain.
- Integrate with Power BI or Azure Functions.
- Check our Azure training courses for DP-300 certification.