#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» были определены как «Количество входных данных = ожидаемое значение в условном»