Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kiểu Dữ liệu trong SQL Server

1. Kiểu số (Numeric Types)

Integer Types

KiểuBytesGiá trị minGiá trị maxDùng khi
tinyint10255Cột nhỏ như status, age (0-120)
smallint2-32,76832,767ID nhỏ, year
int4-2,147,483,6482,147,483,647FK, PK cho hầu hết tables
bigint8-9.2 × 10¹⁸9.2 × 10¹⁸ID lớn, counts, Unix timestamps
-- Ví dụ sử dụng
CREATE TABLE Products (
    ProductId    INT          NOT NULL,  -- PK thông thường
    CategoryId   TINYINT      NOT NULL,  -- Giả sử < 256 categories
    StockCount   SMALLINT     NOT NULL,  -- Tồn kho không quá 32767
    TotalOrders  BIGINT       NOT NULL DEFAULT 0  -- Có thể rất lớn
);

-- Tính toán có thể overflow nếu dùng sai type
SELECT 2000000000 + 2000000000;           -- Overflow với INT!
SELECT CAST(2000000000 AS BIGINT) + 2000000000;  -- OK

-- Kiểm tra giới hạn
SELECT 
    'tinyint'  AS type_name, CAST(255 AS TINYINT) AS max_val UNION ALL
    SELECT 'smallint', CAST(32767 AS SMALLINT) UNION ALL
    SELECT 'int',      CAST(2147483647 AS INT) UNION ALL
    SELECT 'bigint',   CAST(9223372036854775807 AS BIGINT);

Decimal/Numeric Types

DECIMAL(p, s)NUMERIC(p, s) là synonyms, lưu số thập phân chính xác:

  • p (precision): tổng số chữ số (1-38)
  • s (scale): số chữ số sau dấu thập phân (0 đến p)
PrecisionStorage
1-95 bytes
10-199 bytes
20-2813 bytes
29-3817 bytes
-- Dùng cho tiền tệ và số thập phân cần chính xác
DECLARE @price DECIMAL(10, 2);  -- Tối đa 99999999.99
DECLARE @rate  DECIMAL(5, 4);   -- Tối đa 9.9999 (ví dụ: tax rate)
DECLARE @qty   NUMERIC(18, 3);  -- Lượng với 3 chữ số thập phân

-- Ví dụ tính toán chính xác
SELECT CAST(0.1 AS DECIMAL(10,1)) + CAST(0.2 AS DECIMAL(10,1));  -- 0.3 chính xác
SELECT 0.1 + 0.2;  -- 0.300000000000... (float, không chính xác)

Float và Real

KiểuBytesKý hiệu khoa họcChính xác
real4~7 chữ sốApproximate
float8~15 chữ sốApproximate
float(n)4 hoặc 8n = 1-53Approximate

Cảnh báo: floatrealapproximate - KHÔNG dùng cho tiền tệ, kế toán, hoặc bất kỳ tính toán nào cần độ chính xác tuyệt đối!

-- Vấn đề với float
SELECT CAST(0.1 AS FLOAT) + CAST(0.2 AS FLOAT);
-- Kết quả: 0.30000000000000004 (sai!)

-- Float thích hợp cho: tọa độ GPS, đo lường khoa học, phân tích thống kê
CREATE TABLE SensorData (
    ReadingId  INT PRIMARY KEY,
    Latitude   FLOAT NOT NULL,   -- GPS coordinates
    Longitude  FLOAT NOT NULL,
    Temperature REAL NOT NULL    -- Nhiệt độ, sai số nhỏ chấp nhận được
);

Money Types

KiểuBytesPhạm viChính xác
money8±922 trillion4 decimal places
smallmoney4±214,7484 decimal places
-- money type
DECLARE @price MONEY = 19.99;
DECLARE @tax   MONEY = 0.10;
SELECT @price + @tax;  -- 20.09

-- Vấn đề với money: phép chia có thể mất độ chính xác
DECLARE @total MONEY = 100;
SELECT @total / 3;  -- 33.3333 (OK)
SELECT @total / 3 * 3;  -- 99.9999 (mất 0.0001!)

-- Khuyến nghị: dùng DECIMAL(19, 4) thay vì MONEY để có kiểm soát tốt hơn

