Все строки, в которых хотя бы у одного дочернего элемента есть все его собственные дочерние элементы, передают условие

#sql #sql-server

#sql #sql-server

Вопрос:

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

Проблема

Допустим, у меня есть несколько таблиц:

 Prize
  - PrizeId

RulePrize_Map
  - PrizeId
  - RuleId

Rule
  - RuleId

Conditional
  - ConditionalId
  - RuleId
  - InputId
  - ExpectedValue (bit)

Input
  - InputId
  

A Prize считается выигранным, когда хотя бы один Rule имеет значение true. A Rule является истинным, когда все его Conditionals значения истинны. A Conditional является «истинным», когда оно InputId либо присутствует, либо отсутствует в Input таблице, как указано в ExpectedValue поле. Возможно, это можно было бы рассматривать как эквивалентное: Count(InputId in Input table) = ExpectedValue для Conditional's InputId .

Некоторые примеры:

 Conditional (InputId = 11, ExpectedValue = 1) -> True if InputId 11 in Input Table
Conditional (InputId = 12, ExpectedValue = 0) -> True if Inputid 12 NOT in Input Table
  

Моя цель

Я хочу получить все, Prizes где хотя бы один Rule является «истинным». Я бы согласился на: «Все Rules , которые являются истинными».

Моя попытка

 select p.PrizeId from Prize p INNER JOIN RulePrize_Map rpm ON rpm.PrizeId = p.PrizeId
WHERE p.PrizeId IN
(select r.PrizeId from Rule r
where
  (select count(*) from Conditional c1 where c1.RuleId = r.RuleId)
  = 
  (select count(*) from Conditional c2
   where c2.RuleId = r.RuleId AND
   (select count(*) from Input i where i.InputId = c2.InputId) = c2.ExpectedValue
  )
)
GROUP BY p.prizeId
  

Комментарии:

1. Просто идея: может быть, посмотреть на исключение призов, где все правила являются ложными…

Ответ №1:

Вопрос немного изменился, поэтому я переделал ответ…

 SELECT
  PrizeId
FROM
  (
  SELECT
    PrizeRule_Map.PrizeId,
    PrizeRule_Map.RuleId
  FROM
    PrizeRule_Map
  INNER JOIN
    Rule
      ON Rule.RuleId = PrizeRule_Map.RuleId
  INNER JOIN
    Conditional
      ON Conditional.RuleId = Rule.RuleID
  LEFT JOIN
    Input
      ON Input.InputId = Conditional.InputID
  GROUP BY
    PrizeRule_Map.PrizeId,
    PrizeRule_Map.RuleId
  HAVING
    COUNT(*) = SUM(CASE Conditional.ExpectedValue
                     WHEN 1 THEN CASE WHEN Input.InputId IS NULL THEN 0 ELSE 1 END
                     WHEN 0 THEN CASE WHEN Input.InputId IS NULL THEN 1 ELSE 0 END
                   END
                   )
  )
    AS map
GROUP BY
  PrizeId
  

Комментарии:

1. Это делает «где любое правило истинно». Если вы хотите, «где все правила верны», затем удалите строки «PrizeRule_Map.RuleId» из SELECT и GROUP BY основного запроса (и тогда вам также не нужен запрос-оболочка).

2. Большое спасибо за обновление «Приза» в соответствии с изменением вопроса.

3. @Dems: LEFT JOIN ON Input.InputId = ... , вы пропускаете Input

4. @ypercube: нет, это было намеренно, я хотел посмотреть, заметит ли OP. Честно. покраснение

5. @Dems — lol Я сделал, но не хотел быть сторонником идеального ответа

Ответ №2:

Чтобы получить все RuleId строки, в которых ВСЕ условные обозначения верны:

 SELECT r.RuleID
FROM Rule r
  JOIN Conditional c
    ON c.RuleId = r.RuleId
  LEFT JOIN Input i
    ON i.InputId = c.InputId
GROUP BY r.RuleID
HAVING COUNT( CASE WHEN (c.ExpectedValue=1) AND (i.InputId IS NOT NULL)
                     OR (c.ExpectedValue=0) AND (i.InputId IS NULL)
                   THEN 1
                   ELSE NULL 
              END ) 
       = COUNT( * )
  

Другой способ — возможно, более медленный, но проверить скорость не помешает. Оно не использует CASE , но разницу ( EXCEPT ) двух JOIN строк, только одна из них использует GROUP BY :

     SELECT r.RuleID
    FROM Rule r
      JOIN Conditional c
        ON c.RuleId = r.RuleId
      LEFT JOIN Input i
        ON i.InputId = c.InputId
    WHERE c.ExpectedValue = 1
    GROUP BY r.RuleID
    HAVING COUNT( i.InputId ) = COUNT( * )
  EXCEPT
    SELECT r.RuleID
    FROM Rule r
      JOIN Conditional c
        ON c.RuleId = r.RuleId
      JOIN Input i
        ON i.InputId = c.InputId
    WHERE c.ExpectedValue = 0
  

Комментарии:

1. Вы использовали COUNT(NULL|1) там, где я использовал SUM(0|1) , знаете ли вы о каких-либо различиях в производительности для этого?

2. Нужно указать i.InputId или c.InputId в логической логике?

3. Довольно умный вариант второй. Мне также нравится Count метод для наглядности.

Ответ №3:

Попробуйте следующее:

 SELECT Rule.RuleId, Rule.RuleName
FROM Rule
INNER JOIN Conditional ON Rule.RuleId = Conditional.RuleId
Where Conditional.ExpectedValue == true
  

Комментарии:

1. Ваш запрос не учитывает «все» условные обозначения, связанные с правилом, и даже не просматривает содержимое Input .

2. Это показало бы все правила, в которых выполняется только одно условие. Это также не вычисляет условие должным образом (ExpectedValue == true не обязательно делает условие истинным, вместо этого оно указывает, что условие истинно, если входная таблица содержит inputId условия).

3. Это не обрабатывает expectedVluae = false и наличие значения во входной таблице

4. @pascal, @Graphain и @Jon Egerton. Вопрос был изменен. Теперь цель другая. Мой ответ касается предыдущей цели, т. е «Я хочу получить все правила, в которых все их условные обозначения являются «истинными»».

5. Прочитайте исходный вопрос, условия «true» были определены как «Количество входных данных = ожидаемое значение в условном»