SQL Server T-SQL Reference

SQL Server Cheat Sheet: Every T-SQL Command You Need (2026)

A complete SQL Server T-SQL reference covering every command from basic CRUD to window functions, CROSS APPLY, stored procedures, and query tuning. Bookmark this page and stop searching for T-SQL syntax.

Mar 12, 2026 18 min read

This cheat sheet covers T-SQL syntax specific to Microsoft SQL Server. If you need general SQL syntax that works across databases, see the universal SQL cheat sheet. For filtering deep dives, check the SQL WHERE clause guide. New to SQL entirely? Start with What is SQL?

Basic CRUD

The four fundamental operations plus MERGE, a powerful SQL Server statement that combines INSERT, UPDATE, and DELETE in one pass.

SELECT - Read data

-- Select specific columns
SELECT first_name, last_name, email
FROM users;

-- Select all columns
SELECT * FROM products;

-- Select with alias
SELECT first_name AS name, email AS contact
FROM users;

INSERT INTO - Add new rows

-- Insert a single row
INSERT INTO users (first_name, last_name, email)
VALUES ('Jane', 'Smith', 'jane@example.com');

-- Insert multiple rows
INSERT INTO users (first_name, last_name, email)
VALUES
    ('Alice', 'Johnson', 'alice@example.com'),
    ('Bob', 'Williams', 'bob@example.com');

-- Insert from another table
INSERT INTO archive_orders (id, customer_id, total)
SELECT id, customer_id, total_amount
FROM orders
WHERE order_date < '2025-01-01';

UPDATE - Modify existing rows

-- Update specific rows
UPDATE users
SET email = 'newemail@example.com'
WHERE id = 42;

-- Update multiple columns
UPDATE products
SET price = price * 1.10, updated_at = GETDATE()
WHERE category = 'electronics';

DELETE - Remove rows

-- Delete specific rows
DELETE FROM orders
WHERE status = 'cancelled' AND created_at < '2025-01-01';

-- Delete all rows (use with caution)
DELETE FROM temp_logs;

MERGE - Upsert in one statement (SQL Server-specific)

MERGE INTO products AS target
USING staging_products AS source
ON target.sku = source.sku
WHEN MATCHED THEN
    UPDATE SET
        target.price = source.price,
        target.updated_at = GETDATE()
WHEN NOT MATCHED BY TARGET THEN
    INSERT (sku, product_name, price, created_at)
    VALUES (source.sku, source.product_name, source.price, GETDATE())
WHEN NOT MATCHED BY SOURCE THEN
    DELETE;

Filtering and Sorting

Control which rows are returned and in what order. SQL Server uses TOP instead of LIMIT, and OFFSET FETCH NEXT for pagination.

WHERE, AND, OR

SELECT * FROM orders
WHERE status = 'shipped'
  AND total_amount > 100
  AND (region = 'US' OR region = 'EU');

IN and BETWEEN

-- IN: match any value in a list
SELECT * FROM products
WHERE category IN ('electronics', 'books', 'clothing');

-- BETWEEN: inclusive range
SELECT * FROM orders
WHERE order_date BETWEEN '2026-01-01' AND '2026-03-31';

LIKE - Pattern matching

-- % matches any sequence of characters
SELECT * FROM users WHERE email LIKE '%@gmail.com';

-- _ matches exactly one character
SELECT * FROM products WHERE sku LIKE 'SKU-____';

IS NULL

-- Find rows with missing values
SELECT * FROM customers WHERE phone IS NULL;

-- Find rows that have values
SELECT * FROM customers WHERE phone IS NOT NULL;

ORDER BY with TOP (SQL Server-specific)

-- Return first 10 rows
SELECT TOP 10 product_name, price
FROM products
ORDER BY price DESC;

-- Return top 5 percent
SELECT TOP 5 PERCENT *
FROM orders
ORDER BY total_amount DESC;

-- OFFSET FETCH NEXT for pagination (SQL Server 2012+)
SELECT * FROM users
ORDER BY created_at DESC
OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;  -- Page 3, 20 per page

Aggregation

Summarize data across multiple rows into a single result.

COUNT, SUM, AVG, MIN, MAX