2. Kiểu chuỗi (String Types)

Non-Unicode vs Unicode

KiểuEncodingbytes/ký tựTối đa
char(n)Non-Unicode (ASCII)18,000 ký tự
varchar(n)Non-Unicode (ASCII)18,000 ký tự
varchar(MAX)Non-Unicode12 GB
nchar(n)Unicode (UTF-16)24,000 ký tự
nvarchar(n)Unicode (UTF-16)24,000 ký tự
nvarchar(MAX)Unicode22 GB

char vs varchar

-- char(n): fixed-length, padding bằng spaces nếu ngắn hơn n
DECLARE @fixed CHAR(10) = 'Hello';
SELECT LEN(@fixed);    -- 5 (LEN bỏ trailing spaces)
SELECT DATALENGTH(@fixed);  -- 10 (luôn 10 bytes)

-- varchar(n): variable-length, không padding
DECLARE @var VARCHAR(10) = 'Hello';
SELECT LEN(@var);       -- 5
SELECT DATALENGTH(@var); -- 5

-- Khi nào dùng char: dữ liệu có độ dài cố định (mã code, country code)
CREATE TABLE Countries (
    CountryCode CHAR(2) NOT NULL,  -- Luôn đúng 2 ký tự: 'US', 'VN', 'JP'
    PhoneCode   CHAR(4),           -- '+84 ', '+1  '
    CountryName VARCHAR(100) NOT NULL
);

nvarchar vs varchar - Unicode

-- Dùng N prefix để tạo Unicode string literal
INSERT INTO Employees (FullName) VALUES (N'Nguyễn Văn An');  -- Đúng với dấu
INSERT INTO Employees (FullName) VALUES ('Nguyễn Văn An');   -- Có thể sai nếu column là nvarchar

-- Khi nào dùng nvarchar:
-- - Dữ liệu nhiều ngôn ngữ (tiếng Việt, tiếng Trung, Arabic, etc.)
-- - Tên người, địa chỉ
-- - Nội dung do user nhập

-- Khi nào dùng varchar:
-- - Codes, IDs, email addresses (ASCII-safe)
-- - Dữ liệu hệ thống không cần Unicode
-- - Tiết kiệm storage khi biết chắc chỉ dùng ASCII

-- So sánh storage
CREATE TABLE StorageDemo (
    col_varchar  VARCHAR(100)  = 'Hello',
    col_nvarchar NVARCHAR(100) = N'Hello'
);
-- varchar: 5 bytes
-- nvarchar: 10 bytes (2 bytes/ký tự)

varchar(MAX) và nvarchar(MAX)

-- Dùng cho dữ liệu text rất lớn (tới 2GB)
CREATE TABLE Articles (
    ArticleId INT PRIMARY KEY,
    Title     NVARCHAR(500) NOT NULL,
    Content   NVARCHAR(MAX),           -- Nội dung dài
    HtmlContent VARCHAR(MAX)           -- HTML content (ASCII)
);

-- Chú ý hiệu năng với MAX:
-- - Không thể là key trong index
-- - Storage khác: có thể lưu inline (nếu < 8000 bytes) hoặc LOB pages
-- - Dùng .WRITE() để update hiệu quả
UPDATE Articles
SET Content.WRITE(N' Thêm nội dung', DATALENGTH(Content)/2, 0)
WHERE ArticleId = 1;

text và ntext (Deprecated)

-- KHÔNG dùng nữa từ SQL Server 2005+
-- text: tương tự varchar(MAX) nhưng deprecated
-- ntext: tương tự nvarchar(MAX) nhưng deprecated

-- Nếu thấy trong code cũ, convert sang varchar(MAX)/nvarchar(MAX):
ALTER TABLE OldTable ALTER COLUMN OldTextCol NVARCHAR(MAX);

3. Kiểu ngày giờ (Date/Time Types)

KiểuBytesPhạm viĐộ chính xácTimezone
datetime81753-01-01 ~ 9999-12-313.33msKhông
smalldatetime41900-01-01 ~ 2079-06-061 phútKhông
datetime2(n)6-80001-01-01 ~ 9999-12-31100nsKhông
date30001-01-01 ~ 9999-12-311 ngàyKhông
time(n)3-500:00:00 ~ 23:59:59.9…100nsKhông
datetimeoffset(n)8-100001-01-01 ~ 9999-12-31100nsCó (+/-14h)
-- datetime - loại cũ, tránh dùng cho code mới
DECLARE @dt DATETIME = '2026-04-01 10:30:00.000';

