Мне нужно какое-то условное соединение - PullRequest
2 голосов
/ 24 сентября 2010

Хорошо, я знаю, что есть несколько постов, в которых обсуждается это, но моя проблема не может быть решена условным оператором where (объединенное решение).

У меня есть три join оператора, и в зависимости от параметров запроса мне может понадобиться выполнить любую комбинацию из трех. Мой оператор Join довольно дорогой, поэтому я хочу выполнять объединение только тогда, когда это нужно запросу, и я не готов написать 7-комбинационный оператор IF..ELSE.. для выполнения этих комбинаций.

Вот что я использовал для решений до сих пор, но все они были не идеальными:

LEFT JOIN joinedTable jt
ON jt.someCol = someCol
WHERE jt.someCol = conditions
OR @neededJoin is null

(Это слишком дорого, потому что я выполняю объединение, даже когда оно мне не нужно, просто не оцениваю объединение)

OUTER APPLY
(SELECT TOP(1) * FROM joinedTable jt 
WHERE jt.someCol = someCol
AND @neededjoin is null)

(это даже дороже, чем всегда оставляющее соединение)

SELECT @sql = @sql + ' INNER JOIN joinedTable jt ' +
             ' ON jt.someCol = someCol ' +
             ' WHERE (conditions...) '

(это IDEAL, и как оно написано сейчас, но я пытаюсь преобразовать его из динамического SQL).

Любые мысли или помощь будут великолепны!

EDIT: Если я использую подход динамического SQL, я пытаюсь выяснить, что было бы наиболее эффективным в отношении структурирования моего запроса. Учитывая, что у меня есть три необязательных условия, и мне нужны результаты от всех из них, мой текущий запрос выполняет что-то вроде этого:

IF condition one
SELECT from db
INNER JOIN condition one

UNION

IF condition two
SELECT from db
INNER JOIN condition two

UNION

IF condition three
SELECT from db
INNER JOIN condition three

Мой нединамический запрос выполняет эту задачу, выполняя левые соединения:

SELECT from db
LEFT JOIN condition one
LEFT JOIN condition two
LEFT JOIN condition three
WHERE condition one is true
OR condition two is true
OR condition three is true

Что имеет больше смысла делать? поскольку весь код из оператора «SELECT from db» одинаков? Похоже, что условие объединения более эффективно, но мой запрос ОЧЕНЬ длинный из-за этого ....

Спасибо!

Ответы [ 6 ]

2 голосов
/ 24 сентября 2010
LEFT JOIN
joinedTable jt ON jt.someCol = someCol AND jt.someCol = conditions AND @neededjoin ...
...

ИЛИ

LEFT JOIN
(
SELECT col1, someCol, col2 FROM joinedTable WHERE someCol = conditions AND @neededjoin ...
) jt ON jt.someCol = someCol
...

ИЛИ

;WITH jtCTE AS
(SELECT col1, someCol, col2 FROM joinedTable WHERE someCol = conditions AND @neededjoin ...)
SELECT
...
LEFT JOIN
jtCTE ON jtCTE.someCol = someCol
...

Если честно, такой конструкции, как условный JOIN, не существует, если вы не используете литералы.

Если он находится в операторе SQL, он оценивается ... поэтому не используйте его в операторе SQL с помощью динамического SQL или IF ELSE

1 голос
/ 25 сентября 2010

Попробуйте это:

LEFT JOIN joinedTable jt
   ON jt.someCol = someCol
   AND jt.someCol = conditions
   AND @neededJoin = 1 -- or whatever indicates join is needed

Я думаю, вы найдете, что это хорошая производительность и делает то, что вам нужно.

Обновление

Если это не дает требуемой производительности, то, возможно, это из-за того, что в последний раз я делал это, используя соединения с таблицей. Требуемое значение может быть получено из одной из 3 таблиц, основанных на 2 столбцах, поэтому я построил таблицу 'join-map' следующим образом:

Col1  Col2 TableCode
  1     2    A
  1     4    A
  1     3    B
  1     5    B
  2     2    C
  2     5    C
  1     11   C

Тогда

SELECT
   V.*,
   LookedUpValue =
      CASE M.TableCode
      WHEN 'A' THEN A.Value
      WHEN 'B' THEN B.Value
      WHEN 'C' THEN C.Value
      END
