Domanda

Supponiamo di avere una tabella Regole con 3 colonne A, B e C. Mentre i dati entrano nel sistema, voglio sapere se una riga della tabella delle regole corrisponde ai miei dati con la condizione che se la colonna corrispondente nella tabella delle regole è nullo , tutti i dati corrispondono. L'ovvio 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)

Quindi se ho regole:

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

Un input di (50, XYZ, 456) corrisponderà alle regole 1 e 4.

Domanda: C'è un modo migliore per farlo? Con solo 3 campi questo non è un problema. Ma la tabella effettiva avrà 15 colonne e mi preoccupo di quanto bene si ridimensiona.

Speculazione: Un'istruzione SQL alternativa che mi è venuta in mente di aggiungere una colonna extra al tavolo con un conteggio di quanti campi non sono nulli. (Quindi, nell'esempio, questo valore delle colonne per le regole 1-4 è rispettivamente 1, 2, 2 e 2.) Con questa colonna "Col_Count", la selezione potrebbe essere:

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

Sfortunatamente, non ho abbastanza dati di esempio per trovare il nostro di questi approcci funzionerebbe meglio. Prima di iniziare a creare regole casuali, ho pensato di chiedere qui se ci fosse un approccio migliore.

Nota: Qui non sono fattibili tecniche di data mining e vincoli di colonne. I dati devono essere verificati in quanto entra nel sistema e quindi possono essere contrassegnati immediatamente. Inoltre, gli utenti controllano l'aggiunta o la rimozione delle regole in modo da non poter convertire le regole in vincoli di colonne o altre dichiarazioni di definizione dei dati.

Un'ultima cosa, alla fine ho bisogno di un elenco di tutte le regole che i dati non riescono a passare. La soluzione non può interrompere al primo fallimento.

Grazie.

È stato utile?

Soluzione

La prima domanda fornita è perfetta. Dubito davvero che l'aggiunta della colonna di cui stavi parlando ti darebbe più velocità, dal momento che la proprietà non null di ogni voce è comunque selezionata, poiché ogni confronto con NULL produce falso. Quindi immagino che questo x=y è ampliato a x IS NOT NULL AND x=y internamente. Forse qualcun altro può chiarirlo.

Tutte le altre ottimizzazioni a cui riesco a pensare implicherebbero la precalcolazione o la memorizzazione nella cache. È possibile creare tabelle [temporanee] che corrispondono a determinate regole o aggiungere ulteriori colonne con regole di corrispondenza.

Altri suggerimenti

Ci sono troppe righe/regole? Se non è il caso (è soggettivo, ma dire meno di 10.000), è possibile creare indici per tutte le colonne.

Ciò aumenterebbe in modo significativo la velocità e gli indici non impiegheranno molto spazio.

Se non hai intenzione di creare un'enorme tabella di regole, scommetto che il tuo approccio è OK a condizione che indicizzi tutte le colonne.

Perché non fare gli indici della tua tabella delle regole secondo i valori? Allora puoi

SELECT myvalue FROM RULES_A

Sembra che quello che hai veramente sono regole e set di regole. Modellare in questo modo non solo renderà questo particolare codifica molto più semplice, ma renderà anche il modello espandibile quando decidi di aver bisogno di 16 colonne.

Per esempio:

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

Alcuni dati che corrisponderebbero alle regole fornite:

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)

Uno script di prova che conferma la stessa risposta che ti aspetti:

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)

Se è possibile presentare i dati di input in un modulo basato su set piuttosto che come singoli parametri, l'istruzione SQL finale può essere più dinamica e non dovrebbe crescere man mano che si aggiungono colonne aggiuntive.

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

A seconda del tuo RBDMS, questo potrebbe o non potrebbe essere più efficiente, anche se non di molto:

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

In MySQL (il tuo RBDMS può farlo diversamente), questa query consente un index scansione piuttosto che a ref_or_null Scansione, se esiste un indice applicabile. Se l'indice copre tutte le colonne, consente di utilizzare l'intero indice (e in effetti, se l'indice copre tutte le colonne, l'indice è la tavola).

Con la tua domanda, a ref_or_null L'accesso è fatto piuttosto che un index Accesso e viene utilizzata solo la prima colonna in un indice multi-colonna. Insieme a ref_or_null, MySQL deve cercare l'indice per le corrispondenze, quindi cercare di nuovo per nulls. Quindi usiamo l'indice due volte, ma non usiamo mai l'intero indice.

Ma con Coalesce, hai il sovraccarico di eseguire la funzione Coalesce su ciascun valore della colonna. Il che è più veloce probabilmente dipende da quante regole hai, da quante colonne in ogni riga e dall'indice utilizzato, se presente.

Se è più leggibile è una questione di opinione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top