SELECT
    COUNT(*) AS total_orders,
    SUM(total_amount) AS revenue,
    AVG(total_amount) AS avg_order_value,
    MIN(total_amount) AS smallest_order,
    MAX(total_amount) AS largest_order
FROM orders
WHERE order_date >= '2026-01-01';

GROUP BY

-- Revenue by category
SELECT
    category,
    COUNT(*) AS num_products,
    SUM(price) AS total_value
FROM products
GROUP BY category
ORDER BY total_value DESC;

HAVING - Filter groups

-- Only show categories with more than 10 products
SELECT category, COUNT(*) AS num_products
FROM products
GROUP BY category
HAVING COUNT(*) > 10
ORDER BY num_products DESC;

STRING_AGG - Concatenate grouped values (SQL Server 2017+)

-- Comma-separated list of product names per category
SELECT
    category,
    STRING_AGG(product_name, ', ') AS products
FROM products
GROUP BY category;

-- With ordering inside the aggregation
SELECT
    department,
    STRING_AGG(employee_name, ', ')
        WITHIN GROUP (ORDER BY hire_date) AS team
FROM employees
GROUP BY department;

Remember: WHERE filters rows before grouping. HAVING filters groups after aggregation.

Skip the syntax. Describe what you need in English.

AI2SQL generates T-SQL for SQL Server automatically.

Try AI2SQL Free

JOINs

Combine rows from two or more tables based on a related column. SQL Server supports all standard JOINs plus CROSS APPLY and OUTER APPLY.

INNER JOIN - Only matching rows from both tables

SELECT o.id, o.order_date, c.name, c.email
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;

LEFT JOIN - All rows from left table, matching from right

-- All customers, even those with no orders
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.name;

RIGHT JOIN - All rows from right table, matching from left

-- All products, even those never ordered
SELECT p.product_name, SUM(oi.quantity) AS total_sold
FROM order_items oi
RIGHT JOIN products p ON oi.product_id = p.id
GROUP BY p.product_name;

FULL OUTER JOIN - All rows from both tables

-- Show all customers and all orders, matched where possible
SELECT c.name, o.id AS order_id
FROM customers c
FULL OUTER JOIN orders o ON c.id = o.customer_id;

CROSS JOIN - Every combination of rows

-- Generate all size/color combinations
SELECT s.size_name, c.color_name
FROM sizes s
CROSS JOIN colors c;

CROSS APPLY / OUTER APPLY (SQL Server-specific)

-- CROSS APPLY: like INNER JOIN with a correlated subquery
-- Get the top 3 orders for each customer
SELECT c.name, o.id, o.total_amount
FROM customers c
CROSS APPLY (
    SELECT TOP 3 id, total_amount
    FROM orders
    WHERE customer_id = c.id
    ORDER BY total_amount DESC
) o;

-- OUTER APPLY: like LEFT JOIN (returns NULLs when no match)
SELECT c.name, o.id, o.total_amount
FROM customers c
OUTER APPLY (
    SELECT TOP 1 id, total_amount
    FROM orders
    WHERE customer_id = c.id
    ORDER BY order_date DESC
) o;

Quick reference: INNER = intersection, LEFT = all left + matching right, CROSS APPLY = correlated subquery per row (INNER style), OUTER APPLY = same but keeps rows with no match (LEFT style).

Subqueries and CTEs

Break complex queries into manageable parts.

Scalar subquery

-- Single value in SELECT
SELECT
    product_name,
    price,
    price - (SELECT AVG(price) FROM products) AS diff_from_avg
FROM products;

Subquery with IN

-- Customers who placed orders over $500
SELECT name, email
FROM customers
WHERE id IN (
    SELECT customer_id
    FROM orders
    WHERE total_amount > 500
);

EXISTS

-- Products that have been ordered at least once
SELECT p.product_name
FROM products p
WHERE EXISTS (
    SELECT 1
    FROM order_items oi
    WHERE oi.product_id = p.id
);

CTE (Common Table Expression) with WITH

