Динамическая фильтрация таблицы SQL Server с использованием нескольких объединений - PullRequest
1 голос
/ 07 июня 2011

Я пытаюсь отфильтровать одну таблицу (основную) по значениям во многих других таблицах (filter1, filter2, filter3 ... filterN), используя только объединения.

Я хочу применить следующие правила:

(A) Если в таблице фильтров существует одна или несколько строк, включите в таблицу только те строки из мастера, которые соответствуют значениям в таблице фильтров.

(B) Если в таблице фильтров нет строк, игнорируйте ее и верните все строки из основной таблицы.

(C) Это решение должно работать для таблиц фильтров N в комбинации.

(D) Статический SQL с использованием только синтаксиса JOIN, без динамического SQL.

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


Что я пробовал:

  1. Различные ВНУТРЕННИЕ СОЕДИНЕНИЯ между мастер-таблицами и таблицами фильтров - работает для (A), но не работает на (B), потому что соединение удаляет все записи со стороны мастера (слева), когда на стороне фильтра (справа) нет строк.

  2. LEFT JOINS - Always возвращает все записи с главной (левой) стороны. Это терпит неудачу (A), когда некоторые таблицы фильтров имеют записи, а некоторые нет.


Что мне действительно нужно:

Мне кажется, что мне нужно иметь возможность INNER JOIN для каждой таблицы фильтров, в которой есть 1 или более строк, и LEFT JOIN (или вообще не JOIN) для каждой пустой таблицы фильтров.

Мой вопрос: Как бы я это сделал, не прибегая к Dynamic SQL?

Ответы [ 7 ]

0 голосов
/ 08 июня 2011

В SQL Server 2005+ вы можете попробовать это:

WITH
  filter1 AS (
    SELECT DISTINCT
      m.ID,
      HasMatched = CASE WHEN f.ID IS NULL THEN 0 ELSE 1 END,
      AllHasMatched = MAX(CASE WHEN f.ID IS NULL THEN 0 ELSE 1 END) OVER ()
    FROM masterdata m
      LEFT JOIN filtertable1 f ON <i>join_condition</i>
  ),
  filter2 AS (
    SELECT DISTINCT
      m.ID,
      HasMatched = CASE WHEN f.ID IS NULL THEN 0 ELSE 1 END,
      AllHasMatched = MAX(CASE WHEN f.ID IS NULL THEN 0 ELSE 1 END) OVER ()
    FROM masterdata m
      LEFT JOIN filtertable2 f ON <i>join_condition</i>
  ),
  …
SELECT m.*
FROM masterdata m
  INNER JOIN filter1 f1 ON m.ID = f1.ID AND f1.HasMatched = f1.AllHasMatched
  INNER JOIN filter2 f2 ON m.ID = f2.ID AND f2.HasMatched = f2.AllHasMatched
  …

Насколько я понимаю, таблицы фильтров без совпадений просто не должны влиять на результирующий набор.Вывод должен состоять только из тех masterdata строк, которые соответствуют всем фильтрам, в которых совпадения имеют .

0 голосов
/ 31 января 2017

Поскольку у вас есть таблицы фильтров, я предполагаю, что эти таблицы, вероятно, динамически заполняются из внешнего интерфейса. Это будет означать, что у вас есть эти таблицы как #temp_table (или даже материализованная таблица, на самом деле не имеет значения) в вашем сценарии перед фильтрацией по таблице основных данных.

Лично я использую приведенный ниже бит кода для динамической фильтрации без использования динамического SQL.

SELECT *
FROM [masterdata] [m]
INNER JOIN
    [filter_table_1] [f1]
ON
    [m].[filter_column_1] = ISNULL(NULLIF([f1].[filter_column_1], ''), [m].[filter_column_1])

Как видите, код NULL соответствует условию JOIN, если значение столбца является пустой записью в таблице фильтров. Однако суть этого в том, что вам придется активно заполнять значение столбца пустым на тот случай, если у вас нет записей фильтра, для которых вы хотите сократить общий набор основных данных. После того, как вы заполнили таблицу фильтров пробелом, в этих случаях условие СОЕДИНЕНИЯ будет ПУСТО (NULL), а вместо этого объединится с тем же столбцом из таблицы основных данных. Это должно работать во всех случаях, которые вы упомянули в своем вопросе.

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

