Ms Sql Server Start

Типи даних у MS SQL Server

Всеохоплююче дослідження типів даних Microsoft SQL Server - цілочисельні, текстові, дійсні, дата/час та спеціальні типи

Типи даних у MS SQL Server

Вступ: Навіщо потрібні різні типи даних?

Уявіть, що ви будуєте будинок. Чи використовували б ви однаковий матеріал для фундаменту, стін та даху? Звичайно ні — кожна частина будівлі потребує матеріалу з конкретними властивостями. Так само і з базами даних: різні типи даних існують не просто для різноманітності, а для оптимізації продуктивності, економії пам'яті та забезпечення точності.

Ключова ідея: Правильний вибір типу даних — це не просто "щоб працювало". Це рішення, яке впливає на:
  • Продуктивність — швидкість виконання запитів
  • Розмір бази даних — використання дискового простору
  • Точність — коректність обчислень
  • Цілісність — валідація даних на рівні БД

Реальний сценарій: E-commerce система

Припустимо, ми розробляємо інтернет-магазин. Подивимося, як вибір типу даних впливає на різні поля:

ПолеНеправильний вибірНаслідкиПравильний вибір
product_idVARCHAR(50)Повільні JOIN'и, 50 байт на IDINT (4 байти)
priceFLOATПомилки округлення ($0.1 + $0.2 ≠ $0.3)DECIMAL(10,2)
product_nameNVARCHAR(MAX)2 ГБ на назву товару!NVARCHAR(200)
in_stockTINYINTНезрозуміле значення (0/1/2/3?)BIT (TRUE/FALSE)
Розмір має значення: Якщо у вас 10 мільйонів товарів, і ви використовуєте VARCHAR(50) замість INT для ID, ви витратите додатково 460 МБ лише на один стовпець!

Історична еволюція типів даних

Типи даних у SQL Server еволюціонували разом з потребами індустрії:

Loading diagram...
timeline
    title Еволюція типів даних у SQL Server
    section 1990s
        SQL Server 6.5 : char, varchar, int, datetime, text, image
    section 2000s
        SQL Server 2005 : xml, varchar(MAX), новий GUID
    section 2008
        SQL Server 2008 : date, time, datetime2, datetimeoffset<br/>geography, geometry, hierarchyid
    section 2012+
        SQL Server 2012+ : Columnstore, Temporal tables<br/>JSON support (2016)
Про deprecated типи: TEXT, NTEXT та IMAGE застаріли з SQL Server 2005. Замість них використовуйте VARCHAR(MAX), NVARCHAR(MAX) та VARBINARY(MAX) відповідно.

Цілочисельні типи даних

Навіщо потрібні різні розміри цілих чисел?

Проблема: Якби існував лише один цілочисельний тип, ми б або:

  1. Витрачали надто багато пам'яті на маленькі числа
  2. Або не мали б достатньо місця для великих чисел

Рішення: SQL Server надає 5 цілочисельних типів різних розмірів.

Таблиця цілочисельних типів

ТипДіапазонПам'ятьТипове використання
TINYINT0 до 2551 байтВік людини, кількість днів у місяці, відсотки (0-100)
SMALLINT-32,768 до 32,7672 байтиРік народження, номер кімнати
INT-2,147,483,648 до 2,147,483,6474 байтиPrimary Keys, кількість товарів, ціна в копійках
BIGINT-9,223,372,036,854,775,808 до 9,223,372,036,854,775,8078 байтTimestamps (Unix time), великі суми грошей
BIT0, 1 або NULL1 біт*Boolean значення (так/ні, активний/неактивний)
Особливість BIT: SQL Server оптимізує зберігання — перші 8 BIT стовпців займають 1 байт разом, наступні 8 — ще 1 байт, і так далі.

Практичний приклад: Вибір правильного типу

-- ❌ НЕПРАВИЛЬНО: Використання BIGINT для всього
CREATE TABLE Products_Bad (
    product_id BIGINT,           -- 8 байт (надлишок!)
    category_id BIGINT,          -- 8 байт (надлишок!)
    stock_quantity BIGINT,       -- 8 байт (навряд чи буде 9 квінтильйонів товарів)
    is_active BIGINT             -- 8 байт для TRUE/FALSE?!
);

Проблема: Для 1 млн товарів ця таблиця займе 32 МБ лише на ці 4 поля.

-- ✅ ПРАВИЛЬНО: Оптимізоване використання
CREATE TABLE Products_Good (
    product_id INT,              -- 4 байти (до 2 млрд товарів)
    category_id SMALLINT,        -- 2 байти (до 32K категорій)
    stock_quantity INT,          -- 4 байти (достатньо для складу)
    is_active BIT                -- 1 біт
);

Виграш: Для 1 млн товарів ця таблиця займе лише 10 МБ — економія 68%!

Анатомія коду: IDENTITY та автоінкремент

CREATE TABLE Students (
    Id INT IDENTITY(1,1) PRIMARY KEY,  -- Починається з 1, збільшується на 1
    LastName NVARCHAR(50),
    FirstName NVARCHAR(50)
);

Пояснення конструкції IDENTITY(1,1):

  • IDENTITY — вказує, що це автоінкрементне поле
  • Перший параметр (1, ...)SEED (початкове значення): з якого числа починати (1)
  • Другий параметр (..., 1)INCREMENT (крок): на скільки збільшувати (1)
