SQL Server

Cú pháp T-SQL cơ bản

DDL, DML, kiểu dữ liệu, NULL handling, ràng buộc — nền tảng T-SQL cho SQL Server 2025.

1. Kiểu dữ liệu thường dùng

Numeric

KiểuRangeStorageKhi nào dùng
BIT0/1/NULL1 byte (8 bits gom)Boolean
TINYINT0 → 2551 byteStatus nhỏ
SMALLINT-32K → 32K2 bytesSố nhỏ
INT-2.1B → 2.1B4 bytesMặc định cho ID
BIGINT-9.2 × 10¹⁸ → ...8 bytesID cực lớn, timestamp Unix
DECIMAL(p, s)Chính xác5-17 bytesTiềnDECIMAL(18, 2)
FLOAT / REALXấp xỉ4 hoặc 8 bytesKhoa học
Câu hỏi phỏng vấn:"Khi lưu giá tiền, dùng FLOAT hay DECIMAL?"DECIMAL. FLOAT là số gần đúng (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.
Quy tắc thực dụng (2025): mặc định 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ểuRangeChính xácStorage
DATE0001-01-01 → 9999-12-31Ngày3 bytes
TIME00:00:00 → 23:59:59.9999999100 ns3-5 bytes
DATETIME1753-01-01 → 9999-12-313.33 ms8 bytes
DATETIME2(7)0001 → 9999100 ns6-8 bytes
DATETIMEOFFSETCó timezone100 ns8-10 bytes
Best practice 2025: dùng 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);
Hạn chế hiện tại (SQL Server 2025): Sau khi tạo 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ỳ.
Câu hỏi phỏng vấn:"Làm sao để tìm kiếm ngữ nghĩa (semantic search) trong SQL Server?"
  • 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"');
Câu hỏi phỏng vấn:"SQL Server có hỗ trợ JSON không? So với MongoDB thì thế nào?"
  • SQL Server hỗ trợ JSON từ 2016 (OPENJSON, FOR JSON), nhưng lưu dưới dạng string.
  • Từ 2025: kiểu JSON native — 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
);
Câu hỏi phỏng vấn:"Primary Key vs Unique Key khác gì?"
  • 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);
Mệnh đề 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;
Lỗi fresher kinh điển: quên 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 vs TRUNCATE vs DROP:
DELETETRUNCATEDROP
LoạiDMLDDLDDL
WHERE
Reset IDENTITYN/A
Trigger✅ chạyN/A
Tốc độChậmNhanhNhanh 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;
Tranh cãi MERGE: historically có bug edge case. Nhiều DBA khuyên dùng tổ hợp 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;
Câu hỏi PV:"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');
TRY_CAST / TRY_CONVERT dùng khi dữ liệu nguồn có thể lỗi format. Trả 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, '^.+@.+\..+$'));
Câu hỏi phỏng vấn:"Làm sao validate email trong SQL Server?"
  • 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;
Tránh scalar UDF trong SELECT lớn — gây row-by-row execution, chậm. Dùng inline table-valued function (iTVF) thay:
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;
Câu hỏi phỏng vấn:"Làm sao tích hợp AI vào SQL Server?"
  • Pattern RAG đơn giản (2025): Dùng sp_invoke_external_rest_endpoint gọi OpenAI / Azure OpenAI để tạo embedding → lưu vào cột VECTOR → query bằng VECTOR_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 CREDENTIAL hoặ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;
Trước đây phải dùng 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.

© 2026 .NET Fresher Guide. All rights reserved.

Về Trang Web

Hướng dẫn toàn diện để chuẩn bị phỏng vấn vị trí .NET Fresher với nội dung từ lý thuyết đến thực hành.