# SQL Server CLI Guide (sqlcmd)

Run SQL against Microsoft SQL Server and Azure SQL from the command line — without an MCP server or a GUI like SSMS. Uses [go-sqlcmd](https://github.com/microsoft/go-sqlcmd), Microsoft's modern rewrite of the classic `sqlcmd` utility.

> For *persistent* Claude-in-chat SQL access (natural-language queries without typing commands), see `sql-mcp-guide.md`. This guide is for *one-off CLI* use from your shell or from Claude's Bash tool.

Good for:
- One-off reads against local Docker SQL or Azure SQL while debugging
- Scripted before/after snapshots during data migrations
- Ad-hoc DML without opening a heavyweight client
- Pairing with `az sql` firewall commands for clean open-query-close flows

## 1. Install

**Windows:**
```bash
winget install Microsoft.Sqlcmd
```

**macOS:**
```bash
brew install sqlcmd
```

**Linux:** see https://github.com/microsoft/go-sqlcmd for apt/yum instructions.

Verify:
```bash
sqlcmd --version
```

> **Note:** "The old `sqlcmd` that shipped with SQL Server installs is a different utility. This tool is the modern standalone replacement. On Windows it installs to `C:\Program Files\sqlcmd\`. If `sqlcmd` isn't found after install, add that directory to your PATH."

## 2. Connect with SQL Authentication

Simplest path. Works for local Docker SQL or any server with a SQL login:

```bash
sqlcmd -S your-server.database.windows.net,1433 \
  -d YourDatabase \
  -U YourUser -P 'YOUR_PASSWORD' \
  -N -C \
  -Q "SELECT @@VERSION"
```

Flags worth knowing:

| Flag | What it does |
|---|---|
| `-S server,port` | Server + port |
| `-d db` | Initial database |
| `-U user -P password` | SQL authentication — quote passwords with `!`, `*`, `@` etc. |
| `-N` | Require encrypted connection |
| `-C` | Trust server certificate (needed for self-signed, common for local/dev) |
| `-Q "..."` | Execute a single statement and exit |
| `-l 30` | Connection timeout in seconds |
| `-s '|'` | Column separator |
| `-W` | Remove whitespace padding (easier to read wide rows) |

## 3. Connect to Azure SQL with Microsoft Entra ID

If you're already signed in with `az login`, skip the password:

```bash
sqlcmd -S your-server.database.windows.net \
  -d YourDatabase \
  --authentication-method ActiveDirectoryDefault \
  -Q "SELECT DB_NAME()"
```

> **Important:** "This only works if the SQL server has a Microsoft Entra ID admin configured (`az sql server ad-admin create ...`) and your account is that admin or in a group that is. If not, you'll see `Login failed for user '<token-identified principal>'` — fall back to SQL auth with credentials pulled from Key Vault."

## 4. Output Formats

```bash
# Default (whitespace-padded columns)
sqlcmd ... -Q "SELECT id, name FROM MyTable"

# Pipe-separated, no padding (great for wide rows)
sqlcmd ... -s '|' -W -Q "SELECT id, name FROM MyTable"

# CSV
sqlcmd ... -s ',' -W -Q "SELECT id, name FROM MyTable" > out.csv

# Vertical (one column per line — great for a single wide row)
sqlcmd ... -y 0 -Y 0 -Q "SELECT * FROM MyTable WHERE id = 1"
```

## 5. Multi-Statement Scripts with Section Headers

`PRINT` messages print inline between result sets, keeping multi-query output readable:

```bash
sqlcmd -S your-server.database.windows.net -d YourDatabase \
  -U YourUser -P 'YOUR_PASSWORD' -N -C -Q "
PRINT '=== row counts ===';
SELECT COUNT(*) AS rows FROM MyTable;

PRINT '=== top 5 recent ===';
SELECT TOP 5 id, name, created_at FROM MyTable ORDER BY created_at DESC;
" -s '|' -W
```

## 6. Pattern — Azure SQL + Firewall Dance

When querying Azure SQL from a machine that isn't on the allow list: open, query, close. For broader Azure CLI context (Key Vault, App Services, logs), see `azure-cli-guide.md`.

```bash
MY_IP=$(curl -s https://api.ipify.org)

# Open
az sql server firewall-rule create \
  --resource-group my-resource-group --server my-sql-server \
  --name TempDevAccess \
  --start-ip-address $MY_IP --end-ip-address $MY_IP

# Query
sqlcmd -S my-sql-server.database.windows.net -d YourDatabase \
  -U YourUser -P 'YOUR_PASSWORD' -N -C -Q "SELECT ..."

# Close
az sql server firewall-rule delete \
  --resource-group my-resource-group --server my-sql-server \
  --name TempDevAccess
```

> **Tip:** "Ask Claude to orchestrate this — it'll capture your current IP, open the rule, run your query, and close the rule as one flow. Same shape every time."

## 7. Where Credentials Should Live

Never hard-code passwords in your shell history or checked-in scripts.

- **Azure SQL / Key Vault shops:** pull the connection string from Key Vault at query time:
  ```bash
  SECRET=$(az keyvault secret show --vault-name my-key-vault --name my-sql-secret --query value -o tsv)
  # parse or pass to sqlcmd
  ```
- **Local Docker dev DBs:** put creds in a local `.env` that's in `.gitignore`.
- **Claude sessions:** let Claude fetch the secret inline rather than typing it into the prompt.

## 8. Asking Claude

Natural-language prompts that trigger Claude to use `sqlcmd`:

> "Query the local Docker DB for the 5 most recent rows in `MyTable` and show the `created_at` values."

> "Open a temporary firewall rule on the UAT SQL server for my IP, compare row counts in three tables between local and UAT, then close the rule."

> "Pull the SQL admin password from Key Vault, run a quick count of `users` in UAT, and don't echo the password anywhere."

Claude will construct the right `sqlcmd` invocation, fetch credentials as needed, and clean up any temp firewall rules it opens.

---

## Node Alternative

For Node-heavy projects that prefer a JS-native CLI:

- [mssql](https://www.npmjs.com/package/mssql) — install globally (`npm install -g mssql`) for a CLI. Lighter than go-sqlcmd; covers basic queries.
- [sql-cli](https://github.com/hasankhan/sql-cli) — older feature-complete REPL with autocomplete; still maintained.

Feature-wise, go-sqlcmd is the more complete tool (Microsoft Entra auth, streaming, deeper Azure integration). Reach for a Node alternative only if your workflow already lives in npm land.