FROM
    ValueMaster V
    INNER JOIN JoinMap M ON V.Col1 = M.oOl1 AND V.Col2 = M.Col2
    LEFT JOIN TableA A ON M.TableCode = 'A'
    LEFT JOIN TableB B ON M.TableCode = 'B'
    LEFT JOIN TableC C ON M.TableCode = 'C'

Это дало мне огромное улучшение производительности запросов к этим таблицам (большинство из них десятки или сотни таблиц с миллионами строк).

Вот почему я спрашиваю, действительно ли вы улучшили производительность. Конечно, он собирается добавить объединение в план выполнения и назначить ему некоторую стоимость, но в целом будет выполнять намного меньше работы , чем какой-то план, который просто без разбора объединяет все 3 таблицы, а затем Coalesce() s. найти правильное значение.

Если вы обнаружите, что по сравнению с динамическим SQL объединение таким способом обходится только на 5% дороже, но при неразборчивых соединениях это на 100% дороже, возможно, вам стоит сделать это из-за правильности, ясность и простота по сравнению с динамическим SQL, и все это, вероятно, более ценно, чем небольшое улучшение (конечно, в зависимости от того, что вы делаете).

Является ли стоимость шкалы с количеством строк также другим фактором, который необходимо учитывать. Если даже с огромным объемом данных вы экономите только 200 мс ЦП на запросе, который не выполняется десятки раз в секунду, использовать его нетрудно.

Причина, по которой я продолжаю настаивать на том факте, что он будет работать хорошо, заключается в том, что даже при совпадении с хешем у него не будет строк для проверки, или у него не будет строк для создания хеша из. Операция хэширования будет остановлена ​​намного раньше по сравнению с использованием запроса WHERE в стиле OR в вашем первоначальном сообщении.

1 голос
/ 24 сентября 2010

Я бы пошел на простой и прямой подход, как это:

DECLARE @ret TABLE(...) ;

IF <coondition one> BEGIN ;
  INSERT INTO @ret() SELECT ...
END ;

IF <coondition two> BEGIN ;
  INSERT INTO @ret() SELECT ...
END ;

IF <coondition three> BEGIN ;
  INSERT INTO @ret() SELECT ...
END ;

SELECT DISTINCT ... FROM @ret ;

Edit: я предлагаю переменную таблицы, а не временную таблицу, чтобы процедура не перекомпилировалась при каждом запуске. Вообще говоря, три более простых вставки имеют больше шансов получить лучшие планы выполнения, чем один большой запрос монстра, объединяющий все три.

Тем не менее, мы не можем угадать производительность. мы должны определить, чтобы определить это. Все же более простые куски кода лучше для удобочитаемости и удобства обслуживания.

1 голос
/ 24 сентября 2010

динамическое решение sql, как правило, является наилучшим для этих ситуаций, но если вам действительно нужно отойти от этого, то ряд инструкций в заштрихованной части будет делать эту работу. Это боль, и вам придется писать гораздо больше кода, но это будет быстрее, чем попытка сделать объединения условными в самом утверждении.

0 голосов
/ 25 сентября 2010

Просто потому, что никто другой не упомянул об этом, вот что вы можете использовать (не динамически).Если синтаксис выглядит странно, то это потому, что я тестировал его в Oracle.

По сути, вы превращаете объединенные таблицы в подвыборы, у которых есть предложение where, которое ничего не возвращает, если ваше условие не соответствует.Если условие совпадает, то дополнительный выбор возвращает данные для этой таблицы.Оператор Case позволяет вам выбрать, какой столбец будет возвращен в общем выборе.

with m as (select 1 Num, 'One' Txt from dual union select 2, 'Two' from dual union select 3, 'Three' from dual),
t1 as (select 1 Num from dual union select 11 from dual),
t2 as (select 2 Num from dual union select 22 from dual),
t3 as (select 3 Num from dual union select 33 from dual)
SELECT m.*
      ,CASE 1
         WHEN 1 THEN
          t1.Num
         WHEN 2 THEN
          t2.Num
         WHEN 3 THEN
          t3.Num
       END SelectedNum
  FROM m
  LEFT JOIN (SELECT * FROM t1 WHERE 1 = 1) t1 ON m.Num = t1.Num
  LEFT JOIN (SELECT * FROM t2 WHERE 1 = 2) t2 ON m.Num = t2.Num
  LEFT JOIN (SELECT * FROM t3 WHERE 1 = 3) t3 ON m.Num = t3.Num