Практичний кейс: Якщо ви імпортуєте дані і хочете уникнути конфліктів ID, можете використати IDENTITY(10000, 1) — нові записи почнуться з 10,000.

Приклад з різним кроком:

-- Парні номери замовлень для філії A
CREATE TABLE Orders_BranchA (
    order_id INT IDENTITY(2, 2)  -- 2, 4, 6, 8, 10...
);

-- Непарні номери замовлень для філії B
CREATE TABLE Orders_BranchB (
    order_id INT IDENTITY(1, 2)  -- 1, 3, 5, 7, 9...
);

Коли використовувати кожен тип: Decision Tree

Loading diagram...
graph TD
    A[Потрібен цілочисельний тип?] --> B{Може бути від'ємним?}
    B -->|Ні, тільки 0-255| C[TINYINT]
    B -->|Так| D{Розмір значення?}
    D -->|Малий<br/>до ±32K| E[SMALLINT]
    D -->|Середній<br/>до ±2 млрд| F[INT]
    D -->|Величезний<br/>більше ±2 млрд| G[BIGINT]
    A --> H{TRUE/FALSE?}
    H -->|Так| I[BIT]

    style C fill:#10b981,color:#fff
    style E fill:#3b82f6,color:#fff
    style F fill:#f59e0b,color:#333
    style G fill:#ef4444,color:#fff
    style I fill:#8b5cf6,color:#fff

Текстові типи даних

Проблема: Фіксована vs Змінна довжина

Сценарій: Ми зберігаємо коди країн (наприклад, "UA", "US", "GB").

CREATE TABLE Countries (
    code CHAR(2)  -- Завжди виділяє 2 байти
);
INSERT INTO Countries VALUES ('UA');  -- Зберігається як 'UA'

Пам'ять: Завжди 2 байти, навіть якщо код з 1 символу.
Переваги: Швидше (передбачуваний розмір)
Недоліки: Втрата місця при коротких значеннях

Unicode vs Non-Unicode: NVARCHAR vs VARCHAR

Що таке Unicode?
Unicode (UTF-16) — це стандарт кодування, що підтримує всі мови світу: кирилицю, ієрогліфи, арабську, емодзі тощо.

ТипКодуванняБайт на символПідтримуєКоли використовувати
CHAR(n)ASCII/ANSI1Лише латиницю та базові символиКоди, MD5 хеші, старі англомовні системи
VARCHAR(n)ASCII/ANSI1Лише латиницю та базові символиEmail, URL (якщо точно лише латиниця)
NCHAR(n)Unicode2Всі мовиМіжнародні коди з символами
NVARCHAR(n)Unicode2Всі мовиІмена, адреси, будь-який текст від користувачів
Критична помилка: Використання VARCHAR для українських імен призведе до "кракозябр" (���) або втрати даних!

Приклад проблеми з VARCHAR:

-- ❌ НЕПРАВИЛЬНО для української мови
CREATE TABLE Users (
    name VARCHAR(100)
);
INSERT INTO Users VALUES ('Тарас Шевченко');
-- Може зберегтися як "????? ????????" або взагалі пусто!
-- ✅ ПРАВИЛЬНО для будь-якої мови
CREATE TABLE Users (
    name NVARCHAR(100)
);
INSERT INTO Users VALUES ('Тарас Шевченко');  -- Зберігається коректно
INSERT INTO Users VALUES ('田中太郎');        -- Також працює!
INSERT INTO Users VALUES ('محمد');           -- І це теж!

VARCHAR(MAX) vs VARCHAR(8000)

SQL Server має обмеження: VARCHAR(n) може бути максимум 8000 байт, NVARCHAR(n)4000 символів.

Зберігання: В основній сторінці даних (8 КБ)
Продуктивність: Швидка
Коли використовувати: Короткі описи, коментарі до 8000 символів

Deprecated типи (не використовуйте!):

-- ❌ ЗАСТАРІЛО з SQL Server 2005
CREATE TABLE Articles_Old (
    content TEXT        -- Замініть на VARCHAR(MAX)
);

-- ✅ СУЧАСНИЙ ПІДХІД
CREATE TABLE Articles_New (
    content VARCHAR(MAX)  -- До 2 ГБ, кращі функції, підтримка JSON
);

Дійсні типи даних

FLOAT vs REAL: Швидкість чи точність?

Проблема: Нам потрібно зберігати числа з комою. Як вибрати між FLOAT та DECIMAL?

ТипДіапазонТочністьПам'ятьВикористання
REAL±3.40E+38~7 знаків4 байтиНаукові розрахунки, сенсорні дані
FLOAT±1.79E+308~15 знаків8 байтGPS координати, складні математичні обчислення
Критична проблема з FLOAT: Це наближені числа! Можуть бути помилки округлення.

Демонстрація проблеми:

DECLARE @a FLOAT = 0.1;
DECLARE @b FLOAT = 0.2;
DECLARE @sum FLOAT = @a + @b;

SELECT @sum;  -- Очікуємо 0.3
-- Результат: 0.30000000000000004  ❌

Чому так відбувається?
FLOAT використовує бінарне представлення. Деякі десяткові дроби (як 0.1) не можуть бути точно представлені в бінарній формі, так само як 1/3 не можна точно записати в десятковій (0.333...).

Коли використовувати FLOAT

Золоте правило: Використовуйте FLOAT лише для наукових даних, де потрібна швидкість, а не абсолютна точність.

Приклади правильного використання:

-- ✅ Метео станція (температура, тиск)
CREATE TABLE SensorData (
    temperature REAL,      -- Точність ±0.01°C достатня
    pressure REAL,
    humidity REAL
);

-- ✅ GPS координати
CREATE TABLE Locations (
    latitude FLOAT,        -- Точність 15 знаків достатня
    longitude FLOAT
);

Приклади НЕПРАВИЛЬНОГО використання:

-- ❌ НЕ використовуйте для фінансів!
CREATE TABLE Invoices_Bad (
    total_amount FLOAT     -- Помилки округлення накопичуються!
);

Типи даних для дати та часу

Еволюція типів дати

До SQL Server 2008 існував лише DATETIME. Але він мав проблеми:

  • Занадто велика точність для простих дат
  • Відсутність підтримки часових поясів
  • Неможливість зберігати лише час або лише дату

SQL Server 2008 вирішив це, додавши 5 нових типів!

Порівняльна таблиця

ТипФорматДіапазонТочністьПам'ятьTimezone
DATEYYYY-MM-DD0001-01-01 до 9999-12-31День3 байти
TIMEHH:MM:SS.nnnnnnn00:00:00 до 23:59:59100 наносекунд3-5 байт
SMALLDATETIMEYYYY-MM-DD HH:MM1900-01-01 до 2079-06-06Хвилина4 байти
DATETIMEYYYY-MM-DD HH:MM:SS.nnn1753-01-01 до 9999-12-313.33 мс8 байт
DATETIME2YYYY-MM-DD HH:MM:SS.nnnnnnn0001-01-01 до 9999-12-31100 наносекунд6-8 байт
DATETIMEOFFSETYYYY-MM-DD HH:MM:SS.nnnnnnn +HH:MM0001-01-01 до 9999-12-31100 наносекунд10 байт

Практичні сценарії використання

CREATE TABLE Employees (
    employee_id INT PRIMARY KEY,
    birth_date DATE  -- Не потрібен час!
);
INSERT INTO Employees VALUES (1, '1985-05-15');

Проблема часових поясів

Сценарій: Компанія має офіси в Києві, Лондоні та Нью-Йорку. Зустріч призначена на "14:00".

CREATE TABLE Meetings (
    meeting_time DATETIME2
);
INSERT INTO Meetings VALUES ('2024-02-06 14:00:00');

Проблема: 14:00 де? У Києві це 12:00 у Лондоні і 07:00 у Нью-Йорку!


DECIMAL та NUMERIC: Фінансові дані

DECIMAL(p, s) — Анатомія параметрів

DECIMAL(10, 2)
        ↑   ↑
        │   └─ Scale (s): кількість цифр ПІСЛЯ коми (0-2)
        └───── Precision (p): ЗАГАЛЬНА кількість цифр (0-38)

Приклад декодування:

  • DECIMAL(10, 2) означає: максимум 10 цифр загалом, з них 2 після коми
    • Максимальне число: 99999999.99 (8 цифр до коми + 2 після)
    • Мінімальне: -99999999.99

Практичні приклади

-- Ціни в магазині (до 999,999.99 грн)
CREATE TABLE Products (
    price DECIMAL(8, 2)  -- 6 цифр до коми, 2 після
);
INSERT INTO Products VALUES (1299.99);    -- ✅ OK
INSERT INTO Products VALUES (1000000.00); -- ❌ Помилка! Перевищено precision
-- Відсотки (0.00% до 100.00%)
CREATE TABLE Discounts (
    discount_percent DECIMAL(5, 2)  -- Макс 100.00
);
INSERT INTO Discounts VALUES (15.50);  -- ✅ 15.50%
INSERT INTO Discounts VALUES (100.00); -- ✅ 100%
INSERT INTO Discounts VALUES (100.01); -- ❌ Помилка!

DECIMAL vs FLOAT для фінансів

DECLARE @balance FLOAT = 1000.00;
DECLARE @withdrawal FLOAT = 999.90;
DECLARE @remaining FLOAT = @balance - @withdrawal;

SELECT @remaining;
-- Очікуємо: 0.10
-- Отримуємо: 0.09999999999999964 😱
-- На банківському рахунку це неприпустимо!
Золоте правило фінансів: ЗАВЖДИ використовуйте DECIMAL або MONEY для грошових сум. НІКОЛИ не використовуйте FLOAT!

MONEY та SMALLMONEY

Спеціалізовані типи для валют

ТипДіапазонТочністьПам'ять
SMALLMONEY±214,748.36484 знаки після коми4 байти
MONEY±922,337,203,685,477.58074 знаки після коми8 байт
-- Покупки в магазині
CREATE TABLE Transactions (
    amount MONEY
);
INSERT INTO Transactions VALUES ($1299.99);
INSERT INTO Transactions VALUES (1299.99);  -- Символ $ опціональний

MONEY vs DECIMAL: Що вибрати?

Переваги:

  • Оптимізований для валют
  • Автоматичне округлення до 4 знаків

Недоліки:

  • Фіксовані 4 знаки після коми
  • Прив'язаний до однієї валюти

Рекомендація: Для сучасних систем краще використовувати DECIMAL(19, 4) — це дає більше контролю.


Binary типи даних

Коли потрібні бінарні дані?

Бінарні типи використовуються для зберігання:

  • Безпечних хешів паролів (bcrypt, Argon2, PBKDF2)
  • Файлів (зображення, PDF, документи)
  • Криптографічних ключів та сертифікатів
  • Цифрових підписів
  • Бінарних протоколів та токенів
ТипМаксимальний розмірПам'ятьВикористання
BINARY(n)8000 байтФіксована (n байт)Хеші фіксованої довжини, токени
VARBINARY(n)8000 байтЗміннаХеші паролів, невеликі файли, ключі
VARBINARY(MAX)2 ГБЗміннаВеликі файли, зображення, документи
Deprecated: Тип IMAGE застарілий з SQL Server 2005. Використовуйте VARBINARY(MAX).

Практичні приклади

-- ПРАВИЛЬНО: Використання bcrypt/Argon2 на рівні застосунку
CREATE TABLE Users (
    user_id INT PRIMARY KEY,
    username NVARCHAR(50) UNIQUE NOT NULL,
    -- bcrypt створює хеш ~60 символів (залежно від версії)
    password_hash VARBINARY(128) NOT NULL,  -- Достатньо місця для bcrypt/Argon2
    salt VARBINARY(32),                     -- Salt зберігається окремо (опційно)
    created_at DATETIME2 DEFAULT SYSDATETIME()
);
КРИТИЧНО: SQL Server НЕ має вбудованого bcrypt або Argon2!
Хешування паролів ЗАВЖДИ має відбуватися на рівні застосунку (C#, Python, Node.js).

C# приклад (з BCrypt.Net):

using BCrypt.Net;

// Хешування пароля (автоматично генерує salt)
string password = "MySecurePassword123";
string passwordHash = BCrypt.HashPassword(password, workFactor: 12);
// Результат: $2a$12$R9h/cIPz0gi.URNNX3kh2O... (60 байт)

// Збереження в БД
byte[] hashBytes = Encoding.UTF8.GetBytes(passwordHash);
// INSERT INTO Users (username, password_hash) VALUES ('john_doe', @hashBytes);

// Перевірка пароля
bool isValid = BCrypt.Verify(password, passwordHash);  // true

::

-- ❌ НІКОЛИ НЕ РОБІТЬ ТАК ДЛЯ ПАРОЛІВ!
DECLARE @password NVARCHAR(50) = 'MySecurePassword123';
DECLARE @hash BINARY(32) = HASHBYTES('SHA2_256', @password);

-- ПРОБЛЕМИ:
-- 1. SHA-256 занадто ШВИДКИЙ → можна перебрати мільярди паролів/сек
-- 2. Без SALT → однакові паролі мають однаковий хеш
-- 3. Вразливий до rainbow tables
-- 4. MD5 взагалі зламаний, не використовуйте!
Чому SHA-256 небезпечний для паролів?Сучасна відеокарта може обчислити ~10 млрд SHA-256 хешів/секунду.
Це означає, що 8-символьний пароль можна зламати за хвилини.Натомість, bcrypt навмисно повільний (керований work factor):
  • bcrypt з work factor 12: ~3-5 хешів/секунду
  • Той самий пароль зламати буде роки замість хвилин
-- Аватарки користувачів
CREATE TABLE UserProfiles (
    user_id INT PRIMARY KEY,
    avatar VARBINARY(MAX),              -- До 2 ГБ
    avatar_mime_type VARCHAR(50),        -- 'image/jpeg', 'image/png'
    avatar_size_bytes INT,
    uploaded_at DATETIME2 DEFAULT SYSDATETIME()
);

-- Вставка зображення
INSERT INTO UserProfiles (user_id, avatar, avatar_mime_type)
VALUES (
    1,
    (SELECT * FROM OPENROWSET(BULK 'C:\images\avatar.jpg', SINGLE_BLOB) AS img),
    'image/jpeg'
);
-- Токени для відновлення пароля або API keys
CREATE TABLE PasswordResetTokens (
    token_id UNIQUEIDENTIFIER DEFAULT NEWID(),
    user_id INT FOREIGN KEY REFERENCES Users(user_id),
    -- Хеш токену (для безпеки не зберігаємо plain text)
    token_hash BINARY(32) NOT NULL,      -- SHA-256 прийнятний для токенів!
    expires_at DATETIME2 NOT NULL,
    created_at DATETIME2 DEFAULT SYSDATETIME()
);
Для токенів SHA-256 прийнятний, бо:
  • Токени генеруються криптографічно випадково (не паролі користувачів)
  • Токени одноразові та короткотермінові
  • Атака перебором неможлива через високу ентропію

::

Рекомендовані алгоритми для паролів (2024+)

АлгоритмРікБезпечністьWork FactorВикористання
Argon2id2015⭐⭐⭐⭐⭐ НайкращийНалаштовується (memory + time)Рекомендовано OWASP
bcrypt1999⭐⭐⭐⭐ Дуже добрий10-12 (2024)Широко використовується
scrypt2009⭐⭐⭐⭐ Дуже добрийНалаштовуєтьсяMemory-hard
PBKDF22000⭐⭐⭐ Прийнятний600,000+ ітераційЗастарілий, але OK
SHA-2562001⭐ НЕБЕЗПЕЧНОN/A❌ НЕ для паролів
MD51991☠️ ЗЛАМАНИЙN/A❌ НЕ використовувати
OWASP Рекомендація 2024:
  1. Перший вибір: Argon2id (memory cost: 19MB, time cost: 2, parallelism: 1)
  2. Альтернатива: bcrypt з work factor ≥ 12
  3. Legacy системи: PBKDF2-SHA256 з ≥600,000 ітераціями

Приклад: Правильна архітектура

-- Таблиця користувачів
CREATE TABLE Users (
    user_id INT IDENTITY(1,1) PRIMARY KEY,
    email NVARCHAR(100) UNIQUE NOT NULL,
    username NVARCHAR(50) UNIQUE NOT NULL,

    -- Хеш створений bcrypt/Argon2 на рівні застосунку
    password_hash VARBINARY(128) NOT NULL,

    -- Метадані безпеки
    password_changed_at DATETIME2,
    failed_login_attempts TINYINT DEFAULT 0,
    account_locked_until DATETIME2,

    created_at DATETIME2 DEFAULT SYSDATETIME(),
    updated_at DATETIME2 DEFAULT SYSDATETIME()
);

-- Аудит спроб авторизації
CREATE TABLE LoginAttempts (
    attempt_id BIGINT IDENTITY(1,1) PRIMARY KEY,
    user_id INT FOREIGN KEY REFERENCES Users(user_id),
    success BIT NOT NULL,
    ip_address VARCHAR(45),  -- IPv6 підтримка
    user_agent NVARCHAR(500),
    attempted_at DATETIME2 DEFAULT SYSDATETIME()
);

Спеціальні типи даних

XML — Структуровані документи

SQL Server має вбудовану підтримку XML з можливістю індексування та querying!

CREATE TABLE ProductCatalog (
    product_id INT PRIMARY KEY,
    specifications XML
);

INSERT INTO ProductCatalog VALUES (1,
'<product>
    <name>Laptop</name>
    <specs>
        <cpu>Intel i7</cpu>
        <ram>16GB</ram>
    </specs>
</product>');

-- XQuery для пошуку
SELECT specifications.query('/product/specs/cpu')
FROM ProductCatalog
WHERE product_id = 1;

GEOGRAPHY та GEOMETRY — Просторові дані

GEOGRAPHY: Для реальних координат на Землі (широта/довгота).
GEOMETRY: Для двовимірних координат на площині.

-- Локації ресторанів
CREATE TABLE Restaurants (
    restaurant_id INT PRIMARY KEY,
    name NVARCHAR(100),
    location GEOGRAPHY
);

-- Київ, Майдан Незалежності
INSERT INTO Restaurants VALUES (
    1,
    'Центральне кафе',
    GEOGRAPHY::Point(50.4501, 30.5234, 4326)  -- SRID 4326 = WGS84
);

-- Знайти ресторани в радіусі 5 км
DECLARE @myLocation GEOGRAPHY = GEOGRAPHY::Point(50.4547, 30.5238, 4326);

SELECT name, location.STDistance(@myLocation) / 1000 AS distance_km
FROM Restaurants
WHERE location.STDistance(@myLocation) <= 5000;

UNIQUEIDENTIFIER — Глобально унікальні ідентифікатори

CREATE TABLE Orders (
    order_guid UNIQUEIDENTIFIER DEFAULT NEWID(),  -- Автогенерація GUID
    customer_id INT
);

-- GUID: 6F9619FF-8B86-D011-B42D-00C04FC964FF
-- Унікальний в УСЬОМУ СВІТІ, ймовірність колізії практично 0
Коли використовувати GUID:
  • Розподілені системи (мікросервіси)
  • Реплікація між серверами
  • Безпека (непередбачувані ID)
Недоліки:
  • 16 байт (проти 4 байт у INT)
  • Повільніші індекси

Вибір правильного типу даних: Decision Matrix

Алгоритм прийняття рішення

Loading diagram...
graph TD
    Start[Яке поле створюємо?] --> Numbers{Числа?}
    Numbers -->|Так| Integer{Ціле число?}
    Integer -->|Так| Range{Діапазон?}
    Range -->|0-100| TinyInt[TINYINT]
    Range -->|Рік, номер| SmallInt[SMALLINT]
    Range -->|ID, quantity| Int[INT]
    Range -->|Timestamp, huge| BigInt[BIGINT]

    Integer -->|Ні| Money{Гроші?}
    Money -->|Так| UseMoney[DECIMAL p,2<br/>або MONEY]
    Money -->|Ні| Science{Наука?}
    Science -->|Так| UseFloat[FLOAT/REAL]
    Science -->|Ні| UseDecimal[DECIMAL p,s]

    Numbers -->|Ні| Text{Текст?}
    Text -->|Так| Unicode{Мультимовність?}
    Unicode -->|Так| Size1{Розмір?}
    Size1 -->|Фіксований| NChar[NCHAR n]
    Size1 -->|Змінний| NVarchar[NVARCHAR n]
    Size1 -->|Великий| NVarcharMax[NVARCHAR MAX]

    Unicode -->|Ні латиниця| Size2{Розмір?}
    Size2 -->|Фіксований| Char[CHAR n]
    Size2 -->|Змінний| Varchar[VARCHAR n]
    Size2 -->|Великий| VarcharMax[VARCHAR MAX]

    Text -->|Ні| DateTime{Дата/час?}
    DateTime -->|Лише дата| UseDate[DATE]
    DateTime -->|Лише час| UseTime[TIME]
    DateTime -->|Дата+час| TZ{Timezone?}
    TZ -->|Так| UseDateTimeOffset[DATETIMEOFFSET]
    TZ -->|Ні| UseDateTime2[DATETIME2]

    DateTime -->|Ні| Binary{Бінарні?}
    Binary -->|Так| BinSize{Розмір?}
    BinSize -->|Малий| VarBinary[VARBINARY n]
    BinSize -->|Великий| VarBinaryMax[VARBINARY MAX]

    Binary -->|Ні| Special{Спеціальні?}
    Special -->|XML| UseXML[XML]
    Special -->|GPS| UseGeo[GEOGRAPHY]
    Special -->|GUID| UseGuid[UNIQUEIDENTIFIER]

    style TinyInt fill:#10b981,color:#fff
    style Int fill:#f59e0b,color:#333
    style UseMoney fill:#ef4444,color:#fff
    style NVarchar fill:#3b82f6,color:#fff
    style UseDateTime2 fill:#8b5cf6,color:#fff

Чеклист оптимізації

Перед створенням поля запитайте себе:
  1. Який діапазон значень? (вибір між INT, SMALLINT, BIGINT)
  2. Потрібна точність чи швидкість? (DECIMAL vs FLOAT)
  3. Мультимовність? (NVARCHAR vs VARCHAR)
  4. Фіксований чи змінний розмір? (CHAR vs VARCHAR)
  5. Чи змінюватиметься це поле часто? (вплив на індекси)
  6. Скільки таких записів буде? (1000 vs 1 млн — різниця у впливі на розмір БД)

Практичні завдання

Завдання 1: Оптимізація таблиці

Дано неоптимальна таблиця:

CREATE TABLE Students_Bad (
    id BIGINT,
    first_name VARCHAR(MAX),
    last_name VARCHAR(MAX),
    age BIGINT,
    gpa FLOAT,
    is_active BIGINT,
    birth_date DATETIME
);

Завдання: Переробіть таблицю, вибравши оптимальні типи даних. Поясніть кожне рішення.

💡 Розв'язок
CREATE TABLE Students_Good (
    id INT IDENTITY(1,1) PRIMARY KEY,     -- INT достатньо, автоінкремент
    first_name NVARCHAR(50),              -- Unicode для імен, розумна довжина
    last_name NVARCHAR(50),               -- Unicode для прізвищ
    age TINYINT,                          -- 0-255, вік не перевищить
    gpa DECIMAL(3, 2),                    -- Фіксована точка: 0.00-4.00
    is_active BIT,                        -- TRUE/FALSE
    birth_date DATE                       -- Не потрібен час
);

Економія пам'яті на 1 млн студентів:

  • id: 4 МБ замість 8 МБ
  • first_name/last_name: динамічна економія
  • age: 1 МБ замість 8 МБ
  • gpa: 5 МБ замість 8 МБ
  • is_active: 0.125 МБ замість 8 МБ
  • birth_date: 3 МБ замість 8 МБ

Загальна економія: ~30+ МБ на мільйон записів!

Завдання 2: E-commerce БД

Створіть таблиці для інтернет-магазину з полями:

  1. Products: ID, назва, опис, ціна, кількість на складі, активність
  2. Orders: ID замовлення, дата/час, загальна сума, статус
  3. Customers: ID, ім'я, email, дата реєстрації, адреса
💡 Розв'язок
CREATE TABLE Products (
    product_id INT IDENTITY(1,1) PRIMARY KEY,
    name NVARCHAR(200) NOT NULL,
    description NVARCHAR(MAX),
    price DECIMAL(10, 2) NOT NULL,
    stock_quantity INT DEFAULT 0,
    is_active BIT DEFAULT 1
);

CREATE TABLE Customers (
    customer_id INT IDENTITY(1,1) PRIMARY KEY,
    first_name NVARCHAR(50) NOT NULL,
    last_name NVARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,  -- Email лише латиниця
    registered_at DATETIME2 DEFAULT SYSDATETIME(),
    shipping_address NVARCHAR(500)
);

CREATE TABLE Orders (
    order_id UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY,  -- GUID для безпеки
    customer_id INT FOREIGN KEY REFERENCES Customers(customer_id),
    order_date DATETIMEOFFSET DEFAULT SYSDATETIMEOFFSET(),  -- З timezone
    total_amount DECIMAL(12, 2) NOT NULL,
    status NVARCHAR(20) DEFAULT 'Pending'
);

JSON у MS SQL Server

Історія підтримки JSON

JSON (JavaScript Object Notation) став де-факто стандартом для обміну даними між системами. Microsoft додала підтримку JSON у SQL Server 2016, щоб конкурувати з NoSQL базами даних та PostgreSQL.

Loading diagram...
timeline
    title Підтримка JSON у СУБД
    section 2009-2012
        MongoDB : Нативна JSON підтримка<br/>NoSQL революція
        PostgreSQL 9.2 : JSON тип даних (2012)
    section 2014-2016
        PostgreSQL 9.4 : JSONB (binary JSON, 2014)
        SQL Server 2016 : JSON функції (2016)
    section 2017+
        SQL Server 2017+ : JSON покращення<br/>SQL:2016 стандарт

Ключова особливість: Немає окремого типу JSON!

На відміну від PostgreSQL та SQL:2016 стандарту, SQL Server НЕ має окремого типу даних JSON.
JSON зберігається як звичайний NVARCHAR(MAX) текст.
СУБДТип даних JSONЗберіганняВалідація
PostgreSQLJSON, JSONBБінарний формат (JSONB)✅ Автоматична
SQL Server❌ НемаєNVARCHAR(MAX) (текст)✅ Через функції ISJSON()
MySQLJSONБінарний формат✅ Автоматична
OracleJSONРізні (BLOB, CLOB, VARCHAR2)✅ Через constraints

Зберігання JSON у SQL Server

-- JSON зберігається як звичайний текст
CREATE TABLE Products (
    product_id INT PRIMARY KEY,
    name NVARCHAR(100),
    -- JSON атрибути (колір, розмір, матеріал)
    attributes NVARCHAR(MAX),
    -- Валідація через CHECK constraint
    CONSTRAINT CK_ValidJSON CHECK (ISJSON(attributes) = 1)
);

-- Вставка JSON даних
INSERT INTO Products (product_id, name, attributes)
VALUES (
    1,
    'T-Shirt',
    '{"color": "blue", "size": "M", "material": "cotton", "tags": ["casual", "summer"]}'
);
Переваги NVARCHAR(MAX):
  • Гнучкість (можна зберігати будь-який текст)
  • Сумісність з існуючими інструментами
  • Менше змін у структурі БД
Недоліки:
  • Більший розмір (текст vs бінарний формат)
  • Повільніше парсинг
  • Можливість зберегти невалідний JSON (без CHECK constraint)

Вбудовані функції для роботи з JSON

SQL Server надає багатий набір функцій для роботи з JSON:

-- Отримати скалярне значення з JSON
DECLARE @json NVARCHAR(MAX) = N'{
    "product": {
        "name": "Laptop",
        "price": 1299.99,
        "specs": {
            "cpu": "Intel i7",
            "ram": "16GB"
        }
    }
}';

-- Простий шлях
SELECT JSON_VALUE(@json, '$.product.name') AS ProductName;
-- Результат: 'Laptop'

-- Вкладений шлях
SELECT JSON_VALUE(@json, '$.product.specs.cpu') AS CPU;
-- Результат: 'Intel i7'

-- З реальної таблиці
SELECT
    product_id,
    name,
    JSON_VALUE(attributes, '$.color') AS Color,
    JSON_VALUE(attributes, '$.size') AS Size
FROM Products;

Індексування JSON даних

Проблема: Пошук по JSON полям повільний, бо SQL Server робить full table scan.

Рішення: Створити computed columns з індексами!

-- Створити обчислювані стовпці з JSON
ALTER TABLE Products
ADD
    color AS JSON_VALUE(attributes, '$.color') PERSISTED,
    size AS JSON_VALUE(attributes, '$.size') PERSISTED,
    price_json AS CAST(JSON_VALUE(attributes, '$.price') AS DECIMAL(10,2)) PERSISTED;

-- Індексувати обчислювані стовпці
CREATE INDEX IX_Products_Color ON Products(color);
CREATE INDEX IX_Products_Size ON Products(size);
CREATE INDEX IX_Products_Price ON Products(price_json);

-- Тепер запити швидкі!
SELECT product_id, name, attributes
FROM Products
WHERE color = 'blue';  -- Використає індекс IX_Products_Color ⚡
Best Practice: Якщо часто фільтруєте по певним JSON полям, створіть computed columns з індексами.

Повнотекстовий пошук у JSON

-- Створити Full-Text Index для пошуку всередині JSON
CREATE FULLTEXT INDEX ON Products(attributes)
KEY INDEX PK_Products;

-- Пошук по JSON тексту
SELECT product_id, name, attributes
FROM Products
WHERE CONTAINS(attributes, 'cotton OR polyester');

SQL Server vs PostgreSQL: Порівняння JSON

-- Тип даних: NVARCHAR(MAX)
CREATE TABLE Products (
    id INT PRIMARY KEY,
    data NVARCHAR(MAX),
    CONSTRAINT CK_JSON CHECK (ISJSON(data) = 1)
);

-- Отримати значення
SELECT JSON_VALUE(data, '$.name') FROM Products;

-- Computed column для індексування
ALTER TABLE Products
ADD name AS JSON_VALUE(data, '$.name') PERSISTED;
CREATE INDEX IX_Name ON Products(name);

Порівняльна таблиця можливостей:

ФункціяSQL ServerPostgreSQLПереможець
Нативний тип❌ NVARCHAR(MAX)✅ JSON, JSONBPostgreSQL
Продуктивність⭕ Середня (текст)✅ Висока (бінарний)PostgreSQL
Індексування⭕ Computed columns✅ GIN indexesPostgreSQL
Валідація✅ ISJSON()✅ АвтоматичнаНічия
Функції✅ JSON_VALUE, OPENJSON✅ ->, ->>, @>Нічия
FOR JSON✅ Вбудовано✅ row_to_json()Нічия
Екосистема⭕ Microsoft only✅ Open-sourcePostgreSQL
Чому SQL Server не зробив окремий тип?Microsoft пояснює це зворотною сумісністю та простотою інтеграції. Використання NVARCHAR(MAX) означає:
  • Працює з існуючими backup/restore
  • Сумісно зі старими клієнтськими бібліотеками
  • Не потрібна міграція даних
Але в результаті PostgreSQL швидший для JSON workloads.

Коли використовувати JSON у SQL Server?

1. Flexible schema (гнучка схема)

-- Різні продукти мають різні атрибути
-- Laptop: cpu, ram, storage
-- T-Shirt: size, color, material
-- Без JSON потрібні окремі таблиці або EAV pattern
CREATE TABLE Products (
    product_id INT PRIMARY KEY,
    category NVARCHAR(50),  -- 'Electronics', 'Clothing'
    attributes NVARCHAR(MAX)  -- Різні для кожної категорії
);

2. Audit logs / Event sourcing

CREATE TABLE ActivityLog (
    log_id BIGINT IDENTITY PRIMARY KEY,
    event_type NVARCHAR(50),
    event_data NVARCHAR(MAX),  -- Деталі події у JSON
    created_at DATETIME2 DEFAULT SYSDATETIME()
);

3. Налаштування/конфігурації

CREATE TABLE UserSettings (
    user_id INT PRIMARY KEY,
    preferences NVARCHAR(MAX)  -- {"theme": "dark", "language": "uk", ...}
);

4. API responses / Кешування

CREATE TABLE APICache (
    cache_key VARCHAR(100) PRIMARY KEY,
    response_json NVARCHAR(MAX),
    expires_at DATETIME2
);

Практичний приклад: E-commerce з JSON

-- Продукти з різними атрибутами
CREATE TABLE Products (
    product_id INT IDENTITY(1,1) PRIMARY KEY,
    category NVARCHAR(50) NOT NULL,
    name NVARCHAR(200) NOT NULL,
    base_price DECIMAL(10,2) NOT NULL,  -- Індексується

    -- Гнучкі атрибути у JSON
    attributes NVARCHAR(MAX),
    CONSTRAINT CK_Attributes_Valid CHECK (ISJSON(attributes) = 1),

    created_at DATETIME2 DEFAULT SYSDATETIME()
);

-- Computed columns для популярних фільтрів
ALTER TABLE Products ADD
    color AS JSON_VALUE(attributes, '$.color') PERSISTED,
    size AS JSON_VALUE(attributes, '$.size') PERSISTED;

CREATE INDEX IX_Products_Color ON Products(color)
    WHERE color IS NOT NULL;
CREATE INDEX IX_Products_Size ON Products(size)
    WHERE size IS NOT NULL;

-- Вставка різних типів продуктів
INSERT INTO Products (category, name, base_price, attributes)
VALUES
    ('Electronics', 'Gaming Laptop', 1499.99,
     '{"brand": "ASUS", "cpu": "Intel i7", "ram": "32GB", "storage": "1TB SSD", "gpu": "RTX 4070"}'),

    ('Clothing', 'Cotton T-Shirt', 29.99,
     '{"brand": "Nike", "color": "blue", "size": "M", "material": "100% cotton", "tags": ["casual", "summer"]}'),

    ('Books', 'SQL Server 2022 Guide', 59.99,
     '{"author": "John Doe", "pages": 500, "isbn": "978-1234567890", "format": "Hardcover"}');

-- Запити
-- 1. Знайти сині футболки розміру M
SELECT product_id, name, base_price, attributes
FROM Products
WHERE category = 'Clothing'
  AND color = 'blue'      -- Використає індекс
  AND size = 'M';         -- Використає індекс

-- 2. Отримати всі ноутбуки з характеристиками у зручному форматі
SELECT
    product_id,
    name,
    base_price,
    JSON_VALUE(attributes, '$.brand') AS Brand,
    JSON_VALUE(attributes, '$.cpu') AS CPU,
    JSON_VALUE(attributes, '$.ram') AS RAM,
    JSON_VALUE(attributes, '$.gpu') AS GPU
FROM Products
WHERE category = 'Electronics';

-- 3. Знайти продукти з певним тегом
SELECT product_id, name, category
FROM Products
CROSS APPLY OPENJSON(attributes, '$.tags') AS tags
WHERE tags.value = 'summer';

Резюме: JSON у SQL Server

Ключові висновки:
  1. Немає окремого типу: JSON зберігається як NVARCHAR(MAX)
  2. Валідація: Використовуйте ISJSON() у CHECK constraints
  3. Функції: JSON_VALUE, JSON_QUERY, OPENJSON, FOR JSON, JSON_MODIFY
  4. Індексування: Створюйте computed columns з індексами для популярних фільтрів
  5. Коли використовувати: Flexible schemas, configurations, не для transactional data
  6. VS PostgreSQL: PostgreSQL швидший для JSON завдяки JSONB, але SQL Server цілком прийнятний
Альтернатива: Якщо вам потрібна висока продуктивність JSON, розгляньте:
  • PostgreSQL з JSONB
  • MongoDB (нативний JSON/BSON)
  • SQL Server з нормалізованою схемою + materialized views

Перевірка знань

Закріпіть отримані знання, пройшовши короткий тест:

Примітка: Якщо тест не відображається, перейдіть за прямим посиланням.

Резюме

Ключові висновки:
  1. Цілі числа: Використовуйте мінімальний достатній тип (TINYINT → SMALLINT → INT → BIGINT)
  2. Текст: NVARCHAR для мультимовності, VARCHAR лише для гарантовано латиниці
  3. Гроші: DECIMAL або MONEY, НІКОЛИ FLOAT
  4. Дати: DATE для дат, DATETIME2 для timestamps, DATETIMEOFFSET для глобальних систем
  5. Оптимізація: Правильний вибір типу економить пам'ять і покращує продуктивність

Наступна тема: Індекси у MS SQL Server — дізнаємося, як прискорити запити в сотні разів!

Copyright © 2026