-- datetime2 - khuyến nghị thay datetime
DECLARE @dt2 DATETIME2(7) = '2026-04-01 10:30:00.1234567';  -- 7 decimal seconds
DECLARE @dt2s DATETIME2(0) = '2026-04-01 10:30:00';         -- Chỉ đến giây

-- date - chỉ cần ngày, không cần giờ
DECLARE @d DATE = '2026-04-01';
DECLARE @birthDate DATE = '1990-05-15';

-- time - chỉ cần giờ
DECLARE @t TIME(0) = '10:30:00';     -- Đến giây
DECLARE @t7 TIME(7) = '10:30:00.1234567';  -- Cao nhất

-- datetimeoffset - khi cần lưu timezone
DECLARE @dto DATETIMEOFFSET = '2026-04-01 10:30:00 +07:00';
SELECT SWITCHOFFSET(@dto, '+00:00');  -- Convert sang UTC

-- Ví dụ thực tế
CREATE TABLE Orders (
    OrderId       INT PRIMARY KEY,
    OrderDate     DATE NOT NULL,          -- Chỉ cần ngày
    DeliveryTime  TIME(0),                -- Chỉ cần giờ giao
    CreatedAt     DATETIME2(7) NOT NULL DEFAULT SYSDATETIME(),  -- Timestamp đầy đủ
    UpdatedAt     DATETIME2(7),
    ScheduledAt   DATETIMEOFFSET          -- Nếu có múi giờ
);

-- So sánh datetime vs datetime2
SELECT 
    CAST('2026-04-01' AS DATETIME) AS old_datetime,
    CAST('2026-04-01' AS DATETIME2) AS new_datetime2;
-- datetime: '2026-04-01 00:00:00.000'
-- datetime2: '2026-04-01 00:00:00.0000000'

4. Kiểu nhị phân (Binary Types)

KiểuMô tảTối đa
binary(n)Fixed-length binary8,000 bytes
varbinary(n)Variable-length binary8,000 bytes
varbinary(MAX)Variable-length binary2 GB
imageDeprecated, thay bằng varbinary(MAX)2 GB
-- Dùng cho: hình ảnh, files, hashes, encrypted data
CREATE TABLE Documents (
    DocumentId    INT PRIMARY KEY,
    FileName      NVARCHAR(255) NOT NULL,
    FileContent   VARBINARY(MAX),                    -- File content
    ContentHash   BINARY(32),                        -- SHA-256 hash (32 bytes)
    Thumbnail     VARBINARY(MAX)
);

-- Lưu hash của password (KHÔNG lưu plain text!)
-- Trong thực tế nên hash ở application layer, không ở SQL

-- HASHBYTES để tạo hash
SELECT HASHBYTES('SHA2_256', N'password123');  -- Trả về varbinary(32)

-- Convert hex string sang binary và ngược lại
SELECT CONVERT(VARCHAR(64), HASHBYTES('SHA2_256', N'test'), 2);  -- Hex string

5. Kiểu đặc biệt

bit

-- bit: 0, 1, hoặc NULL. SQL Server tối ưu lưu 8 bit columns vào 1 byte
CREATE TABLE Settings (
    SettingId  INT PRIMARY KEY,
    IsActive   BIT NOT NULL DEFAULT 1,
    IsDeleted  BIT NOT NULL DEFAULT 0,
    IsPublic   BIT NOT NULL DEFAULT 0,
    HasExpiry  BIT NOT NULL DEFAULT 0
    -- 4 bit columns ở trên dùng chỉ 1 byte storage
);

-- Convert từ string
SELECT CAST('true' AS BIT);   -- Error! BIT không nhận 'true'
SELECT CAST(1 AS BIT);         -- 1
SELECT CAST(0 AS BIT);         -- 0
SELECT IIF(SomeCondition, CAST(1 AS BIT), CAST(0 AS BIT));