0 голосов
/ 24 сентября 2010

Динамическое SQL-решение является лучшим во многих отношениях; вы пытаетесь выполнить разные запросы с разным количеством объединений, не переписывая запрос, чтобы выполнить разное количество объединений - и это не очень хорошо с точки зрения производительности.


Когда я занимался такими вещами около или около того назад (скажем, в начале 90-х), я использовал язык I4GL, и запросы были построены с использованием оператора CONSTRUCT. Это использовалось для генерации части предложения WHERE, поэтому (на основе пользовательского ввода) сгенерированные критерии фильтрации могут выглядеть следующим образом:

a.column1 BETWEEN 1 AND 50 AND
b.column2 = 'ABCD' AND
c.column3 > 10

В те дни у нас не было современных обозначений JOIN; Я собираюсь немного импровизировать, как мы идем. Обычно есть основная таблица (или набор основных таблиц), которые всегда являются частью запроса; Есть также несколько таблиц, которые могут быть частью запроса. В приведенном выше примере я предполагаю, что «c» является псевдонимом для основной таблицы. Код работал бы так:

  • Обратите внимание, что в запросе указана таблица 'a':
    • Добавить 'FullTableName AS a' в предложение FROM
    • Добавить условие соединения 'AND a.join1 = c.join1' в предложение WHERE
  • Обратите внимание, что ссылка на таблицу 'b' была ...
    • Добавить биты в предложение FROM и предложение WHERE.
  • Соберите оператор SELECT из списка выбора (обычно фиксированного), предложения FROM и предложения WHERE (иногда с такими украшениями, как GROUP BY, HAVING или ORDER BY).

Здесь следует применить ту же базовую технику, но детали немного отличаются.

Прежде всего, у вас нет строки для анализа; из других обстоятельств вы знаете, какие таблицы нужно добавить в свой запрос. Итак, вам все еще нужно спроектировать вещи, чтобы их можно было собрать, но ...

  • Предложение SELECT с его списком выбора, вероятно, исправлено. Он определит таблицы, которые должны присутствовать в запросе, поскольку значения извлекаются из этих таблиц.
  • Предложение FROM, вероятно, будет состоять из серии объединений.

    • Одна часть будет основным запросом:

      FROM CoreTable1 AS C1
      JOIN CoreTable2 AS C2
           ON C1.JoinColumn = C2.JoinColumn
      JOIN CoreTable3 AS M
           ON M.PrimaryKey = C1.ForeignKey
      
    • При необходимости можно добавить другие таблицы:

      JOIN AuxilliaryTable1 AS A
           ON M.ForeignKey1 = A.PrimaryKey
      
    • Или вы можете указать полный запрос:

      JOIN (SELECT RelevantColumn1, RelevantColumn2
              FROM AuxilliaryTable1
             WHERE Column1 BETWEEN 1 AND 50) AS A
      
    • В первом случае вы должны не забыть добавить критерий WHERE в основное предложение WHERE и довериться оптимизатору СУБД для перемещения условия в таблицу JOIN, как показано. Хороший оптимизатор сделает это автоматически; бедный не может. Используйте планы запросов, чтобы определить, насколько работоспособна ваша СУБД.

  • Добавьте предложение WHERE для любых критериев между таблицами, не охватываемых операциями соединения, и любых критериев фильтрации на основе базовых таблиц. Обратите внимание, что я думаю в первую очередь с точки зрения дополнительных критериев (операции И), а не альтернативных критериев (операции ИЛИ), но вы можете иметь дело с ИЛИ слишком долго, если вы будете осторожны, чтобы заключить выражения в скобки достаточно.

  • Иногда вам может понадобиться добавить пару условий JOIN, чтобы соединить таблицу с ядром запроса, что не является необычным.

  • Добавить любые предложения GROUP BY, HAVING или ORDER BY (или ограничения, или любые другие украшения).

Обратите внимание, что вам необходимо хорошее понимание схемы базы данных и условий соединения. По сути, это кодирование на вашем языке программирования, то, как вы должны думать о построении запроса. Пока вы понимаете это и свою схему, непреодолимых проблем нет.

Удачи ...

...