Попытка построить оператор SQL для сложного сценария поиска - PullRequest
1 голос
/ 17 января 2009

Я пытаюсь построить оператор SQL для следующего сценария поиска:

Я пытаюсь вернуть все столбцы для отдельной записи для таблицы A на основе значения столбца состояния в таблице B. Каждая запись в таблице A может иметь несколько строк в таблице B, что делает ее один ко многим отношения. Столбец состояния обнуляется с типом данных integer.

Вот возможные значения для статуса в таблице B:

  • NULL = В ожидании,
  • 1 = Одобрено,
  • 2 = Отказано,
  • 6 = Принудительное утверждение,
  • 7 = Принудительный отказ

Конечный пользователь может выполнять поиск по следующим сценариям:

  • Одобрено - все записи таблицы B должны иметь значение 1 или 6. Для статуса.
  • Запрещено - одна запись таблицы B должна иметь значение 2 или 5. Любые другие записи могут иметь значение 1,6 или ноль.
  • Ожидание - все записи таблицы B могут иметь значение 1,6 или ноль. Одна запись должна быть нулевой, поскольку она не считается завершенной.

ОБНОВЛЕНИЕ
Я проконсультировался с одним из наших администраторов баз данных, и он разработал следующее решение:

Утверждено:

SELECT a.* FROM TableA a INNER JOIN TableB ON b.id = a.id
WHERE
(b.status in (1,6) and b.status IS NOT NULL) AND
b.id NOT IN (SELECT id from TableB  WHERE status IS NULL)
AND b.id NOT IN (SELECT id from TableB WHERE status in (2,7))

Denied:

SELECT a.* FROM TableA a INNER JOIN TableB ON b.id = a.id
WHERE
(b.status in (2,7))

В ожидании:

SELECT a.* FROM TableA a INNER JOIN TableB ON b.id = a.id
WHERE
(b.status IN (1,6) OR b.status IS NULL)
AND b.id NOT IN (SELECT b.id FROM TableA a INNER JOIN TableB b ON b.id = a.id WHERE (b.status IN (1,6) AND b.status IS NOT NULL) AND b.id NOT IN (SELECT id from TableB WHERE status IS NULL))
AND b.id NOT IN (SELECT id FROM TableB WHERE status IN (2,7))

ОБНОВЛЕНИЕ 2:
@ Micth Wheat - Как бы я провел рефакторинг следующего решения, используя ключевое слово t-sql EXIST / NOT EXIST?

Ответы [ 5 ]

2 голосов
/ 17 января 2009

В качестве примера для «Одобрено»:

select 
    * 
from 
    A 
where
    (select count(*) from B where B.parent_id = A.id and B.status in (1,6)) > 0
and (select count(*) from B where B.parent_id = A.id and B.status not in (1,6)) = 0

Рефакторинг для использования существует и не существует :

select 
    * 
from 
    A 
where
    exists (select * from B where B.parent_id = A.id and B.status in (1,6)) 
and not exists (select * from B where B.parent_id = A.id and B.status not in (1,6)) 

Если вы ввели критерии, вы можете упаковать их в один запрос, например, если это более удобно:

select 
    * 
from 
    A 
where     
    (@Criteria = 'Approved'
and (select count(*) from B where B.parent_id = A.id and B.status in (1,6)) > 0
and (select count(*) from B where B.parent_id = A.id and B.status not in (1,6)) = 0
    )
or  (@Criteria = 'Denied'
and (select count(*) from B where B.parent_id = A.id and B.status in (2,7)) > 0
    )
or  (@Criteria = 'Pending'
and (select count(*) from B where B.parent_id = A.id and B.status not in (2,7)) = 0
and (select count(*) from B where B.parent_id = A.id and B.status is null) > 0
    )

Обратите внимание, что я изменил пример Запретить, чтобы он стал значением 2 и 7, а не 2 и 5, на основе ваших данных выборки.

Редактировать: Вы также можете использовать существует и не существует , как предполагает Джо.

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

1 голос
/ 17 января 2009

Из того, что я прочитал, Крис Тейшейра и Хова используют в основном ту же логику, но;
- Hova анализирует таблицы только один раз
- Крис Тейшейра разбирает таблицы несколько раз
=> Техника Ховы предпочтительна (на мой взгляд)

