Existe-t-il un bon moyen de vérifier les règles par rapport aux n colonnes?

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

  •  09-09-2019
  •  | 
  •  

Question

Supposons que vous ayez des règles de table avec 3 colonnes A, B et C. À mesure que les données entrent dans le système, je veux savoir si une ligne du tableau des règles correspond à mes données avec la condition que si la colonne correspondante dans le tableau des règles est nul , toutes les données correspondent. Le SQL évident est:

SELECT * FROM RULES
WHERE (A = :a OR A IS NULL)
  AND (B = :b OR B IS NULL)
  AND (C = :c OR C IS NULL)

Donc, si j'ai des règles:

RULE    A        B        C
1       50       NULL     NULL
2       51       xyz      NULL
3       51       NULL     123
4       NULL     xyz      456

Une entrée de (50, XYZ, 456) correspondra aux règles 1 et 4.

Question: Y a-t-il une meilleure manière de faire cela? Avec seulement 3 champs, ce n'est pas un problème. Mais le tableau réel aura 15 colonnes et je m'inquiète de la façon dont ces échelles SQL.

Spéculation: Une autre instruction SQL que j'ai inventée impliquée ajoutant une colonne supplémentaire à la table avec un compte du nombre de champs ne sont pas nuls. (Ainsi, dans l'exemple, cette valeur de colonnes pour les règles 1 à 4 est respectivement 1, 2, 2 et 2.) Avec cette colonne "col_count", la sélection pourrait être:

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

Malheureusement, je n'ai pas suffisamment d'échantillons de données pour trouver notre autre approche. Avant de commencer à créer des règles aléatoires, je pensais que je demanderais ici s'il y avait une meilleure approche.

Noter: Les techniques d'exploration de données et les contraintes de colonne ne sont pas possibles ici. Les données doivent être vérifiées lorsqu'elles entrent dans le système et peuvent donc être signalées de passage / échouer immédiatement. Et, les utilisateurs contrôlent l'addition ou la suppression des règles afin que je ne puisse pas convertir les règles en contraintes de colonne ou autres instructions de définition de données.

Une dernière chose, à la fin, j'ai besoin d'une liste de toutes les règles que les données ne réussissent pas. La solution ne peut pas interrompre à la première défaillance.

Merci.

Était-ce utile?

La solution

La première requête que vous avez fournie est parfaite. Je doute vraiment que l'ajout de la colonne dont vous parliez vous donnerait plus de vitesse, car la propriété non nul de chaque entrée est de toute façon vérifiée, car chaque comparaison avec Null donne faux. Alors je suppose que x=y est étendu à x IS NOT NULL AND x=y intérieurement. Peut-être que quelqu'un d'autre peut clarifier cela.

Toutes les autres optimisations auxquelles je peux penser impliqueraient une précalcul ou une mise en cache. Vous pouvez créer des tables [temporaires] correspondant à certaines règles ou ajouter d'autres colonnes contenant des règles de correspondance.

Autres conseils

Y a-t-il trop de lignes / règles? Si ce n'est pas le cas (c'est subjectif, mais dire moins de 10 000), vous pouvez créer des index pour toutes les colonnes.

Cela augmenterait considérablement la vitesse et les index ne prendront pas beaucoup de place.

Si vous ne prévoyez pas de faire une énorme table de règles, je parie que votre approche est ok à condition d'indexer toutes les colonnes.

Pourquoi ne pas faire des indices de votre table de règles par les valeurs? Ensuite vous pouvez

SELECT myvalue FROM RULES_A

Il semble que ce que vous avez vraiment, ce sont des règles et des ensembles de règles. Le modéliser de cette façon rendra non seulement ce codage particulier beaucoup plus simple, mais rendra également le modèle extensible lorsque vous décidez que vous avez besoin de 16 colonnes.

Par exemple:

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)
)

Certaines données correspondraient à vos règles données:

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)

Un script de test qui confirme la même réponse que vous attendez:

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)

Si vous pouvez présenter les données d'entrée sous une forme basée sur un ensemble plutôt que comme des paramètres individuels, l'instruction SQL finale peut être plus dynamique et n'aurait pas à croître à mesure que vous ajoutez des colonnes supplémentaires.

SELECT * FROM RULES
 WHERE (A = :a OR A IS NULL)
   AND (B = :b OR B IS NULL)
   AND (C = :c OR C IS NULL);

Selon vos RBDM, cela peut ou non être plus efficace, mais pas beaucoup:

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

Dans MySQL (vos RBDM peuvent le faire différemment), cette requête permet un index scan plutôt qu'un ref_or_null Scan, s'il existe un index applicable. Si l'index couvre toutes les colonnes, il permet à l'index entier d'être utilisé (et en effet, si l'index couvre toutes les colonnes, l'index est la table).

Avec votre requête, un ref_or_null l'accès est effectué plutôt qu'un index Accès, et seule la première colonne d'un index multi-colonne est utilisée. Avec ref_or_null, MySQL doit rechercher l'index pour les correspondances, puis rechercher à nouveau Nulls. Nous utilisons donc deux fois l'index, mais n'utilisons jamais l'index entier.

Mais avec Coalesce, vous avez les frais généraux d'exécuter la fonction de coalesce sur chaque valeur de colonne. Ce qui est plus rapide dépend probablement du nombre de règles que vous avez, du nombre de colonnes dans chaque ligne et de l'index utilisé, le cas échéant.

Que ce soit plus lisible est une question d'opinion.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top