Общие правила упрощения инструкций 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;