Надеюсь, это поможет. Пожалуйста, дайте мне знать в комментариях.

0 голосов
/ 08 июня 2011

Ранее я публиковал - теперь удалил - ответ, основанный на неверных предположениях о ваших проблемах.

Но я думаю, что вы могли бы пойти на решение, в котором вы разбили свою начальную проблему поиска на вопрос построения набора идентификаторов из основной таблицы, а затем выбрали объединение данных для этого набора идентификаторов. Здесь я, естественно, предполагаю, что у вас есть своего рода идентификатор на вашем главном столе Таблицы фильтров содержат только значения фильтров. Затем это можно объединить в приведенную ниже инструкцию, где каждый SELECT в подмножестве eligble предоставляет набор главных идентификаторов, они объединяются, чтобы избежать дубликатов, и этот набор идентификаторов объединяется с таблицей с данными.

    SELECT * FROM tblData INNER JOIN 
    (
        SELECT id FROM tblData td 
            INNER JOIN fa on fa.a = td.a
        UNION 
        SELECT id FROM tblData td 
            INNER JOIN fb on fb.b = td.b
        UNION 
        SELECT id FROM tblData td 
            INNER JOIN fc on fc.c = td.c
    ) eligible ON eligible.id = tblData.id

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

    CREATE TABLE tblData (id int not null primary key identity(1,1), a varchar(40), b datetime, c int)

    CREATE TABLE fa (a varchar(40) not null primary key)
    CREATE TABLE fb (b datetime  not null primary key)
    CREATE TABLE fc (c int not null primary key)
0 голосов
/ 07 июня 2011

Во-первых, невозможно иметь "N количество объединений" или "N количество фильтров", не прибегая к динамическому SQL. Язык SQL не был предназначен для динамического определения сущностей, к которым вы обращаетесь.

Во-вторых, один из способов выполнить то, что вы хотите (но будет построен динамически), - это что-то вроде:

Select ...
From master
Where Exists    (
                Select 1
                From filter_1
                Where filter_1 = master.col1
                Union All
                Select 1
                From ( Select 1 )
                Where Not Exists    (
                                    Select 1
                                    From filter_1
                                    )
                Intersect
                Select 1
                From filter_2
                Where filter_2 = master.col2
                Union All
                Select 1
                From ( Select 1 )
                Where Not Exists    (
                                    Select 1
                                    From filter_2
                                    )
                ...
                Intersect
                Select 1
                From filter_N
                Where filter_N = master.colN
                Union All
                Select 1
                From ( Select 1 )
                Where Not Exists    (
                                    Select 1
                                    From filter_N
                                    )
                        )
0 голосов
/ 07 июня 2011

Left Outer Join - дает вам пропущенные записи в главной таблице ....

ВЫБРАТЬ * ИЗ МАСТЕРА M ВНУТРЕННЕЕ ПРИКЛЮЧЕНИЕ A НА A.PK = M.PK ВЛЕВО НАРУЖНОЕ СОЕДИНЕНИЕ F НА F.FK = M.PK

Если у FOREIGN есть ключи, которые не являются частью MASTER, у вас будут «нулевые столбцы», в которых отсутствуют слоты

Я думаю, это то, что вы ищете ...

Майк

0 голосов
/ 07 июня 2011

Выполните внутреннее объединение, чтобы получить результаты только для (A), и выполните левое объединение, чтобы получить результаты только для (B) (вам нужно будет добавить что-то подобное в предложении where: filterN.column is null) объединить результаты внутреннего объединения и осталось присоединиться с UNION.

0 голосов
/ 07 июня 2011
SELECT *
FROM master_table mt
WHERE (0 = (select count(*) from filter_table_1)
      OR mt.id IN (select id from filter_table_1)
  AND (0 = (select count(*) from filter_table_2)
      OR mt.id IN (select id from filter_table_2)
  AND (0 = (select count(*) from filter_table_3)
      OR mt.id IN (select id from filter_table_3)

Имейте в виду, что на практике это может быть неэффективно. Если у вас нет конкретной причины убить ваше существующее, работающее решение, я бы оставил его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...