SQL и Qt

Microsoft SQL Server: T-SQL особенности

Введение

Microsoft SQL Server — широко используемая в корпоративной среде (особенно в организациях, уже глубоко интегрированных с экосистемой Microsoft) СУБД со своим процедурным диалектом T-SQL — в статье разберём ключевые особенности T-SQL, отличающие его от уже разобранных диалектов PostgreSQL/MySQL.

Концепция

T-SQL (Transact-SQL) расширяет стандартный SQL собственным синтаксисом для переменных, управления потоком выполнения, обработки ошибок (TRY/CATCH, концептуально похожим на обработку исключений в обычных языках программирования) и специфичными функциями (TOP вместо LIMIT, отличающийся синтаксис работы с датами). Хранимые процедуры (статья 411) особенно широко используются в традиционной разработке на SQL Server, часто значительно активнее, чем в типичной практике PostgreSQL/MySQL проектов, что отражает историческую архитектурную традицию, распространённую в корпоративных .NET-приложениях, тесно интегрированных с SQL Server.

Пример кода: Microsoft SQL Server

-- TOP вместо LIMIT — выборка ограниченного количества строк (синтаксис T-SQL)
SELECT TOP 10 name, total_amount FROM orders ORDER BY total_amount DESC;
-- ^ в PostgreSQL/MySQL это было бы: SELECT ... ORDER BY ... LIMIT 10 (статья 427)
-- TRY/CATCH — обработка ошибок в T-SQL, аналогично try/catch обычных языков программирования
BEGIN TRY
    BEGIN TRANSACTION;

    UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
    UPDATE accounts SET balance = balance + 1000 WHERE id = 2;

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    PRINT 'Ошибка при переводе средств: ' + ERROR_MESSAGE();
END CATCH;
-- Переменные и управление потоком — T-SQL близок к процедурному стилю программирования
DECLARE @customer_id INT = 5;
DECLARE @total_spent DECIMAL(12, 2);

SELECT @total_spent = SUM(total_amount) FROM orders WHERE customer_id = @customer_id;

IF @total_spent > 10000
    PRINT 'VIP-клиент';
ELSE
    PRINT 'Обычный клиент';
-- Хранимая процедура T-SQL с входными и выходными параметрами
CREATE PROCEDURE GetCustomerDiscount
    @customer_id INT,
    @discount_rate DECIMAL(5, 2) OUTPUT
AS
BEGIN
    DECLARE @total_spent DECIMAL(12, 2);
    SELECT @total_spent = COALESCE(SUM(total_amount), 0) FROM orders WHERE customer_id = @customer_id;

    SET @discount_rate = CASE
        WHEN @total_spent > 50000 THEN 0.15
        WHEN @total_spent > 10000 THEN 0.10
        ELSE 0.0
    END;
END;

Пояснения к коду

SELECT TOP 10 ... ORDER BY ... показывает один из самых заметных синтаксических отличий T-SQL от PostgreSQL/MySQL — ограничение количества строк выражается через TOP сразу после SELECT, а не через LIMIT в конце запроса, что для разработчиков, переходящих между СУБД, требует явного пересмотра привычного синтаксиса (подробнее систематизировано в статье 427). TRY/CATCH с ERROR_MESSAGE() показывает структурированную обработку ошибок, более близкую по стилю к обычным языкам программирования по сравнению с менее формализованным подходом некоторых других СУБД, что отражает общую тенденцию T-SQL быть более процедурно ориентированным языком, активно используемым для построения значительной части бизнес-логики непосредственно внутри БД (статья 411), а не просто декларативным языком запросов.

Подводные камни

  • Прямой перенос T-SQL кода (хранимых процедур, синтаксиса запросов) на PostgreSQL/MySQL без понимания, что синтаксис существенно отличается — TOP, TRY/CATCH, специфичный синтаксис переменных и многие функции T-SQL не работают «как есть» в других СУБД, и перенос проекта с SQL Server на другую СУБД (или наоборот) почти всегда требует существенной переработки любого нетривиального, специфичного для T-SQL кода, а не простого, механического копирования.
  • Чрезмерный перенос бизнес-логики в хранимые процедуры T-SQL по исторически сложившейся традиции, не подвергая сомнению, действительно ли это лучшее архитектурное решение для конкретного нового проекта — хотя эта традиция широко распространена в экосистеме SQL Server/.NET, общие компромиссы хранимых процедур (статья 411 — снижение переносимости, усложнение тестирования) остаются актуальными и для T-SQL, и решение о размещении логики в хранимых процедурах должно быть осознанным архитектурным выбором, а не автоматическим следствием исторической традиции конкретной технологической экосистемы.
  • Игнорирование различий в обработке NULL и иных тонких семантических деталей между T-SQL и стандартным SQL/другими диалектами — хотя основные принципы работы с NULL (статья 396) общие для всех диалектов SQL, отдельные детали поведения (например, конкатенация строк с NULL некоторыми настройками может вести себя иначе, чем в PostgreSQL) могут отличаться, и код, перенесённый между диалектами без тщательной проверки, может содержать незаметные, тонкие расхождения в поведении именно в граничных случаях с NULL.
  • Использование устаревших возможностей T-SQL без оглядки на актуальные рекомендации Microsoft для текущей версии SQL Server — как и любая активно развивающаяся технология, T-SQL со временем добавляет новые, более удобные конструкции (взаимодействие с JSON, улучшенная поддержка оконных функций) и постепенно выводит из рекомендуемого использования некоторые более старые подходы, и для нового кода стоит ориентироваться на актуальную документацию конкретной используемой версии, а не на потенциально устаревшие примеры из давних источников.