-- Monthly revenue with running total
WITH monthly_revenue AS (
    SELECT
        DATEFROMPARTS(YEAR(order_date), MONTH(order_date), 1) AS month,
        SUM(total_amount) AS revenue
    FROM orders
    GROUP BY YEAR(order_date), MONTH(order_date)
)
SELECT
    month,
    revenue,
    SUM(revenue) OVER (ORDER BY month) AS running_total
FROM monthly_revenue
ORDER BY month;

Recursive CTE

-- Org chart: find all reports under a manager
WITH org_chart AS (
    -- Anchor: start with the top manager
    SELECT id, name, manager_id, 0 AS level
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- Recursive: find direct reports
    SELECT e.id, e.name, e.manager_id, oc.level + 1
    FROM employees e
    INNER JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT id, name, level
FROM org_chart
ORDER BY level, name;

Window Functions

Perform calculations across a set of rows related to the current row, without collapsing them into a single output row.

ROW_NUMBER, RANK, DENSE_RANK, NTILE

SELECT
    name,
    department,
    salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
    RANK() OVER (ORDER BY salary DESC) AS rank,
    DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank,
    NTILE(4) OVER (ORDER BY salary DESC) AS quartile
FROM employees;

ROW_NUMBER gives unique sequential numbers. RANK leaves gaps after ties (1,2,2,4). DENSE_RANK has no gaps (1,2,2,3). NTILE(4) divides rows into 4 equal groups.

Window functions getting complex?

Describe your ranking or running total in English. AI2SQL handles the T-SQL.

Generate T-SQL Free

PARTITION BY

-- Rank employees within each department
SELECT
    name,
    department,
    salary,
    RANK() OVER (
        PARTITION BY department
        ORDER BY salary DESC
    ) AS dept_rank
FROM employees;

LAG and LEAD

-- Compare each month's revenue to the previous month
SELECT
    month,
    revenue,
    LAG(revenue, 1) OVER (ORDER BY month) AS prev_month,
    revenue - LAG(revenue, 1) OVER (ORDER BY month) AS month_over_month
FROM monthly_revenue;

SUM() OVER with ROWS BETWEEN

-- Running total
SELECT
    order_date,
    total_amount,
    SUM(total_amount) OVER (
        ORDER BY order_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total
FROM orders;

-- 7-day moving average
SELECT
    order_date,
    daily_revenue,
    AVG(daily_revenue) OVER (
        ORDER BY order_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS moving_avg_7d
FROM daily_sales;

SQL Server-Specific Features

T-SQL features you will not find in other databases.

IDENTITY - Auto-increment columns

CREATE TABLE users (
    id INT IDENTITY(1,1) PRIMARY KEY,
    name NVARCHAR(100) NOT NULL,
    email NVARCHAR(255) NOT NULL
);

-- Get the last inserted identity value
INSERT INTO users (name, email) VALUES ('Jane', 'jane@example.com');
SELECT SCOPE_IDENTITY() AS new_id;      -- Preferred: current scope only
SELECT @@IDENTITY AS new_id;             -- Caution: may include trigger inserts

ISNULL and COALESCE

-- ISNULL: SQL Server-specific, takes exactly 2 args
SELECT ISNULL(phone, 'N/A') AS phone FROM customers;

-- COALESCE: standard SQL, takes multiple args, returns first non-null
SELECT COALESCE(mobile, work_phone, home_phone, 'N/A') AS contact
FROM customers;

TOP WITH TIES

-- Return top 5 plus any rows tied with the 5th
SELECT TOP 5 WITH TIES product_name, price
FROM products
ORDER BY price DESC;

OUTPUT clause (like RETURNING in PostgreSQL)

-- Return inserted rows
INSERT INTO users (name, email)
OUTPUT INSERTED.id, INSERTED.name, INSERTED.email
VALUES ('Jane', 'jane@example.com');

-- Return deleted rows
DELETE FROM orders
OUTPUT DELETED.id, DELETED.customer_id, DELETED.total_amount
WHERE status = 'cancelled';

-- Return both old and new values on UPDATE
UPDATE products
SET price = price * 1.10
OUTPUT DELETED.price AS old_price, INSERTED.price AS new_price, INSERTED.id
WHERE category = 'electronics';

TRY_CAST and TRY_CONVERT - Safe type conversion

-- Returns NULL instead of error on failure
SELECT TRY_CAST('abc' AS INT);        -- NULL
SELECT TRY_CAST('123' AS INT);        -- 123

SELECT TRY_CONVERT(DATE, '2026-13-01');  -- NULL (invalid month)
SELECT TRY_CONVERT(DATE, '2026-03-12');  -- 2026-03-12

IIF and CHOOSE - Inline conditionals

-- IIF: inline if-else
SELECT name, salary,
    IIF(salary > 100000, 'Senior', 'Standard') AS band
FROM employees;

-- CHOOSE: pick by position (1-based)
SELECT order_id,
    CHOOSE(MONTH(order_date), 'Jan','Feb','Mar','Apr','May','Jun',
           'Jul','Aug','Sep','Oct','Nov','Dec') AS order_month
FROM orders;

Temp tables and table variables

-- Local temp table (visible to current session)
CREATE TABLE #temp_orders (
    id INT,
    customer_id INT,
    total DECIMAL(10,2)
);

INSERT INTO #temp_orders
SELECT id, customer_id, total_amount
FROM orders WHERE order_date >= '2026-01-01';

-- Global temp table (visible to all sessions)
CREATE TABLE ##global_cache (
    key_name NVARCHAR(100),
    value NVARCHAR(MAX)
);

-- Table variable (scoped to batch, no transaction log)
DECLARE @top_customers TABLE (
    customer_id INT,
    total_spent DECIMAL(10,2)
);

INSERT INTO @top_customers
SELECT TOP 100 customer_id, SUM(total_amount)
FROM orders
GROUP BY customer_id
ORDER BY SUM(total_amount) DESC;

SELECT * FROM @top_customers;

NEWID() - Generate unique identifiers

-- Random UUID
SELECT NEWID();  -- e.g., 6F9619FF-8B86-D011-B42D-00CF4FC964FF

-- Use as default value
CREATE TABLE sessions (
    id UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY,
    user_id INT NOT NULL,
    created_at DATETIME2 DEFAULT SYSDATETIME()
);

Migrating from SQL Server to MySQL?

AI2SQL auto-converts T-SQL syntax to any target dialect.

Convert T-SQL to MySQL

String Functions

Manipulate and transform text data in T-SQL.

-- CONCAT: combine strings (NULL-safe)
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM users;

-- + operator: concatenation (NULL propagates)
SELECT first_name + ' ' + last_name AS full_name
FROM users;

-- SUBSTRING: extract part of a string (1-based)
SELECT SUBSTRING(phone, 1, 3) AS area_code
FROM contacts;

-- TRIM / LTRIM / RTRIM
SELECT TRIM(name) FROM products;         -- SQL Server 2017+
SELECT LTRIM(RTRIM(name)) FROM products;  -- Older versions

-- UPPER / LOWER
SELECT UPPER(email) FROM users;
SELECT LOWER(city) FROM addresses;

-- LEN: count characters (excludes trailing spaces)
SELECT name, LEN(name) AS name_length FROM products;

-- REPLACE
SELECT REPLACE(url, 'http://', 'https://') FROM links;

-- CHARINDEX: find position of substring (1-based, 0 = not found)
SELECT CHARINDEX('@', email) AS at_position FROM users;

-- STUFF: delete + insert at position
SELECT STUFF('SQL Server', 5, 6, 'Database');  -- 'SQL Database'

-- STRING_AGG: concatenate with delimiter (SQL Server 2017+)
SELECT department, STRING_AGG(name, ', ') AS members
FROM employees
GROUP BY department;

-- FORMAT: locale-aware formatting
SELECT FORMAT(price, 'C', 'en-US') AS formatted_price  -- $1,234.56
FROM products;

Date and Time

T-SQL date functions differ significantly from other databases. Here is every function you need.

-- Current date/time
SELECT GETDATE();          -- DATETIME (millisecond precision)
SELECT SYSDATETIME();      -- DATETIME2 (100-nanosecond precision)
SELECT GETUTCDATE();       -- UTC datetime

-- DATEADD: add interval to date
SELECT DATEADD(DAY, 7, order_date) AS one_week_later FROM orders;
SELECT DATEADD(MONTH, -3, GETDATE()) AS three_months_ago;

-- DATEDIFF: difference between dates
SELECT DATEDIFF(DAY, ship_date, delivery_date) AS transit_days
FROM shipments;
SELECT DATEDIFF(YEAR, birth_date, GETDATE()) AS age FROM users;

-- DATEPART: extract part of a date (returns INT)
SELECT DATEPART(WEEKDAY, order_date) AS day_of_week FROM orders;
SELECT DATEPART(QUARTER, order_date) AS quarter FROM orders;

-- Shorthand date part functions
SELECT YEAR(order_date) AS yr,
       MONTH(order_date) AS mo,
       DAY(order_date) AS dy
FROM orders;

-- FORMAT: flexible date formatting
SELECT FORMAT(order_date, 'yyyy-MM-dd') FROM orders;
SELECT FORMAT(order_date, 'MMMM dd, yyyy') FROM orders;  -- March 12, 2026

-- CONVERT with style codes (classic T-SQL approach)
SELECT CONVERT(VARCHAR, GETDATE(), 23);   -- 2026-03-12 (ISO)
SELECT CONVERT(VARCHAR, GETDATE(), 101);  -- 03/12/2026 (US)
SELECT CONVERT(VARCHAR, GETDATE(), 108);  -- 14:30:00 (time)

-- EOMONTH: last day of month
SELECT EOMONTH(GETDATE());              -- 2026-03-31
SELECT EOMONTH(GETDATE(), -1);           -- 2026-02-28 (previous month)
SELECT EOMONTH(GETDATE(), 2);            -- 2026-05-31 (2 months ahead)

-- Build a date from parts
SELECT DATEFROMPARTS(2026, 3, 12);       -- 2026-03-12
SELECT DATETIME2FROMPARTS(2026, 3, 12, 14, 30, 0, 0, 0);

-- Filter by relative dates
SELECT * FROM orders
WHERE order_date >= DATEADD(DAY, -30, GETDATE());

Table Operations

Define and modify your database structure in SQL Server.

CREATE TABLE

CREATE TABLE users (
    id INT IDENTITY(1,1) PRIMARY KEY,
    first_name NVARCHAR(100) NOT NULL,
    last_name NVARCHAR(100) NOT NULL,
    email NVARCHAR(255) UNIQUE NOT NULL,
    created_at DATETIME2 DEFAULT SYSDATETIME(),
    is_active BIT DEFAULT 1
);

ALTER TABLE

-- Add a column
ALTER TABLE users ADD phone NVARCHAR(20);

-- Change a column type
ALTER TABLE products ALTER COLUMN price DECIMAL(10,2) NOT NULL;

-- Add a constraint
ALTER TABLE orders ADD CONSTRAINT fk_customer
    FOREIGN KEY (customer_id) REFERENCES customers(id);

-- Add a default constraint
ALTER TABLE users ADD CONSTRAINT df_status
    DEFAULT 'active' FOR status;

-- Drop a column
ALTER TABLE users DROP COLUMN phone;

DROP TABLE

-- Drop if exists (SQL Server 2016+)
DROP TABLE IF EXISTS temp_imports;

-- Classic check (older versions)
IF OBJECT_ID('temp_imports', 'U') IS NOT NULL
    DROP TABLE temp_imports;

CREATE INDEX (Clustered and Nonclustered)

-- Nonclustered index (default)
CREATE NONCLUSTERED INDEX idx_users_email ON users(email);

-- Composite index
CREATE INDEX idx_orders_customer_date
ON orders(customer_id, order_date);

-- Unique index
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);

