Cú pháp T-SQL cơ bản
1. Kiểu dữ liệu thường dùng
Numeric
| Kiểu | Range | Storage | Khi nào dùng |
|---|---|---|---|
BIT | 0/1/NULL | 1 byte (8 bits gom) | Boolean |
TINYINT | 0 → 255 | 1 byte | Status nhỏ |
SMALLINT | -32K → 32K | 2 bytes | Số nhỏ |
INT | -2.1B → 2.1B | 4 bytes | Mặc định cho ID |
BIGINT | -9.2 × 10¹⁸ → ... | 8 bytes | ID cực lớn, timestamp Unix |
DECIMAL(p, s) | Chính xác | 5-17 bytes | Tiền — DECIMAL(18, 2) |
FLOAT / REAL | Xấp xỉ | 4 hoặc 8 bytes | Khoa học |
0.1 + 0.2 = 0.30000000000000004), gây sai sót khi tính tiền.Chuỗi
| Kiểu | Đặc điểm |
|---|---|
CHAR(n) | Cố định n ký tự, pad space. ASCII. |
VARCHAR(n) | Biến đổi đến n. ASCII. Tốn ít space hơn CHAR khi data ngắn. |
NCHAR(n), NVARCHAR(n) | Unicode, dùng cho tiếng Việt, Nhật, Trung. |
VARCHAR(MAX), NVARCHAR(MAX) | Tới 2GB. Cho bài viết, JSON. |
NVARCHAR(n) cho mọi text user-input. SQL Server 2019+ hỗ trợ UTF-8 collation, có thể dùng VARCHAR(n) + UTF-8 cho tiết kiệm space — nhưng phức tạp, fresher nên cứ NVARCHAR.Thời gian
| Kiểu | Range | Chính xác | Storage |
|---|---|---|---|
DATE | 0001-01-01 → 9999-12-31 | Ngày | 3 bytes |
TIME | 00:00:00 → 23:59:59.9999999 | 100 ns | 3-5 bytes |
DATETIME | 1753-01-01 → 9999-12-31 | 3.33 ms | 8 bytes |
DATETIME2(7) | 0001 → 9999 | 100 ns | 6-8 bytes |
DATETIMEOFFSET | Có timezone | 100 ns | 8-10 bytes |
DATETIME2 thay cho DATETIME cũ (range rộng hơn, precision cao hơn, cùng size 8 bytes). Cần timezone thì DATETIMEOFFSET.Kiểu mới SQL Server 2025
VECTOR — Lưu embedding cho AI/ML
SQL Server 2025 giới thiệu kiểu VECTOR native, cho phép lưu và tìm kiếm vector embedding ngay trong database — không cần extension như pgvector bên PostgreSQL.
-- Tạo bảng với cột VECTOR
CREATE TABLE Products (
Id INT IDENTITY PRIMARY KEY,
Name NVARCHAR(200),
Description NVARCHAR(MAX),
Embedding VECTOR(1536) -- 1536 dimensions cho OpenAI text-embedding-3-small
);
-- Insert vector
INSERT INTO Products (Name, Embedding)
VALUES (N'Laptop', '[0.123, -0.456, 0.789, ...]');
-- Tính khoảng cách vector (cosine similarity)
DECLARE @query_embedding VECTOR(1536) = '[0.111, -0.222, 0.333, ...]';
SELECT TOP 5 Name,
VECTOR_DISTANCE('cosine', Embedding, @query_embedding) AS Similarity
FROM Products
ORDER BY Similarity ASC;
DiskANN index cho vector search — SQL Server 2025 dùng thuật toán DiskANN (Microsoft Research) cho approximate nearest neighbor (ANN) search, nhanh hơn full scan hàng trăm lần trên dataset lớn:
-- Tạo vector index với DiskANN
CREATE VECTOR INDEX idx_products_embedding
ON Products (Embedding) WITH (DISTANCE_METRIC = 'cosine');
-- Tìm kiếm vector với index
DECLARE @query_embedding VECTOR(1536) = '[...]';
SELECT TOP 10 * FROM Products
ORDER BY VECTOR_SEARCH(Embedding, @query_embedding, 10);
VECTOR INDEX, bảng trở thành read-only — không INSERT/UPDATE/DELETE được. Microsoft dự kiến sẽ fix trong CU (Cumulative Update) tiếp theo. Khi thiết kế hệ thống, cần tách bảng embedding riêng hoặc chấp nhận rebuild index định kỳ.- Trước 2025: dùng full-text search +
FREETEXT, hoặc tự tính cosine similarity bằng T-SQL (chậm, không scale). - Từ 2025: dùng
VECTOR+VECTOR_DISTANCE+VECTOR INDEX(DiskANN) — nhanh, chính xác, native trong engine. - Pattern điển hình: App gọi OpenAI / Azure OpenAI API để tạo embedding → lưu vào SQL Server → query bằng
VECTOR_SEARCH.
JSON — Kiểu first-class
Trước SQL Server 2025, JSON được lưu dưới dạng NVARCHAR(MAX) + CHECK (ISJSON(col) = 1). Từ 2025, JSON là kiểu dữ liệu first-class:
-- Tạo bảng với cột JSON native
CREATE TABLE Orders (
Id INT IDENTITY PRIMARY KEY,
OrderData JSON, -- first-class type, không còn là NVARCHAR nữa!
CreatedAt DATETIME2 DEFAULT GETDATE(),
INDEX idx_customer_id ON OrderData(json_path = '$.customer_id')
);
-- Insert JSON
INSERT INTO Orders (OrderData)
VALUES ('{"customer_id": 101, "total": 2500000, "status": "pending"}');
Hàm JSON mới trong SQL Server 2025:
-- JSON_ARRAYAGG — aggregate thành JSON array
SELECT JSON_ARRAYAGG(Name) FROM Products WHERE Category = N'Điện tử';
-- Kết quả: ["iPhone 15", "iPad Pro", "MacBook Air"]
-- JSON_OBJECTAGG — aggregate thành JSON object
SELECT JSON_OBJECTAGG(Id: Name) FROM Products;
-- Kết quả: {"1": "iPhone 15", "2": "iPad Pro", ...}
-- JSON_CONTAINS — kiểm tra giá trị tồn tại trong JSON
SELECT * FROM Orders WHERE JSON_CONTAINS(OrderData, 'completed', '$.status');
-- Sửa JSON tại chỗ (in-place modification)
UPDATE Orders SET OrderData.modify('append $.tags', '"urgent"');
- SQL Server hỗ trợ JSON từ 2016 (
OPENJSON,FOR JSON), nhưng lưu dưới dạng string. - Từ 2025: kiểu
JSONnative — validate tự động, index trực tiếp trên json path, sửa in-place với.modify(). - Khác MongoDB: SQL Server là relational-first có JSON capability. MongoDB là document-first. Dùng SQL Server JSON khi cần hybrid: schema cố định cho phần lớn data + flexible JSON cho phần metadata/unstructured.
2. DDL — Data Definition Language
CREATE / DROP / ALTER TABLE
CREATE TABLE Products (
Id INT IDENTITY PRIMARY KEY,
Name NVARCHAR(200) NOT NULL,
Price DECIMAL(18, 2) NOT NULL CHECK (Price >= 0),
CategoryId INT NOT NULL,
CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
IsActive BIT NOT NULL DEFAULT 1,
CONSTRAINT FK_Products_Category FOREIGN KEY (CategoryId)
REFERENCES Categories(Id) ON DELETE NO ACTION
);
-- Thêm cột
ALTER TABLE Products ADD Description NVARCHAR(MAX) NULL;
-- Đổi kiểu
ALTER TABLE Products ALTER COLUMN Name NVARCHAR(300) NOT NULL;
-- Drop cột
ALTER TABLE Products DROP COLUMN Description;
-- Drop bảng (dropping cascading FK nếu cần xử lý trước)
DROP TABLE IF EXISTS Products;
Ràng buộc (Constraint)
CREATE TABLE Users (
Id INT IDENTITY PRIMARY KEY, -- PRIMARY KEY
Email NVARCHAR(150) NOT NULL UNIQUE, -- UNIQUE
Age INT CHECK (Age BETWEEN 0 AND 150), -- CHECK
Status NVARCHAR(20) NOT NULL DEFAULT 'Active', -- DEFAULT
CountryId INT NULL FOREIGN KEY REFERENCES Countries(Id) -- FK
);
- PK: không null, 1 cái / bảng, mặc định clustered index.
- Unique: cho 1 null (SQL Server, khác chuẩn ANSI), nhiều cái / bảng, mặc định non-clustered.
Computed column
ALTER TABLE Products ADD
PriceWithTax AS (Price * 1.10) PERSISTED; -- PERSISTED = lưu vật lý, index được
3. DML — Data Manipulation
INSERT
-- Single row
INSERT INTO Products (Name, Price, CategoryId)
VALUES (N'Bàn phím cơ', 2500000, 1);
-- Multi-row
INSERT INTO Products (Name, Price, CategoryId)
VALUES
(N'Chuột Logitech', 1500000, 1),
(N'Tai nghe Sony', 3500000, 2);
-- INSERT từ SELECT
INSERT INTO ProductsBackup (Name, Price)
SELECT Name, Price FROM Products WHERE IsActive = 1;
-- Lấy ID vừa insert
INSERT INTO Products (Name, Price, CategoryId)
OUTPUT INSERTED.Id, INSERTED.Name
VALUES (N'iPhone 15', 25000000, 3);
OUTPUT — đặc thù SQL Server, cực mạnh. Trả về row đã insert/update/delete trong cùng statement. Tương đương RETURNING của Postgres.SELECT
-- Cơ bản
SELECT Id, Name, Price FROM Products WHERE Price > 1000000 ORDER BY Price DESC;
-- TOP + WITH TIES
SELECT TOP 5 WITH TIES Name, Price
FROM Products ORDER BY Price DESC;
-- DISTINCT
SELECT DISTINCT CategoryId FROM Products;
-- Pagination (SQL Server 2012+)
SELECT * FROM Products
ORDER BY Id
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
UPDATE
-- Update có WHERE
UPDATE Products
SET Price = Price * 1.1, UpdatedAt = SYSUTCDATETIME()
WHERE CategoryId = 1;
-- UPDATE từ JOIN (đặc thù SQL Server — Postgres/MySQL syntax khác)
UPDATE p
SET p.CategoryName = c.Name
FROM Products p
JOIN Categories c ON c.Id = p.CategoryId;
WHERE trong UPDATE/DELETE → đè/xoá hết bảng. Trước khi chạy, luôn SELECT thử với cùng WHERE.DELETE
DELETE FROM Products WHERE IsActive = 0;
-- TOP — xoá có giới hạn
DELETE TOP (1000) FROM AuditLogs WHERE CreatedAt < DATEADD(MONTH, -6, GETDATE());
TRUNCATE
TRUNCATE TABLE Logs;
-- Nhanh hơn DELETE (không log từng row, deallocate page).
-- KHÔNG dùng được nếu bảng được FK reference.
| DELETE | TRUNCATE | DROP | |
|---|---|---|---|
| Loại | DML | DDL | DDL |
| WHERE | ✅ | ❌ | ❌ |
| Reset IDENTITY | ❌ | ✅ | N/A |
| Trigger | ✅ chạy | ❌ | N/A |
| Tốc độ | Chậm | Nhanh | Nhanh nhất |
| Bảng sau khi chạy | ✅ còn | ✅ còn | ❌ mất |
| Rollback trong tx | ✅ | ✅ (SQL Server!) | ✅ |
MERGE — Upsert atomic
MERGE INTO Products AS target
USING (VALUES
(N'iPhone 15', 25000000, 3),
(N'iPad Pro', 20000000, 3)
) AS src (Name, Price, CategoryId)
ON target.Name = src.Name
WHEN MATCHED THEN
UPDATE SET Price = src.Price
WHEN NOT MATCHED THEN
INSERT (Name, Price, CategoryId) VALUES (src.Name, src.Price, src.CategoryId)
OUTPUT $action, INSERTED.Id, INSERTED.Name;
IF EXISTS / UPDATE / INSERT thay vì MERGE. SQL Server 2022+ đã fix nhiều, nhưng kỹ thì test kỹ.4. NULL handling
-- NULL không bằng gì, kể cả NULL khác
SELECT NULL = NULL; -- không trả TRUE, trả UNKNOWN (NULL)
-- Phải dùng IS NULL / IS NOT NULL
SELECT * FROM Users WHERE Email IS NULL;
SELECT * FROM Users WHERE Email IS NOT NULL;
-- ISNULL / COALESCE — trả giá trị thay thế
SELECT Name, ISNULL(Email, 'no-email') AS Email FROM Users;
-- COALESCE chuẩn ANSI, nhận nhiều argument
SELECT Name, COALESCE(MobilePhone, HomePhone, 'no-phone') AS Phone FROM Users;
-- NULLIF — trả NULL nếu 2 giá trị bằng nhau (tránh chia 0)
SELECT a / NULLIF(b, 0) AS Result FROM Calc;
COUNT(*) vs COUNT(col) khác gì?"COUNT(*)đếm tất cả row.COUNT(col)đếm row có col IS NOT NULL.COUNT(DISTINCT col)đếm distinct non-null.
5. Function thường dùng
String
SELECT
LEN(N'Hello'), -- 5
LOWER(N'ABC'), UPPER(N'abc'),
LEFT(N'Hello', 3), -- 'Hel'
RIGHT(N'Hello', 2), -- 'lo'
SUBSTRING(N'Hello', 2, 3), -- 'ell'
LTRIM(N' abc '), RTRIM(N' abc '), TRIM(N' abc '),
REPLACE(N'abc', 'b', 'X'), -- 'aXc'
CONCAT(N'Hello', ' ', N'World'),
STRING_AGG(Name, ', ') -- aggregate, gộp string
WITHIN GROUP (ORDER BY Name);
Date
SELECT
SYSUTCDATETIME(), -- UTC, DATETIME2 — KHUYẾN NGHỊ
GETUTCDATE(), -- UTC, DATETIME
GETDATE(), -- Local server time
DATEADD(DAY, 7, GETDATE()), -- +7 ngày
DATEDIFF(DAY, '2026-01-01', GETDATE()), -- số ngày giữa
DATEPART(YEAR, GETDATE()), -- 2026
YEAR(GETDATE()), MONTH(GETDATE()), DAY(GETDATE()),
FORMAT(GETDATE(), 'yyyy-MM-dd HH:mm'), -- chậm hơn CONVERT, đẹp hơn
EOMONTH(GETDATE()); -- last day of month
Conversion
SELECT
CAST('123' AS INT), -- 123
CONVERT(INT, '123'), -- 123
TRY_CAST('abc' AS INT), -- NULL (không throw)
TRY_CONVERT(DATE, '2026-13-99'), -- NULL (không throw)
PARSE('2,500.00' AS DECIMAL(18, 2) USING 'en-US');
NULL thay vì raise error → tránh vỡ batch.RegEx (SQL Server 2025)
SQL Server 2025 bổ sung native RegEx functions — trước đây chỉ có LIKE với pattern hạn chế, muốn RegEx phải dùng CLR integration:
-- Kiểm tra pattern trong WHERE
SELECT * FROM Users
WHERE REGEXP_LIKE(Email, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
-- Trích xuất substring theo pattern
SELECT REGEXP_SUBSTR(ContactInfo, '\d{3}-\d{3}-\d{4}') AS Phone FROM Contacts;
-- Thay thế theo pattern
SELECT REGEXP_REPLACE(SSN, '(\d{3})-(\d{2})-(\d{4})', '***-**-$3') AS MaskedSSN
FROM Users;
-- Đếm số lần khớp pattern
SELECT REGEXP_COUNT(SqlText, '\b(SELECT|INSERT|UPDATE|DELETE)\b', 'i') AS DMLCount
FROM AuditLogs;
-- Tách chuỗi thành bảng (nhiều delimiter)
SELECT * FROM REGEXP_SPLIT_TO_TABLE('a,b;c|d', '[,;|]');
-- Kết quả: 4 rows — 'a', 'b', 'c', 'd'
-- CHECK constraint với RegEx
ALTER TABLE Users ADD CONSTRAINT ck_email
CHECK (REGEXP_LIKE(Email, '^.+@.+\..+$'));
- Trước 2025:
CHECK (Email LIKE '%_@__%.__%')— sơ sài, dễ bỏ sót edge case. - Từ 2025:
CHECK (REGEXP_LIKE(Email, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'))— chuẩn RFC 5322 simplified. - Lưu ý: validation hoàn chỉnh nên làm ở tầng application. RegEx trong DB là lớp bảo vệ cuối cùng, tránh data bẩn lọt vào.
6. Variable & control flow trong T-SQL
DECLARE @Name NVARCHAR(50) = N'Alice';
DECLARE @Age INT;
SET @Age = 25;
IF @Age >= 18
PRINT N'Adult';
ELSE
PRINT N'Minor';
-- WHILE
DECLARE @i INT = 0;
WHILE @i < 5
BEGIN
PRINT @i;
SET @i += 1;
END
-- CASE — dùng cả trong SELECT
SELECT Name,
CASE
WHEN Price < 1000000 THEN N'Cheap'
WHEN Price < 10000000 THEN N'Medium'
ELSE N'Premium'
END AS Tier
FROM Products;
7. Stored procedure & user-defined function
Stored Procedure
CREATE OR ALTER PROCEDURE GetUsersByCity
@City NVARCHAR(50),
@MinAge INT = 18
AS
BEGIN
SET NOCOUNT ON;
SELECT Id, Name, Age
FROM Users
WHERE City = @City AND Age >= @MinAge;
END
GO
EXEC GetUsersByCity @City = N'HCM', @MinAge = 21;
Scalar function
CREATE OR ALTER FUNCTION fn_FullName(@First NVARCHAR(50), @Last NVARCHAR(50))
RETURNS NVARCHAR(101)
AS BEGIN
RETURN CONCAT(@First, ' ', @Last);
END
GO
SELECT dbo.fn_FullName(FirstName, LastName) FROM Users;
CREATE FUNCTION fn_FullNameTbl(@First NVARCHAR(50), @Last NVARCHAR(50))
RETURNS TABLE
AS RETURN (SELECT CONCAT(@First, ' ', @Last) AS FullName);
8. sp_invoke_external_rest_endpoint — Gọi API từ T-SQL
SQL Server 2025 cho phép gọi HTTP REST API trực tiếp từ T-SQL thông qua stored procedure hệ thống sp_invoke_external_rest_endpoint. Đây là tính năng cực mạnh để tích hợp AI/ML và external service ngay trong database:
-- Gọi OpenAI API để tạo embedding ngay trong T-SQL
DECLARE @response NVARCHAR(MAX);
EXEC sp_invoke_external_rest_endpoint
@url = 'https://api.openai.com/v1/embeddings',
@method = 'POST',
@headers = '{"Content-Type": "application/json", "Authorization": "Bearer <your-api-key>"}',
@payload = '{"model": "text-embedding-3-small", "input": "describe this product"}',
@response = @response OUTPUT;
-- Parse response để lấy embedding
SELECT JSON_VALUE(@response, '$.data[0].embedding') AS Embedding;
- Pattern RAG đơn giản (2025): Dùng
sp_invoke_external_rest_endpointgọi OpenAI / Azure OpenAI để tạo embedding → lưu vào cộtVECTOR→ query bằngVECTOR_SEARCH+ DiskANN index. Toàn bộ pipeline chạy trong T-SQL, không cần code C# / Python trung gian. - Lưu ý bảo mật: API key không nên hardcode trong script. Dùng
DATABASE SCOPED CREDENTIALhoặc Azure Key Vault để quản lý secret.
9. Các tính năng T-SQL mới khác (2022+)
GREATEST / LEAST (SQL Server 2022+)
-- Trả về giá trị lớn nhất / nhỏ nhất trong danh sách
SELECT GREATEST(10, 25, 7, 42) AS MaxValue; -- 42
SELECT LEAST(10, 25, 7, 42) AS MinValue; -- 7
-- Ứng dụng: lấy ngày gần nhất trong nhiều cột
SELECT Id, GREATEST(CreatedAt, UpdatedAt, DeletedAt) AS LastActivity
FROM AuditLog;
CASE WHEN a > b THEN a ELSE b END lồng nhau nhiều tầng — GREATEST/LEAST gọn và dễ đọc hơn nhiều.STRING_SPLIT với vị trí (SQL Server 2022+)
-- Tham số thứ 3 (enable_ordinal = 1) để có thêm cột ordinal
SELECT value, ordinal
FROM STRING_SPLIT('apple,banana,cherry', ',', 1);
-- Kết quả: (apple, 1), (banana, 2), (cherry, 3)
DATE_BUCKET (SQL Server 2022+)
-- Gom nhóm theo khoảng thời gian cố định
SELECT
DATE_BUCKET(MINUTE, 15, CreatedAt) AS Bucket,
COUNT(*) AS OrderCount
FROM Orders
GROUP BY DATE_BUCKET(MINUTE, 15, CreatedAt)
ORDER BY Bucket;
-- Gom mỗi 15 phút: 08:00, 08:15, 08:30, 08:45, ...
GENERATE_SERIES (SQL Server 2022+)
-- Sinh chuỗi số liên tiếp
SELECT value FROM GENERATE_SERIES(1, 10);
-- 1, 2, 3, ..., 10
-- Ứng dụng: sinh calendar table
SELECT DATEADD(DAY, value, '2026-01-01') AS CalendarDate
FROM GENERATE_SERIES(0, 364);
-- 365 ngày của năm 2026
WINDOW clause (SQL Server 2022+)
-- Định nghĩa window một lần, dùng nhiều lần — tránh lặp code
SELECT Name, Category, Price,
ROW_NUMBER() OVER w AS RowNum,
AVG(Price) OVER w AS AvgByCategory,
RANK() OVER w AS RankByPrice
FROM Products
WINDOW w AS (PARTITION BY Category ORDER BY Price DESC);
FIRST_VALUE / LAST_VALUE với IGNORE NULLS
-- Bỏ qua NULL khi lấy giá trị đầu/cuối trong window
SELECT Id, SensorReading,
LAST_VALUE(SensorReading) IGNORE NULLS OVER (
ORDER BY Id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS LastNonNullReading
FROM SensorData;
IGNORE NULLS cực hữu ích cho bài toán fill-down: điền giá trị gần nhất không null vào các row null trong time-series data — trước đây phải viết subquery hoặc cursor phức tạp.1. Giới thiệu và Cài đặt
SQL Server 2025 (17.x) — RDBMS của Microsoft với native vector + JSON + RegEx + Copilot. Hướng dẫn cài đặt bằng Docker, SSMS, Azure Data Studio.
3. Truy vấn nâng cao
JOIN, subquery, set operation, CTE recursive, window function, transaction, isolation level — gốc cho SQL Server.