Общие правила упрощения инструкций SQL
Я ищу некоторые "правила вывода" (похожие на правила операции set или логические правила), которые я могу использовать для уменьшения SQL-запроса по сложности или размеру. Существует ли что-то подобное? Какие-нибудь бумаги, инструменты? Есть эквиваленты, которые вы нашли сами? Это как-то похоже на оптимизацию запросов, но не с точки зрения производительности.
чтобы указать его по-другому: имея (сложный) запрос с соединениями, Подселектами, объединениями, можно (или нет) свести его к более простому, эквивалентная инструкция SQL, которая дает тот же результат, используя некоторые правила преобразования?
Итак, я ищу эквивалентные преобразования операторов SQL, такие как тот факт, что большинство подзапросов могут быть переписаны как соединение.
8 ответов
сформулировать это по-другому: имея (сложный) запрос с объединениями, Подселектами, объединениями, можно ли (или нет) свести его к более простому, эквивалентному SQL-оператору, который дает тот же результат, используя некоторые правила преобразования?
это именно то, что оптимизаторы делают для жизни (не то, чтобы я говорю, что они всегда делают это хорошо).
С SQL
- это язык на основе набора, обычно существует несколько способов преобразования одного запроса в другой.
такой запрос:
SELECT *
FROM mytable
WHERE col1 > @value1 OR col2 < @value2
можно преобразовать в это:
SELECT *
FROM mytable
WHERE col1 > @value1
UNION
SELECT *
FROM mytable
WHERE col2 < @value2
или такой:
SELECT mo.*
FROM (
SELECT id
FROM mytable
WHERE col1 > @value1
UNION
SELECT id
FROM mytable
WHERE col2 < @value2
) mi
JOIN mytable mo
ON mo.id = mi.id
, которые выглядят уродливее, но могут дать лучшие планы выполнения.
одна из самых распространенных вещей, чтобы сделать, это заменить этот запрос:
SELECT *
FROM mytable
WHERE col IN
(
SELECT othercol
FROM othertable
)
С этим:
SELECT *
FROM mytable mo
WHERE EXISTS
(
SELECT NULL
FROM othertable o
WHERE o.othercol = mo.col
)
в некоторых RDBMS
' s (как PostgreSQL
), DISTINCT
и GROUP BY
используйте различные планы выполнения, поэтому иногда это лучше заменить одно на другое:
SELECT mo.grouper,
(
SELECT SUM(col)
FROM mytable mi
WHERE mi.grouper = mo.grouper
)
FROM (
SELECT DISTINCT grouper
FROM mytable
) mo
и
SELECT mo.grouper, SUM(col)
FROM mytable
GROUP BY
mo.grouper
на PostgreSQL
, DISTINCT
сортировка и GROUP BY
хэши.
MySQL
не хватает FULL OUTER JOIN
, поэтому его можно переписать как folloing:
SELECT t1.col1, t2.col2
FROM table1 t1
LEFT OUTER JOIN
table2 t2
ON t1.id = t2.id
и
SELECT t1.col1, t2.col2
FROM table1 t1
LEFT JOIN
table2 t2
ON t1.id = t2.id
UNION ALL
SELECT NULL, t2.col2
FROM table1 t1
RIGHT JOIN
table2 t2
ON t1.id = t2.id
WHERE t1.id IS NULL
, но увидеть эту статью в моем блоге о том, как сделать это более эффективно в MySQL
:
этот иерархический запрос в Oracle
:
SELECT DISTINCT(animal_id) AS animal_id
FROM animal
START WITH
animal_id = :id
CONNECT BY
PRIOR animal_id IN (father, mother)
ORDER BY
animal_id
можно преобразовать в это:
SELECT DISTINCT(animal_id) AS animal_id
FROM (
SELECT 0 AS gender, animal_id, father AS parent
FROM animal
UNION ALL
SELECT 1, animal_id, mother
FROM animal
)
START WITH
animal_id = :id
CONNECT BY
parent = PRIOR animal_id
ORDER BY
animal_id
, последний из них более эффективен.
см. эту статью в моем блоге для деталей плана выполнения:
чтобы найти все диапазоны, которые перекрывают данный диапазон, вы можете использовать следующее запрос:
SELECT *
FROM ranges
WHERE end_date >= @start
AND start_date <= @end
, но в SQL Server
этот более сложный запрос дает те же результаты быстрее:
SELECT *
FROM ranges
WHERE (start_date > @start AND start_date <= @end)
OR (@start BETWEEN start_date AND end_date)
, и считаем это или нет, у меня есть статья в моем блоге об этом тоже:
SQL Server
также не хватает эффективного способа сделать кумулятивные агрегаты, поэтому этот запрос:
SELECT mi.id, SUM(mo.value) AS running_sum
FROM mytable mi
JOIN mytable mo
ON mo.id <= mi.id
GROUP BY
mi.id
можно более эффективно переписать, используя, Господи, помоги мне, курсоры (вы меня правильно расслышали:cursors
, more efficiently
и SQL Server
в одном предложении).
эту статью в моем блоге о том, как сделать это:
существует определенный тип запроса, обычно встречающийся в финансовых приложениях, которые ищут эффективный курс для валюты, как этот в Oracle
:
SELECT TO_CHAR(SUM(xac_amount * rte_rate), 'FM999G999G999G999G999G999D999999')
FROM t_transaction x
JOIN t_rate r
ON (rte_currency, rte_date) IN
(
SELECT xac_currency, MAX(rte_date)
FROM t_rate
WHERE rte_currency = xac_currency
AND rte_date <= xac_date
)
этот запрос может быть сильно переписана для использования условие равенства, которое позволяет a HASH JOIN
вместо NESTED LOOPS
:
WITH v_rate AS
(
SELECT cur_id AS eff_currency, dte_date AS eff_date, rte_rate AS eff_rate
FROM (
SELECT cur_id, dte_date,
(
SELECT MAX(rte_date)
FROM t_rate ri
WHERE rte_currency = cur_id
AND rte_date <= dte_date
) AS rte_effdate
FROM (
SELECT (
SELECT MAX(rte_date)
FROM t_rate
) - level + 1 AS dte_date
FROM dual
CONNECT BY
level <=
(
SELECT MAX(rte_date) - MIN(rte_date)
FROM t_rate
)
) v_date,
(
SELECT 1 AS cur_id
FROM dual
UNION ALL
SELECT 2 AS cur_id
FROM dual
) v_currency
) v_eff
LEFT JOIN
t_rate
ON rte_currency = cur_id
AND rte_date = rte_effdate
)
SELECT TO_CHAR(SUM(xac_amount * eff_rate), 'FM999G999G999G999G999G999D999999')
FROM (
SELECT xac_currency, TRUNC(xac_date) AS xac_date, SUM(xac_amount) AS xac_amount, COUNT(*) AS cnt
FROM t_transaction x
GROUP BY
xac_currency, TRUNC(xac_date)
)
JOIN v_rate
ON eff_currency = xac_currency
AND eff_date = xac_date
несмотря на то, что он громоздкий, как ад, последний запрос 6
раза быстрее.
основная идея здесь-замена <=
С =
, что требует создания таблицы календаря в памяти. к JOIN
С.
вот несколько из работы с Oracle 8 & 9 (конечно, иногда наоборот может сделать запрос проще или быстрее):
скобки могут быть удалены, если они не используются, чтобы изменить приоритет оператора. Простой пример, когда все логические операторы в where
предложение то же самое:where ((a or b) or c)
эквивалентно where a or b or c
.
подзапрос может часто (если не всегда) быть слился с основным запросом чтобы упростить его. По моему опыту, это часто значительно повышает производительность:
select foo.a,
bar.a
from foomatic foo,
bartastic bar
where foo.id = bar.id and
bar.id = (
select ban.id
from bantabulous ban
where ban.bandana = 42
)
;
эквивалентно
select foo.a,
bar.a
from foomatic foo,
bartastic bar,
bantabulous ban
where foo.id = bar.id and
bar.id = ban.id and
ban.bandana = 42
;
используя соединения ANSI отделяет много логики" code monkey " от действительно интересных частей предложения where: предыдущий запрос эквивалентен
select foo.a,
bar.a
from foomatic foo
join bartastic bar on bar.id = foo.id
join bantabulous ban on ban.id = bar.id
where ban.bandana = 42
;
если вы хотите проверить наличие строки, Не используйте count (*), вместо того, чтобы использовать rownum = 1
или поместите запрос в where exists
предложение для извлечения только одной строки вместо все.
- Я полагаю, что очевидным является поиск любых курсоров, которые могут быть заменены операцией на основе SQL 'Set'.
- далее в моем списке искать любые коррелированные подзапросы, которые могут быть переписаны как некоррелированный запрос
- в длинных хранимых процедурах разбейте отдельные операторы SQL на свои собственные хранимые процедуры. Таким образом, они получат собственный кэшированный план запроса.
- ищите транзакции, у которых может быть сокращена область действия. Я регулярно нахожу операторы внутри транзакции, которые могут безопасно находиться снаружи.
- Sub-selects часто могут быть переписаны как прямые соединения (современные оптимизаторы хороши в обнаружении простых)
как упоминал @Quassnoi, оптимизатор часто делает хорошую работу. Один из способов помочь в этом-обеспечить обновление индексов и статистики и наличие подходящих индексов для рабочей нагрузки запроса.
мне нравится заменять все виды подзапросов на запрос join.
это очевидно :
SELECT *
FROM mytable mo
WHERE EXISTS
(
SELECT *
FROM othertable o
WHERE o.othercol = mo.col
)
by
SELECT mo.*
FROM mytable mo inner join othertable o on o.othercol = mo.col
и это по оценкам :
SELECT *
FROM mytable mo
WHERE NOT EXISTS
(
SELECT *
FROM othertable o
WHERE o.othercol = mo.col
)
by
SELECT mo.*
FROM mytable mo left outer join othertable o on o.othercol = mo.col
WHERE o.othercol is null
это может помочь СУБД выбрать хороший план выполнения в большом запросе.
Мне нравится, что все в команде следуют набору стандартов, чтобы сделать код читаемым, ремонтопригодным, понятным, моющимся и т. д.. :)
- все используют один и тот же псевдоним
- нет курсоров. нет петель
- зачем даже думать о том, когда вы можете существует
- абзац
- согласованность в стиле кодирования
здесь есть еще кое-какие мелочи Каковы некоторые из ваших самых полезных стандартов базы данных?
учитывая природу SQL, вы абсолютно должны знать о последствиях производительности любого рефакторинга. рефакторинг SQL приложений - хороший ресурс по рефакторингу с большим акцентом на производительность (см. Главу 5).
хотя упрощение не может равняться оптимизации, упрощение может быть важно при написании читаемого кода SQL, что, в свою очередь, имеет решающее значение для проверки вашего кода SQL на концептуальную корректность (а не синтаксическую корректность, которую ваша среда разработки должна проверить для вас). Мне кажется, что в идеальном мире мы бы написали самый простой, читаемый SQL-код, а затем оптимизатор переписал бы этот SQL-код в любой форме (возможно, более подробный) самый быстрый.
я обнаружил, что думать о SQL-операторах, основанных на логике набора, очень полезно, особенно если мне нужно объединить предложения where или выяснить сложное отрицание предложения where. Я использую законы булевой алгебры в этом случае.
наиболее важными для упрощения предложения where, вероятно, являются законы деморгана (обратите внимание, что " · "есть" и " и " + "есть " или"):
- не (x · y) = Не x + не y
- не (x + y) = Не x · не y
это переводится в SQL на:
NOT (expr1 AND expr2) -> NOT expr1 OR NOT expr2
NOT (expr1 OR expr2) -> NOT expr1 AND NOT expr2
эти законы могут быть очень полезны при упрощении выражений where с большим количеством вложенных элементов AND
и OR
запасные части.
также полезно помнить, что утверждение field1 IN (value1, value2, ...)
эквивалентно field1 = value1 OR field1 = value2 OR ...
. Это позволяет вам отрицать IN ()
одним из двух способов:
NOT field1 IN (value1, value2) -- for longer lists
NOT field1 = value1 AND NOT field1 = value2 -- for shorter lists
подзапрос также можно рассматривать таким образом. Например, это отрицается где пункт:
NOT (table1.field1 = value1 AND EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2))
можно переписать так:
NOT table1.field1 = value1 OR NOT EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2))
эти законы не говорят вам, как преобразовать SQL-запрос с помощью подзапроса в один с помощью соединения, но логика boolean может помочь вам понять типы соединений и то, что ваш запрос должен возвращать. Например, с таблицами A
и B
, an INNER JOIN
как A AND B
, a LEFT OUTER JOIN
как (A AND NOT B) OR (A AND B)
что упрощает к A OR (A AND B)
и FULL OUTER JOIN
is A OR (A AND B) OR B
что упрощает для A OR B
.
мой подход заключается в изучении реляционной теории в целом и реляционной алгебры в частности. Затем научитесь определять конструкции, используемые в SQL для реализации операторов из реляционной алгебры (например, универсальная квантификация a.к. a. деление) и исчисление (например, экзистенциальная квантификация). Суть в том, что SQL имеет функции, не найденные в реляционной модели, например нули, которые, вероятно, лучше всего рефакторировать. Рекомендуемое чтение:SQL и реляционная теория: Как писать Точный код SQL по C. J. Date.
в этом ключе я не убежден ,что "тот факт, что большинство Подселектов могут быть переписаны как соединение", представляет собой упрощение.
возьмите этот запрос, например:
SELECT c
FROM T1
WHERE c NOT IN ( SELECT c FROM T2 );
переписать с помощью JOIN
SELECT DISTINCT T1.c
FROM T1 NATURAL LEFT OUTER JOIN T2
WHERE T2.c IS NULL;
соединение более многословно!
альтернативно, признать, что конструкция реализует антисоединение на проекции c
например, псевдо algrbra
T1 { c } antijoin T2 { c }
упрощение использование реляционных операторов:
SELECT c FROM T1 EXCEPT SELECT c FROM T2;