-- Clustered index (one per table, determines physical row order)
CREATE CLUSTERED INDEX idx_orders_date ON orders(order_date);

-- Include columns (cover the query without key lookup)
CREATE INDEX idx_orders_status
ON orders(status)
INCLUDE (customer_id, total_amount);

-- Drop an index
DROP INDEX idx_users_email ON users;

TRUNCATE

-- Remove all rows (faster than DELETE, resets identity)
TRUNCATE TABLE temp_logs;

sp_help and sp_columns - Inspect table structure

-- Full table info (columns, indexes, constraints)
EXEC sp_help 'users';

-- Column details only
EXEC sp_columns 'users';

Pro Tips

Write faster, more efficient T-SQL queries.

SET STATISTICS IO/TIME - Measure query cost

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY c.name;

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
-- Check the Messages tab for logical reads and CPU time

Execution Plans

-- Estimated plan (does not execute the query)
SET SHOWPLAN_XML ON;
GO
SELECT * FROM orders WHERE customer_id = 42;
GO
SET SHOWPLAN_XML OFF;

-- Actual plan (executes and shows real row counts)
SET STATISTICS XML ON;
SELECT * FROM orders WHERE customer_id = 42;
SET STATISTICS XML OFF;

-- In SSMS: Ctrl+L (estimated) or Ctrl+M (actual)

