Есть ли хороший способ проверить правила против N столбцов?

StackOverflow https://stackoverflow.com/questions/749044

  •  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 по каждому значению столбца. Что быстрее, вероятно, зависит от того, сколько у вас правил, сколько столбцов в каждой строке и используемого индекса, если таковые имеются.

Будь то более читаемо - это вопрос мнения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top