Типы данных в MS SQL Server (T-SQL): обзор с таблицей и примерами

Типы данных в MS SQL Server (T-SQL): обзор с таблицей и примерами Полезное

Тип данных в MS SQL Server — это характеристика столбца (или переменной), которая задает, какие значения в нем допустимы и сколько места они занимают. Тип указывают при создании таблицы, и дальше сервер сам контролирует диапазон, точность и правила преобразования.

Речь именно про Microsoft SQL Server и его диалект Transact-SQL (T-SQL), а не про общий SQL: диапазоны, размеры и имена типов ниже — это правила MS SQL Server, в PostgreSQL, MySQL или Oracle они другие. Разберу типы по группам, сведу диапазоны в таблицу, разведу пары, которые чаще всего путают (VARCHAR и NVARCHAR, DATETIME2 и DATETIME), и покажу три типовые ошибки.

Числовые типы данных

Целые числа различаются только диапазоном и размером — берут самый узкий тип, в который значение точно поместится:

  • BIT — 0, 1 или NULL, аналог логического типа. Один столбец занимает 1 байт, но несколько столбцов BIT в одной таблице сервер упаковывает в общий байт (до 8 в 1 байт).
  • TINYINT — целые 0-255, 1 байт (без знака, отрицательных нет).
  • SMALLINT — от -32 768 до 32 767, 2 байта.
  • INT — от -2 147 483 648 до 2 147 483 647, 4 байта. Частый стартовый выбор для ключей и счетчиков; диапазон проверяют по ожидаемому числу строк.
  • BIGINT — от -9,2 x 10^18 до 9,2 x 10^18, 8 байт. Нужен, когда INT уже мал.

Для дробных значений различают точные и приблизительные типы:

  • DECIMAL(p, s) и его синоним NUMERIC(p, s) — число с фиксированной точностью. p (precision) — общее число значащих цифр, 1-38 (по умолчанию 18); s (scale) — сколько из них после запятой, от 0 до p (по умолчанию 0). Размер 5-17 байт. Хранит десятичные значения точно в пределах заданных precision и scale, поэтому им считают деньги; но scale и правило округления результатов нужно задавать явно — при присваивании и арифметике значение все равно округляется до scale.
  • MONEY и SMALLMONEY — денежные типы с фиксированными 4 знаками после запятой, 8 и 4 байта.
  • FLOAT(n) и REAL — приблизительные числа с плавающей точкой. FLOAT хранит значения примерно до 1,79 x 10^308; n (бит мантиссы, 1-53, по умолчанию 53) задает точность: при n до 24 это 4 байта, при большем — 8. REAL — синоним FLOAT(24).

Приблизительные типы не хранят десятичные дроби точно, поэтому для денег и сравнений на равенство берут DECIMAL, а FLOAT оставляют для инженерных расчетов, где важен диапазон, а не последняя копейка.

Строковые типы: VARCHAR против NVARCHAR

Ключевое различие строковых типов в MS SQL Server — Unicode или нет, и фиксированная длина или переменная.

  • CHAR(n) — строка фиксированной длины: n задает лимит в БАЙТАХ (1-8000), а не число символов. Более короткое значение дополняется пробелами до n.
  • VARCHAR(n) — строка переменной длины: n — лимит в байтах (до 8000) или MAX (до 2 Гб).
  • NCHAR(n) — Unicode-строка фиксированной длины: n — число двухбайтовых пар (1-4000).
  • NVARCHAR(n) — Unicode-строка переменной длины: n — число двухбайтовых пар (до 4000) или MAX.

Ключевой момент — единица измерения n. У CHAR/VARCHAR это байты: в однобайтной кодировке обычно помещается до n символов, но в UTF-8 или DBCS один символ может занять несколько байт, и символов поместится меньше. У NCHAR/NVARCHAR n — это число двухбайтовых пар (UTF-16): символы базовой многоязычной плоскости обычно занимают одну пару (2 байта), а эмодзи и часть редких знаков — две пары (суррогатная пара), поэтому и здесь символов может быть меньше n. Префикс N — от слова national.

