Есть хороший способ проверить правила по n столбцов? - PullRequest
1 голос
/ 14 апреля 2009

Предположим, у вас есть таблица ПРАВИЛ с 3 столбцами A, B и C. Когда данные поступают в систему, я хочу узнать, соответствует ли какая-либо строка таблицы RULES моим данным с условием, что если соответствующий столбец в ПРАВИЛАХ таблица пуста, все данные совпадают. Очевидный 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

К сожалению, у меня недостаточно образцов данных, чтобы выяснить, какой из этих подходов будет работать лучше. Прежде чем приступить к созданию случайных правил, я подумал, что должен спросить здесь, есть ли лучший подход.

Примечание: Методы добычи данных и ограничения столбцов здесь неосуществимы. Данные должны быть проверены, когда они поступают в систему, и поэтому их можно пометить как пройденные / не пройденные немедленно. И пользователи контролируют добавление или удаление правил, поэтому я не могу преобразовать правила в ограничения столбцов или другие операторы определения данных.

И последнее, в конце мне нужен список всех правил, которые данные не могут пройти. Решение не может быть прервано при первой ошибке.

Спасибо.

Ответы [ 5 ]

1 голос
/ 14 апреля 2009

Первый ваш запрос идеален. Я действительно сомневаюсь, что добавление столбца, о котором вы говорили, даст вам еще большую скорость, поскольку свойство NOT NULL каждой записи проверяется в любом случае, поскольку каждое сравнение с NULL приводит к значению false. Так что я бы предположил, что x=y расширен до x IS NOT NULL AND x=y внутри. Может быть, кто-то еще может уточнить это.

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

0 голосов
/ 15 апреля 2009
SELECT * FROM RULES
 WHERE (A = :a OR A IS NULL)
   AND (B = :b OR B IS NULL)
   AND (C = :c OR C IS NULL);

В зависимости от вашей RBDMS, это может быть или не быть более эффективным, хотя и ненамного:

SELECT * FROM RULES
 WHERE coalesce(A, :a) = :a
   AND coalesce(B, :b) = :b 
   AND coalesce(C, :c) = :c ;

В MySQL (ваша RBDMS может делать это по-другому), этот запрос разрешает сканирование index, а не ref_or_null, если есть соответствующий индекс. Если индекс охватывает все столбцы, он позволяет использовать весь индекс (и действительно, если индекс охватывает все столбцы, индекс равен таблице).

В вашем запросе выполняется доступ ref_or_null, а не index, и используется только первый столбец в многостолбцовом индексе. При ref_or_null MySQL должен искать в индексе совпадения, а затем снова искать нулевые значения. Таким образом, мы используем индекс дважды, но никогда не используем весь индекс.

Но с coalesce у вас есть накладные расходы на выполнение функции coalesce для каждого значения столбца. Что быстрее, вероятно, зависит от того, сколько правил у вас есть, сколько столбцов в каждой строке и индекс, если таковые имеются.

Вопрос о том, является ли он более читабельным, является вопросом мнения.

0 голосов
/ 15 апреля 2009

Звучит так, как будто у вас есть Правила и Наборы Правил. Такое моделирование не только сделает этот конкретный код намного проще, но также сделает модель расширяемой, когда вы решите, что вам нужно 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 может быть более динамичным и не должен увеличиваться при добавлении дополнительных столбцов.

0 голосов
/ 14 апреля 2009

Почему бы не сделать индексы вашей таблицы правил по значениям? Тогда вы можете

SELECT myvalue FROM RULES_A
0 голосов
/ 14 апреля 2009

Слишком много строк / правил? Если это не так (это субъективно, но, скажем, менее 10 000), вы можете создать индексы для всех столбцов.

Это значительно увеличит скорость, и индексы не займут много места.

Если вы не планируете составлять огромную таблицу правил, держу пари, что ваш подход в порядке, если вы проиндексировали все столбцы.

...