Тип данных в 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 бывает компактнее.