Index types at a glance

  • Clustered index: Determines the physical order of rows in the table. One per table. Usually on the primary key.
  • Nonclustered index: Separate structure pointing back to the clustered index key. Multiple per table. Use on columns in WHERE, JOIN, ORDER BY.
  • Columnstore index: Column-based storage for analytics workloads. Massive compression and fast aggregation on large tables.
  • Filtered index: Index with a WHERE clause. Great for partial data (e.g., WHERE is_active = 1).

Query Store - Built-in performance monitoring

-- Enable Query Store on a database
ALTER DATABASE MyDatabase SET QUERY_STORE = ON;

-- Find top resource-consuming queries
SELECT TOP 10
    qt.query_sql_text,
    rs.avg_duration,
    rs.avg_logical_io_reads,
    rs.count_executions
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.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
ORDER BY rs.avg_duration DESC;

Dynamic SQL with sp_executesql

-- Parameterized dynamic SQL (prevents SQL injection)
DECLARE @sql NVARCHAR(MAX);
DECLARE @category NVARCHAR(50) = 'electronics';
DECLARE @min_price DECIMAL(10,2) = 100;

SET @sql = N'SELECT product_name, price
FROM products
WHERE category = @cat AND price >= @minp
ORDER BY price DESC';

EXEC sp_executesql @sql,
    N'@cat NVARCHAR(50), @minp DECIMAL(10,2)',
    @cat = @category, @minp = @min_price;