uniqueidentifier (GUID)

-- Lưu GUID (128-bit)
CREATE TABLE Sessions (
    SessionId   UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(),
    UserId      INT NOT NULL,
    CreatedAt   DATETIME2 NOT NULL DEFAULT SYSDATETIME()
);

-- NEWID() - random GUID (gây index fragmentation vì random)
SELECT NEWID();  -- e.g., '6F9619FF-8B86-D011-B42D-00C04FC964FF'

-- NEWSEQUENTIALID() - sequential GUID (dùng làm PK clustered index tốt hơn)
-- Chỉ dùng được trong DEFAULT constraint
CREATE TABLE AuditLogs (
    LogId      UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWSEQUENTIALID(),
    Action     NVARCHAR(100),
    CreatedAt  DATETIME2 DEFAULT SYSDATETIME()
);

-- So sánh NEWID() vs NEWSEQUENTIALID()
-- NEWID(): Hoàn toàn random, gây page splits khi INSERT nhiều
-- NEWSEQUENTIALID(): Tăng dần, giảm fragmentation như IDENTITY

-- Convert GUID sang string và ngược lại
DECLARE @id UNIQUEIDENTIFIER = NEWID();
SELECT CAST(@id AS VARCHAR(36));  -- '6F961...'
SELECT CAST('6F9619FF-8B86-D011-B42D-00C04FC964FF' AS UNIQUEIDENTIFIER);

xml

-- Lưu XML data với validation tùy chọn
CREATE TABLE Configurations (
    ConfigId  INT PRIMARY KEY,
    ConfigKey NVARCHAR(100),
    ConfigXml XML  -- Untyped XML
);

-- Insert XML
INSERT INTO Configurations VALUES (1, 'AppSettings', 
    '<settings><timeout>30</timeout><maxRetry>3</maxRetry></settings>');

-- Query XML với XQuery
SELECT 
    ConfigId,
    ConfigXml.value('(/settings/timeout)[1]', 'INT') AS timeout,
    ConfigXml.value('(/settings/maxRetry)[1]', 'INT') AS max_retry
FROM Configurations
WHERE ConfigKey = 'AppSettings';

-- XML methods: value(), query(), nodes(), exist(), modify()
DECLARE @xml XML = '<employees><emp id="1">Alice</emp><emp id="2">Bob</emp></employees>';

SELECT 
    emp.value('@id', 'INT') AS emp_id,
    emp.value('.', 'NVARCHAR(100)') AS emp_name
FROM @xml.nodes('/employees/emp') AS t(emp);

JSON (via OPENJSON - SQL Server 2016+)

-- SQL Server không có native JSON type, dùng NVARCHAR + OPENJSON
-- Validate JSON
SELECT ISJSON('{"name": "Alice", "age": 30}');  -- 1 (valid)
SELECT ISJSON('not json');  -- 0

-- JSON_VALUE - lấy scalar value
DECLARE @json NVARCHAR(MAX) = '{"name": "Alice", "age": 30, "city": "Hanoi"}';
SELECT JSON_VALUE(@json, '$.name');  -- 'Alice'
SELECT JSON_VALUE(@json, '$.age');   -- '30'

-- JSON_QUERY - lấy object hoặc array
DECLARE @json2 NVARCHAR(MAX) = '{"person": {"name": "Alice", "skills": ["SQL", "C#"]}}';
SELECT JSON_QUERY(@json2, '$.person');         -- '{"name": "Alice", "skills": [...]}'
SELECT JSON_QUERY(@json2, '$.person.skills');  -- '["SQL", "C#"]'

-- OPENJSON - parse JSON thành rows
SELECT *
FROM OPENJSON('{"orders": [{"id":1,"total":100}, {"id":2,"total":200}]}', '$.orders')
WITH (
    order_id    INT           '$.id',
    order_total DECIMAL(10,2) '$.total'
);

-- FOR JSON - convert query result sang JSON
SELECT EmployeeId, FullName, Salary
FROM Employees
FOR JSON AUTO;

SELECT EmployeeId, FullName, Salary
FROM Employees
FOR JSON PATH, ROOT('employees');

sql_variant

