SQL и Qt

Различия диалектов SQL между СУБД: практические примеры

Введение

Предыдущие статьи блока (423-426) разобрали особенности конкретных СУБД по отдельности — в этой статье явно сводим воедино самые частые, практически значимые различия синтаксиса между ними в виде сравнительной таблицы, полезной при переносе кода между разными СУБД или при работе с несколькими разными СУБД одновременно в одной организации.

Концепция

Стандарт SQL (ISO/IEC 9075) определяет общее ядро языка, которому в той или иной степени следуют все основные СУБД — но множество практически важных деталей (ограничение количества строк, автоинкремент первичного ключа, конкатенация строк, обработка конфликтов при вставке) стандартизированы слабо или вовсе оставлены на volю конкретной реализации, что и порождает заметные практические различия диалектов, систематизированные в этой статье.

Пример кода: Различия диалектов SQL между СУБД

-- Ограничение количества строк результата — разные диалекты
SELECT * FROM orders ORDER BY total_amount DESC LIMIT 10;        -- PostgreSQL, MySQL, SQLite
SELECT TOP 10 * FROM orders ORDER BY total_amount DESC;            -- SQL Server
SELECT * FROM orders ORDER BY total_amount DESC FETCH FIRST 10 ROWS ONLY; -- стандарт SQL, Oracle
-- Автоинкрементный первичный ключ — разные диалекты
CREATE TABLE t (id SERIAL PRIMARY KEY);              -- PostgreSQL
CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY);   -- MySQL
CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT); -- SQLite
CREATE TABLE t (id INT IDENTITY(1,1) PRIMARY KEY);    -- SQL Server
-- Конкатенация строк — разные диалекты
SELECT 'Привет, ' || name FROM customers;             -- PostgreSQL, SQLite, стандарт SQL
SELECT CONCAT('Привет, ', name) FROM customers;        -- MySQL, SQL Server (также поддерживает ||)
-- UPSERT (вставка или обновление при конфликте) — разные диалекты
INSERT INTO customers (id, name) VALUES (1, 'Иван')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;          -- PostgreSQL

INSERT INTO customers (id, name) VALUES (1, 'Иван')
ON DUPLICATE KEY UPDATE name = VALUES(name);                    -- MySQL (статья 424)

MERGE INTO customers AS target
USING (SELECT 1 AS id, 'Иван' AS name) AS source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET name = source.name
WHEN NOT MATCHED THEN INSERT (id, name) VALUES (source.id, source.name); -- SQL Server, стандарт SQL

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

Четыре сравнительные группы запросов (ограничение строк, автоинкремент, конкатенация, upsert) показывают типичный паттерн различий — задача концептуально одна и та же во всех случаях, но конкретный синтаксис её выражения варьируется между СУБД достаточно, чтобы прямое, механическое копирование кода между ними почти никогда не работает без изменений. MERGE (стандартизированная в SQL:2003, но не одинаково широко поддерживаемая всеми СУБД конструкция) показывает наиболее многословный, но при этом наиболее формально «стандартный» подход к upsert по сравнению с более компактными, но СУБД-специфичными альтернативами PostgreSQL/MySQL.

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

  • Написание кода, ориентированного на одну конкретную СУБД, при заявленном требовании поддержки нескольких разных СУБД одновременно (что бывает нужно для коммерческого ПО, продаваемого разным клиентам с разной инфраструктурой) — без явного учёта различий диалектов с самого начала разработки, обнаружение несовместимости при попытке развернуть на другой СУБД может потребовать существенной переработки уже написанного кода; для таких случаев часто практичнее с самого начала использовать ORM (статья 436) или явный слой абстракции, скрывающий эти различия диалектов от основной бизнес-логики.
  • Полное игнорирование диалектных различий при изучении SQL по материалам, ориентированным на конкретную, отличную от используемой СУБД — многие учебные материалы, статьи, примеры в интернете написаны с неявным предположением о конкретной СУБД (часто MySQL или PostgreSQL, как наиболее распространённые в открытых, бесплатных образовательных материалах), и копирование такого примера «как есть» в проект, использующий другую СУБД (особенно SQL Server, Oracle), может потребовать неочевидной для новичка адаптации синтаксиса.
  • Предположение, что «стандартный SQL» (стандартизированный синтаксис) гарантированно поддерживается каждой конкретной СУБД — несмотря на наличие официального стандарта ISO/IEC 9075, реальная степень соответствия этому стандарту существенно различается между конкретными СУБД и даже между разными версиями одной и той же СУБД, и предположение о полной, гарантированной стандартности конкретной синтаксической конструкции без проверки в документации конкретной используемой версии конкретной СУБД может привести к неожиданной ошибке выполнения.
  • Создание собственного, неполного слоя абстракции диалектов «с нуля» вместо использования зрелых, проверенных существующих решений (готовых ORM, статья 436, или специализированных библиотек абстракции SQL-диалектов) для проектов с реальной потребностью поддержки нескольких СУБД — самостоятельная реализация полной, надёжной абстракции над всеми тонкими различиями диалектов значительно сложнее, чем может показаться на первый взгляд, и для серьёзных проектов с этим требованием использование зрелого, проверенного временем готового решения обычно практичнее, чем разработка собственного слоя абстракции с нуля.