Проверить это можно на одном запросе — LEN() считает символы, DATALENGTH() — байты:

SELECT DATALENGTH(N'aЯ😀') AS bytes, LEN(N'aЯ😀') AS len;

DATALENGTH вернет 8: a и Я — по одной паре (2 байта), эмодзи 😀 — суррогатная пара (4 байта). LEN посчитает единицы кода: обычно 4, а на collation с суффиксом _SC (supplementary characters) — 3, потому что эмодзи считается за один символ. Отсюда вывод: лимит NVARCHAR(3) под три «пользовательских» символа с эмодзи может не хватить.

Здесь же классическая ловушка. Раньше правило звучало жестко: «VARCHAR не хранит Unicode, для кириллицы берите NVARCHAR». С SQL Server 2019 это уже не абсолют: если у столбца VARCHAR collation с суффиксом _UTF8 (например, Latin1_General_100_CI_AS_SC_UTF8), он хранит любой Unicode в кодировке UTF-8. Без такой collation VARCHAR ограничен кодовой страницей своей collation, и кириллица в столбце с латинской кодовой страницей превратится в знаки вопроса (покажу в разделе с ошибками).

Типы TEXT и NTEXT устарели — вместо них используют VARCHAR(MAX) и NVARCHAR(MAX).

Дата и время: DATETIME2 против DATETIME

В MS SQL Server несколько типов для даты и времени, и выбор между ними — частый источник багов.

  • DATE — только дата, 0001-01-01 … 9999-12-31, 3 байта.
  • TIME(n) — только время с точностью до 100 нс, 3-5 байт.
  • DATETIME2(n) — дата и время, 0001-01-01 … 9999-12-31, точность до 100 нс, 6-8 байт.
  • DATETIME — устаревающий тип: диапазон только с 1753-01-01, точность округляется до 1/300 секунды (около 3,33 мс), 8 байт.
  • SMALLDATETIME — экономичный тип, 1900-01-01 … 2079-06-06, точность до минуты, 4 байта.
  • DATETIMEOFFSET(n) — дата и время вместе со смещением часового пояса, 8-10 байт в зависимости от точности n (10 байт — при максимальной точности).

Для новых таблиц Microsoft рекомендует DATE, TIME, DATETIME2, DATETIMEOFFSET вместо DATETIME и SMALLDATETIME. У DATETIME2 два плюса: он покрывает даты до 1753 года и дает точность выше 3,33 мс при том же или меньшем размере. Оговорка — DATETIME2 появился в SQL Server 2008, для очень старых версий его может не быть.

Двоичные и специальные типы

Для бинарных данных есть BINARY(n) (фиксированная длина, 1-8000 байт) и VARBINARY(n) (переменная, до 8000 байт или MAX до 2 Гб). Тип IMAGE устарел, его заменяет VARBINARY(MAX).

Отдельная группа специальных типов:

  • UNIQUEIDENTIFIER — глобально уникальный идентификатор (GUID), 16 байт.
  • ROWVERSION (прежнее имя — TIMESTAMP) — автоматически обновляемый счетчик версии строки в пределах базы: он растет при каждом INSERT/UPDATE строки с таким столбцом. Вопреки имени, это не дата и не время и не последовательный номер записи; для меток времени он не годится.
  • XML — документ или фрагмент XML, до 2 Гб; SQL_VARIANT — значение почти любого другого типа.
  • HIERARCHYID — позиция в дереве; GEOGRAPHY и GEOMETRY — координаты на сфере и на плоскости.
  • CURSOR и TABLE — служебные типы для переменных и параметров, в столбцах таблиц не используются.

Сводная таблица диапазонов и размеров