-- Có thể lưu nhiều kiểu dữ liệu khác nhau (tránh dùng nếu có thể)
DECLARE @v SQL_VARIANT;
SET @v = 42;
SELECT @v, SQL_VARIANT_PROPERTY(@v, 'BaseType');  -- 'int'
SET @v = 'Hello';
SELECT @v, SQL_VARIANT_PROPERTY(@v, 'BaseType');  -- 'varchar'

rowversion / timestamp

-- Tự động tăng khi row được INSERT/UPDATE, dùng cho optimistic concurrency
CREATE TABLE Products (
    ProductId   INT PRIMARY KEY,
    ProductName NVARCHAR(200),
    Price       DECIMAL(10,2),
    RowVer      ROWVERSION NOT NULL  -- Tự cập nhật, 8 bytes
);

-- timestamp là synonym của rowversion (deprecated alias)
-- Dùng để detect concurrency conflicts
SELECT ProductId, ProductName, CAST(RowVer AS BIGINT) AS version
FROM Products;

6. Unicode vs Non-Unicode

-- N prefix tạo Unicode literal
SELECT 'Xin chào';        -- Mất dấu nếu collation không hỗ trợ
SELECT N'Xin chào';       -- Unicode, luôn đúng

-- Collation ảnh hưởng đến sorting và comparison
SELECT * FROM sys.fn_helpcollations() WHERE name LIKE 'Vietnamese%';

-- Khai báo explicit collation
CREATE TABLE MultiLanguage (
    NameEn NVARCHAR(200) COLLATE Latin1_General_CI_AS,
    NameVn NVARCHAR(200) COLLATE Vietnamese_CI_AS
);

-- So sánh cross-collation cần COLLATE trong query
SELECT * FROM t1
JOIN t2 ON t1.Name = t2.Name COLLATE SQL_Latin1_General_CP1_CI_AS;

7. Implicit vs Explicit Conversion

CAST vs CONVERT vs TRY_CAST vs TRY_CONVERT

-- CAST - chuẩn ANSI SQL
SELECT CAST(42 AS VARCHAR(10));         -- '42'
SELECT CAST('2026-04-01' AS DATE);      -- 2026-04-01
SELECT CAST(3.14 AS INT);              -- 3 (truncation)

-- CONVERT - SQL Server specific, hỗ trợ format code cho ngày
SELECT CONVERT(VARCHAR(10), GETDATE(), 103);  -- '01/04/2026' (dd/mm/yyyy)
SELECT CONVERT(VARCHAR(10), GETDATE(), 120);  -- '2026-04-01' (yyyy-mm-dd)
SELECT CONVERT(VARCHAR(8),  GETDATE(), 112);  -- '20260401' (yyyymmdd)
SELECT CONVERT(INT, '42');

-- TRY_CAST - trả về NULL thay vì error nếu convert thất bại
SELECT TRY_CAST('abc' AS INT);          -- NULL (không báo lỗi)
SELECT TRY_CAST('42' AS INT);           -- 42

-- TRY_CONVERT - tương tự TRY_CAST với format code
SELECT TRY_CONVERT(DATE, '2026-13-01'); -- NULL (tháng 13 không tồn tại)
SELECT TRY_CONVERT(DATE, '2026-04-01'); -- 2026-04-01

-- Implicit conversion - SQL Server tự convert (CÓ THỂ GÂY VẤN ĐỀ!)
SELECT * FROM Employees WHERE EmployeeId = '42';
-- SQL Server convert '42' sang INT: OK nhưng có thể ảnh hưởng performance

-- Implicit conversion GÂY VẤN ĐỀ với indexes:
-- Nếu column là VARCHAR và bạn so sánh với INT, SQL Server phải convert TỪNG GIÁ TRỊ TRONG TABLE
-- -> Index không được dùng -> Table scan!
SELECT * FROM Employees WHERE VarcharId = 42;  -- BAD: convert mỗi row
SELECT * FROM Employees WHERE VarcharId = '42'; -- GOOD: dùng index

-- Kiểm tra implicit conversion với execution plan:
-- Tìm "CONVERT_IMPLICIT" trong execution plan để phát hiện vấn đề

Bảng Conversion