Однако, Хова сделал это немного неправильно ...

Логика должна быть:

- If Any 2 or 7 records   => DENIED
- ElseIf Any NULL records => PENDING
- Else                    => ACCEPTED

Это дает следующий код ...

SELECT
    [main].id,
    CASE WHEN MAX(CASE WHEN [status].value IN (2,7) THEN 1 ELSE 0 END) = 1 THEN 'DENIED'
         WHEN MAX(CASE WHEN [status].value IS NULL  THEN 1 ELSE 0 END) = 1 THEN 'PENDING'
         ELSE 'ACCEPTED' END
FROM
    [main]
INNER JOIN
    [status]
        ON [main].id = [status].main_id
GROUP BY
    [main].id

Кроме того, использование MAX, а не SUM (которое использовал Hova) означает, что механизм запросов должен находить только одно совпадение, а не несколько. Кроме того, оптимизатору проще использовать соответствующие индексы в таблице [status]. (В этом случае индекс будет (main_id, value) в указанном порядке в таблице [status].

Демс.

EDIT:

Нечто подобное может быть следующим. У меня нет экземпляра SQL для тестирования здесь, поэтому я не могу сказать вам, если это быстрее, но я представляю, что это может быть ...

SELECT
    [main].id,
    MIN(CASE WHEN [status].value IN (2,7) THEN -1        -- Denied
             WHEN [status].value IS NULL  THEN  0        -- Pending
             ELSE                               1  END)  -- Accepted
FROM
    [main]
INNER JOIN
    [status]
        ON [main].id = [status].main_id
GROUP BY
    [main].id

EDIT:

Другой вариант - просто присоединиться к таблице сопоставления вместо использования операторов CASE.

DECLARE @map TABLE (
   status_value   INT,
   status_result  INT
   )

INSERT INTO @map VALUES (1,    1)
INSERT INTO @map VALUES (2,   -1)
INSERT INTO @map VALUES (6,    1)
INSERT INTO @map VALUES (7,   -1)
INSERT INTO @map VALUES (NULL, 0)

SELECT
    [main].id,
    MIN([map].status_result)
FROM
    [main]
INNER JOIN
    [status]
        ON [main].id = [status].main_id
INNER JOIN
    @map AS [map]
        ON [status].value = [map].status_value
        OR ([status].value IS NULL AND [map].status_value IS NULL)
        -- # This has been faster than using ISNULLs in my experience...
GROUP BY
    [main].id
0 голосов
/ 17 января 2009

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

Утверждено:

SELECT
    a.*
FROM
    TableA a
WHERE
    NOT EXISTS (SELECT * FROM TableB b WHERE b.id = a.id AND b.status IN (NULL,1,6))

Denied:

SELECT
    a.*
FROM
    TableA a
WHERE
    EXISTS (SELECT * FROM TableB b WHERE b.id = a.id AND b.status IN (1,6))

В ожидании:

SELECT
    a.*
FROM
    TableA a
WHERE
    EXISTS (SELECT * FROM TableB b WHERE b.id = a.id AND b.status IS NULL)
    AND NOT EXISTS (SELECT * FROM TableB b WHERE b.id = a.id AND b.status IN (1,6))
0 голосов
/ 17 января 2009

Возможно, что-то вроде этого

select CASE WHEN SUM(CASE WHEN ISNULL(b.status, 0) IN (1,6) THEN 1 ELSE 0 END) = COUNT(*) THEN 'Approved'
            WHEN SUM(CASE WHEN ISNULL(b.status, 0) IN (2,5) THEN 1 ELSE 0 END) > 0 THEN 'Denied'
            WHEN SUM(CASE WHEN ISNULL(b.status, 0) IN (1,0,6) THEN 1 ELSE 0 END) = COUNT(*)
                AND SUM(CASE WHEN b.status IS NULL THEN 1 else 0 END) > 0  THEN 'Pending'
            ELSE '???' END as Status, <a column list>
FROM A 
INNER JOIN b ON a.id = b.id
group by <a column list>
0 голосов
/ 17 января 2009

Похоже, вы хотели бы использовать некоторые существующие операторы.

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

- Approved

exists(select 1 from B where A.id = B.id and status in (1,6))
and not exists(select 1 from B where A.id = B.id and (status is null or status not in (1,6)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...