Группа Тип Диапазон / длина Размер
Целые TINYINT 0 … 255 1 байт
Целые SMALLINT -32 768 … 32 767 2 байта
Целые INT -2,15 x 10^9 … 2,15 x 10^9 4 байта
Целые BIGINT -9,2 x 10^18 … 9,2 x 10^18 8 байт
Дробные точные DECIMAL(p,s) до 38 значащих цифр 5-17 байт
Дробные приближ. FLOAT(n) ~ +/-1,79 x 10^308 4 или 8 байт
Деньги MONEY ~ +/-9,2 x 10^14 8 байт
Строки VARCHAR(n) до 8000 байт или MAX 1 байт/символ в однобайтной кодировке, в UTF-8 больше
Строки Unicode NVARCHAR(n) до 4000 двухбайтовых пар или MAX 2 байта на пару; эмодзи — 2 пары
Дата и время DATE 0001-01-01 … 9999-12-31 3 байта
Дата и время DATETIME2(n) 0001-01-01 … 9999-12-31 6-8 байт
Дата и время DATETIMEOFFSET(n) как DATETIME2 + смещение TZ 8-10 байт
Дата и время DATETIME 1753-01-01 … 9999-12-31 8 байт

Как выбрать тип под задачу

Таблица выше отвечает на вопрос «какие бывают типы», но проектируют схему обычно от задачи. Короткий ориентир «задача -> тип -> когда иначе»:

  • Счетчик или суррогатный ключ -> INT, при больших объемах -> BIGINT.
  • Деньги и точные расчеты -> DECIMAL(p, s) с явными precision и scale.
  • Произвольный текст на любом языке -> NVARCHAR (или VARCHAR с collation _UTF8 на SQL Server 2019+).
  • Момент времени в UTC -> DATETIME2; локальное время со смещением -> DATETIMEOFFSET.
  • Версия строки для контроля конкурентных изменений -> ROWVERSION (не для меток времени).

Типы в объявлении таблицы

Типы указывают прямо при создании таблицы — вот минимальный рабочий пример:

CREATE TABLE employees (
    id         INT            IDENTITY(1,1) PRIMARY KEY,  -- целочисленный ключ
    full_name  NVARCHAR(100)  NOT NULL,                   -- Unicode: любые языки
    department CHAR(3)        NULL,                        -- код из 3 символов
    salary     DECIMAL(10, 2) NOT NULL,                    -- деньги, точный тип
    hired_at   DATE           NOT NULL,                     -- только дата
    updated_at DATETIME2(3)   DEFAULT SYSDATETIME()         -- дата и время, мс
);

INSERT INTO employees (full_name, department, salary, hired_at)
VALUES (N'Иван Петров', N'IT', 150000.00, '2026-01-15');

SELECT id, full_name, salary, hired_at FROM employees;

Ожидаемый вывод:

id | full_name   | salary       | hired_at
---+-------------+--------------+------------
 1 | Иван Петров | 150000.00    | 2026-01-15

Значение N'Иван Петров' записано с префиксом N — это литерал Unicode, он корректно ложится в NVARCHAR. Про то, что бывает без N, — ниже.

Три частые ошибки

Ошибка 1: кириллица в VARCHAR без Unicode. Кладем русский текст в VARCHAR со стандартной латинской collation:

DECLARE @t TABLE (txt VARCHAR(50) COLLATE Latin1_General_100_CI_AS);
INSERT INTO @t VALUES ('Привет');
SELECT txt FROM @t;

Результат зависит от collation столбца — при латинской кодовой странице сервер молча заменит непредставимые символы:

txt
------
??????

Данные потеряны без ошибки — это опаснее явного падения. Исправление: хранить текст в NVARCHAR и писать литерал с префиксом N, либо (SQL Server 2019+) задать столбцу collation _UTF8:

DECLARE @t TABLE (txt NVARCHAR(50));
INSERT INTO @t VALUES (N'Привет');   -- префикс N обязателен
SELECT txt FROM @t;                  -- вернет: Привет

Ошибка 2: неявное преобразование (implicit conversion) строки и числа. Пробуем склеить текст с числом через +:

SELECT 'Заказ № ' + 42 AS label;

У int приоритет типа выше, чем у varchar, поэтому сервер пытается превратить строку в число и падает:

Msg 245, Level 16, State 1
Conversion failed when converting the varchar value 'Заказ № ' to data type int.

Исправление — привести число к строке явно через CAST или использовать CONCAT, который сам приводит аргументы к строке:

SELECT CONCAT('Заказ № ', 42) AS label;   -- вернет: Заказ № 42

Ошибка 3: дата раньше 1753 года в DATETIME. Записываем историческую дату в устаревающий DATETIME:

DECLARE @d DATETIME = '1700-01-01';
SELECT @d;

Диапазон DATETIME начинается с 1753 года, поэтому:

Msg 242, Level 16, State 3
The conversion of a varchar data type to a datetime data type resulted
in an out-of-range value.

Исправление — взять DATETIME2, чей диапазон стартует с 0001 года:

DECLARE @d DATETIME2(0) = '1700-01-01';
SELECT @d;                              -- вернет: 1700-01-01 00:00:00

Выводы

  • Тип данных в MS SQL Server задает допустимые значения столбца и его размер; все диапазоны и имена ниже — правила именно T-SQL, в других СУБД они отличаются.
  • Целые типы выбирают по диапазону (TINYINT … BIGINT), для дробных различают точные (DECIMAL, деньги) и приблизительные (FLOAT, REAL) — деньги и сравнения на равенство только точным типом.
  • NVARCHAR хранит Unicode (UTF-16) и подходит для любых языков; VARCHAR ограничен кодовой страницей своей collation, кроме случая collation _UTF8 в SQL Server 2019 и новее.
  • Для даты и времени в новых таблицах берут DATE, TIME, DATETIME2, DATETIMEOFFSET; DATETIME устаревает из-за начала с 1753 года и грубой точности около 3,33 мс.
  • Устаревшие TEXT, NTEXT, IMAGE заменяют на VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX).

Где применяется / связь с практикой

Выбор типа — первое проектное решение по каждой таблице, и оно влияет на корректность, размер базы и скорость запросов. Слишком широкий тип раздувает индексы, слишком узкий упрется в диапазон; несогласованные типы в соседних таблицах порождают неявные преобразования, которые незаметно тормозят JOIN и фильтры.

Освойте тему на практике

Отработать проектирование таблиц, типы, индексы и написание запросов на реальных задачах можно на курсе sql в Otus. Посмотреть формат и уровень занятий, не оплачивая курс, помогут бесплатные вебинары — там разбирают конкретные запросы и отвечают на вопросы.

Смежные темы: T-SQL — что должен знать разработчик, Особенности MS SQL таблиц, Все, что нужно знать про MS SQL запросы просто и быстро.

FAQ

Почему TIMESTAMP в T-SQL не хранит дату и время?
В MS SQL Server TIMESTAMP (современное имя — ROWVERSION) — это счетчик версии строки в пределах базы, который меняется при вставке и обновлении строки, а не момент времени и не порядковый номер записи. Для отметок времени нужен DATETIME2 или DATETIMEOFFSET, а ROWVERSION оставляют для контроля конкурентных изменений.

Что выбрать для денег — MONEY или DECIMAL?
На практике чаще берут DECIMAL(19, 4) или DECIMAL(19, 2): он переносим между СУБД и дает явный контроль над точностью. MONEY компактен и удобен, но у него ровно 4 знака после запятой и есть нюансы округления при промежуточном делении, поэтому для сложных расчетов надежнее DECIMAL.

Нужен ли NVARCHAR, если на сервере включена collation UTF-8?
Начиная с SQL Server 2019, VARCHAR с collation _UTF8 хранит любой Unicode, так что для многоязычного текста можно обойтись и им. NVARCHAR остается универсальным выбором для более старых версий и для случаев, когда преобладают символы, которые в UTF-8 занимают 3 байта (например, кириллица) — там UTF-16 бывает компактнее.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)