Введение
Предыдущие статьи блока (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-диалектов) для проектов с реальной потребностью поддержки нескольких СУБД — самостоятельная реализация полной, надёжной абстракции над всеми тонкими различиями диалектов значительно сложнее, чем может показаться на первый взгляд, и для серьёзных проектов с этим требованием использование зрелого, проверенного временем готового решения обычно практичнее, чем разработка собственного слоя абстракции с нуля.