Try these queries instantly -- AI2SQL generates T-SQL from plain English so you can skip the syntax and focus on the data you need.

Frequently Asked Questions

What is the difference between SQL and T-SQL?

T-SQL (Transact-SQL) is Microsoft's extension of standard SQL used in SQL Server and Azure SQL Database. It includes everything in standard SQL plus additional features like TRY_CAST, IIF, CHOOSE, OUTPUT clause, CROSS APPLY, temp tables (#table), table variables (@table), and procedural programming with BEGIN/END, IF/ELSE, and WHILE loops.

How do I limit results in SQL Server?

SQL Server does not support the LIMIT keyword. Use TOP N to return the first N rows (e.g., SELECT TOP 10 * FROM users), or use OFFSET FETCH for pagination (e.g., ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY). TOP requires no ORDER BY, but OFFSET FETCH does.

What is CROSS APPLY in SQL Server?

CROSS APPLY is a SQL Server-specific JOIN type that lets you invoke a table-valued function or correlated subquery for each row of the outer table. It works like INNER JOIN but can reference columns from the outer table. OUTER APPLY is the LEFT JOIN equivalent, returning NULLs when the right side produces no rows.

How do I get the ID of a newly inserted row in SQL Server?

Use SCOPE_IDENTITY() to get the last identity value inserted in the current scope. Avoid @@IDENTITY as it can return values from triggers. You can also use the OUTPUT clause: INSERT INTO users (name) OUTPUT INSERTED.id VALUES ('Jane'), which returns the inserted ID directly in the result set.

Can AI generate T-SQL queries for SQL Server?

Yes. Tools like AI2SQL let you describe what you need in plain English and generate correct T-SQL syntax automatically. This is especially useful for SQL Server-specific features like MERGE statements, CROSS APPLY, OUTPUT clauses, and window functions with complex frame specifications.

Generate T-SQL from Plain English

Stop memorizing SQL Server syntax. Describe what you need and let AI2SQL generate accurate T-SQL queries for your database.

Try AI2SQL Free

No credit card required