Есть ли хороший способ проверить правила против N столбцов?
-
09-09-2019 - |
Вопрос
Предположим, что у вас есть правила таблицы с 3 столбцами A, B и C. Поскольку данные входят в систему, я хочу знать, соответствует ли какая -либо строка таблицы правил с условием, что, если соответствующий столбец в таблице правил равна нулю , все данные совпадают. Очевидный SQL:
SELECT * FROM RULES
WHERE (A = :a OR A IS NULL)
AND (B = :b OR B IS NULL)
AND (C = :c OR C IS NULL)
Так что, если у меня есть правила:
RULE A B C 1 50 NULL NULL 2 51 xyz NULL 3 51 NULL 123 4 NULL xyz 456
Вход (50, XYZ, 456) будет соответствовать правилам 1 и 4.
Вопрос: Есть лучший способ сделать это? Только с 3 полями это не проблема. Но на фактической таблице будет 15 столбцов, и я беспокоюсь о том, насколько хорошо этот SQL масштабирует.
Предположение: Альтернативное заявление SQL, которое я придумал, включал добавление дополнительного столбца в таблицу с подсчетом того, сколько полей не является нулевым. (Таким образом, в примере значение этого столбца для правил 1-4 равно 1, 2, 2 и 2 соответственно.) С помощью этого столбца COL_COUNT »выбор может быть:
SELECT * FROM RULES
WHERE (CASE WHEN A = :a THEN 1 ELSE 0 END)
+ (CASE WHEN B = :b THEN 1 ELSE 0 END)
+ (CASE WHEN C = :c THEN 1 ELSE 0 END)
= COL_COUNT
К сожалению, у меня нет достаточно образцов данных, чтобы найти наш, какой из этих подходов будет работать лучше. Прежде чем я начну создавать случайные правила, я подумал, что спрошу здесь, был ли лучший подход.
Примечание: Методы интеллектуального анализа данных и ограничения столбцов здесь не возможны. Данные должны быть проверены по мере того, как он входит в систему, и поэтому они могут быть немедленно помечены. И пользователи контролируют добавление или удаление правил, поэтому я не могу преобразовать правила в ограничения столбцов или другие операторы определения данных.
И последнее, в конце концов мне нужен список всех правил, которые данные не проходят. Решение не может прервать при первом сбое.
Спасибо.
Решение
Первый запрос, который вы предоставили, идеально подходит. Я действительно сомневаюсь, что добавление колонки, о которой вы говорили, даст вам большую скорость, поскольку не нулевая свойство каждой записи в любом случае проверяется, поскольку каждое сравнение с нулевым дает ложным. Так что я предполагаю, что x=y
расширяется до x IS NOT NULL AND x=y
внутренне. Может быть, кто -то другой может уточнить это.
Все другие оптимизации, о которых я могу придумать, будут включать предварительное расселение или кэширование. Вы можете создать [временные] таблицы, соответствующие определенным правилам, или добавить дополнительные столбцы, содержащие правила сопоставления.
Другие советы
Слишком много строк/правил? Если это не так (это субъективно, но скажем, менее 10 000), вы можете создать индексы для всех столбцов.
Это значительно увеличит скорость, и индексы не займет много места.
Если вы не планируете создавать огромную таблицу правил, то держу пари, что ваш подход в порядке, если вы индексируете все столбцы.
Почему бы не сделать индексы вашей таблицы правил по значениям? Тогда ты можешь
SELECT myvalue FROM RULES_A
Похоже, что у вас действительно есть правила и наборы правил. Моделирование таким образом, таким образом, не только сделает это конкретное кодирование намного проще, но также сделает модель расширять, когда вы решите, что вам нужно 16 столбцов.
Например:
CREATE TABLE Rules (
rule_id INT NOT NULL,
rule_category CHAR(1) NOT NULL, -- This is like your column idea
rule_int_value INT NULL,
rule_str_value VARCHAR(20) NULL,
CONSTRAINT PK_Rules PRIMARY KEY CLUSTERED (rule_id),
CONSTRAINT CK_Rules_one_value CHECK (rule_int_value IS NULL OR rule_str_value IS NULL)
)
CREATE TABLE Rule_Sets (
rule_set_id INT NOT NULL,
rule_id INT NOT NULL,
CONSTRAINT PK_Rule_Sets PRIMARY KEY CLUSTERED (rule_set_id, rule_id)
)
Некоторые данные, которые соответствовали бы вам данным правилам:
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (1, 'A', 50, NULL)
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (2, 'A', 51, NULL)
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (3, 'B', NULL, 'xyz')
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (4, 'C', 123, NULL)
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (5, 'C', 456, NULL)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (1, 1)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (2, 2)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (2, 3)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (3, 2)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (3, 4)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (4, 3)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (4, 5)
Тестовый скрипт, который подтверждает тот же ответ, который вы ожидаете:
DECLARE
@a INT,
@b VARCHAR(20),
@c INT
SET @a = 50
SET @b = 'xyz'
SET @c = 456
SELECT DISTINCT
rule_set_id AS failed_rule_set_id
FROM
Rule_Sets RS
WHERE
NOT EXISTS (SELECT * FROM Rules R WHERE R.rule_id = RS.rule_id AND @a = R.rule_int_value) AND
NOT EXISTS (SELECT * FROM Rules R WHERE R.rule_id = RS.rule_id AND @b = R.rule_str_value) AND
NOT EXISTS (SELECT * FROM Rules R WHERE R.rule_id = RS.rule_id AND @c = R.rule_int_value)
Если вы можете представить входные данные в форме, основанной на сборе, а не в качестве отдельных параметров, то окончательный оператор SQL может быть более динамичным и не придется расти, когда вы добавляете дополнительные столбцы.
SELECT * FROM RULES
WHERE (A = :a OR A IS NULL)
AND (B = :b OR B IS NULL)
AND (C = :c OR C IS NULL);
В зависимости от ваших RBDM, это может или не может быть более эффективным, хотя и не очень:
SELECT * FROM RULES
WHERE coalesce(A, :a) = :a
AND coalesce(B, :b) = :b
AND coalesce(C, :c) = :c ;
В MySQL (ваши RBDM могут делать это по -другому), этот запрос позволяет index
сканировать, а не ref_or_null
сканировать, если есть применимый индекс. Если индекс охватывает все столбцы, он позволяет использовать весь индекс (и действительно, если индекс охватывает все столбцы, индекс является Таблица).
С вашим запросом, ref_or_null
доступ к index
Используется доступ только первый столбец в многоцелевом индексе. С ref_or_null
, MySQL должен искать индекс для совпадений, а затем снова искать NULLS. Поэтому мы используем индекс дважды, но никогда не используем весь индекс.
Но с Coalesce у вас есть накладные расходы выполнения функции Coalesce по каждому значению столбца. Что быстрее, вероятно, зависит от того, сколько у вас правил, сколько столбцов в каждой строке и используемого индекса, если таковые имеются.
Будь то более читаемо - это вопрос мнения.