From \ ToINTVARCHARDATEFLOATBIT
INT-✅ Explicit✅ Implicit✅ Implicit
VARCHAR✅ Explicit-✅ Explicit✅ Explicit
DATE✅ Explicit-
FLOAT✅ Explicit (truncate)✅ Explicit-✅ Implicit

8. Data Type Best Practices

-- 1. Chọn kiểu nhỏ nhất phù hợp
-- BAD: dùng BIGINT cho tất cả
CREATE TABLE BadDesign (OrderId BIGINT, Status BIGINT, YearsExp BIGINT);

-- GOOD: chọn đúng kích thước
CREATE TABLE GoodDesign (
    OrderId   INT      NOT NULL,  -- Đủ cho hầu hết use cases
    Status    TINYINT  NOT NULL,  -- 0-255 là đủ cho status
    YearsExp  TINYINT  NOT NULL   -- 0-120 năm kinh nghiệm
);

-- 2. Không dùng float/real cho tiền tệ
-- BAD:
CREATE TABLE BadMoney (Price FLOAT);  -- Sai về độ chính xác!

-- GOOD:
CREATE TABLE GoodMoney (Price DECIMAL(19, 4));  -- Chính xác

-- 3. Luôn dùng nvarchar cho user-input text trong ứng dụng đa ngôn ngữ
CREATE TABLE UserProfiles (
    UserId    INT           PRIMARY KEY,
    UserName  VARCHAR(50)   NOT NULL,       -- Username: ASCII-safe
    FullName  NVARCHAR(200) NOT NULL,       -- Tên: cần Unicode
    Email     VARCHAR(320)  NOT NULL,       -- Email: ASCII-safe
    Bio       NVARCHAR(MAX)                 -- Bio: user input
);

-- 4. Dùng DATE thay DATETIME khi không cần time component
-- BAD: lưu ngày sinh với DATETIME
CREATE TABLE BadDates (BirthDate DATETIME);  -- Lưu '1990-05-15 00:00:00.000'

-- GOOD:
CREATE TABLE GoodDates (BirthDate DATE);  -- Chỉ '1990-05-15', tiết kiệm 5 bytes/row

-- 5. Dùng DATETIME2 thay DATETIME cho timestamps mới
-- datetime: range 1753+, precision 3.33ms, 8 bytes
-- datetime2: range 0001+, precision 100ns, 6-8 bytes
CREATE TABLE AuditLog (
    CreatedAt DATETIME2(7) NOT NULL DEFAULT SYSDATETIME()
);

-- 6. Tránh varchar(MAX) khi biết trước giới hạn
-- BAD: dùng MAX cho mọi thứ
CREATE TABLE BadVarchar (Name NVARCHAR(MAX), Code NVARCHAR(MAX));

-- GOOD: giới hạn phù hợp
CREATE TABLE GoodVarchar (
    Name NVARCHAR(200),    -- Tên người
    Code NVARCHAR(20),     -- Mã code ngắn
    Description NVARCHAR(2000),  -- Mô tả
    Content NVARCHAR(MAX)  -- Nội dung thực sự cần MAX
);

-- 7. NULL vs NOT NULL
-- Tránh allow NULL trừ khi thực sự cần thiết về mặt business logic
-- NULL gây phức tạp trong queries và có thể ảnh hưởng index efficiency

Bảng tổng hợp chọn kiểu dữ liệu

Tình huốngKhuyến nghị
ID / Primary Key (web app)INT hoặc BIGINT
ID / Primary Key (distributed)UNIQUEIDENTIFIER với NEWSEQUENTIALID()
Tiền tệDECIMAL(19, 4)
Phần trăm, tỉ lệDECIMAL(5, 4)
Tọa độ GPSFLOAT
Tên người, địa chỉNVARCHAR(n)
Email, usernameVARCHAR(n)
Mã code (fixed)CHAR(n)
Ngày sinh, ngày hợp đồngDATE
Timestamp tạo/sửaDATETIME2(7)
Thời gian với timezoneDATETIMEOFFSET
Flag on/offBIT
Trạng thái nhỏ (< 255 options)TINYINT
File, image, binary dataVARBINARY(MAX)
Config JSON/XMLNVARCHAR(MAX) (JSON) hoặc XML
Audit/concurrent controlROWVERSION