Pregunta

Suponga que tiene reglas de tabla con 3 columnas A, B y C. A medida que los datos ingresan al sistema, quiero saber si alguna fila de la tabla de reglas coincide con mis datos con la condición de que si la columna correspondiente en la tabla de reglas es nula , todas las coincidencias de datos. El obvio SQL es:

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

Entonces, si tengo reglas:

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

Una entrada de (50, xyz, 456) coincidirá con las reglas 1 y 4.

Pregunta: ¿Hay una mejor manera de hacer esto? Con solo 3 campos, esto no es un problema. Pero la tabla real tendrá 15 columnas y me preocupa qué tan bien se escala SQL.

Especulación: Una declaración alternativa de SQL que se me ocurrió involucrada agregar una columna adicional a la tabla con un recuento de cuántos campos no son nulos. (Entonces, en el ejemplo, el valor de esta columna para las reglas 1-4 es 1, 2, 2 y 2 respectivamente). Con esta columna "col_count", la selección podría ser:

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

Desafortunadamente, no tengo suficientes datos de muestra para encontrar nuestros enfoques que funcionarían mejor. Antes de comenzar a crear reglas aleatorias, pensé en preguntar aquí si había un mejor enfoque.

Nota: Las técnicas de minería de datos y las restricciones de columna no son factibles aquí. Los datos deben verificarse cuando ingrese al sistema y, por lo tanto, se puede marcar el paso/falla de inmediato. Y, los usuarios controlan la adición o eliminación de reglas para que no pueda convertir las reglas en restricciones de columna u otras declaraciones de definición de datos.

Una última cosa, al final, necesito una lista de todas las reglas que los datos no pasan. La solución no puede abortar en la primera falla.

Gracias.

¿Fue útil?

Solución

La primera consulta que proporcionó es perfecta. Realmente dudo que agregar la columna de la que estabas hablando te daría más velocidad, ya que la propiedad no nula de cada entrada se verifica de todos modos, ya que cada comparación con NULL produce falso. Entonces supongo que x=y se expande a x IS NOT NULL AND x=y internamente. Quizás alguien más pueda aclarar eso.

Todas las demás optimizaciones en las que puedo pensar implicarían precalculación o almacenamiento en caché. Puede crear tablas [temporales] que coincidan con ciertas reglas o agregar más columnas que contienen reglas coincidentes.

Otros consejos

¿Hay demasiadas filas/reglas? Si no es el caso (eso es subjetivo, pero digamos menos de 10,000), podría crear índices para todas las columnas.

Eso aumentaría significativamente la velocidad y los índices no tomarán mucho espacio.

Si no planea hacer una gran tabla de reglas, apuesto a que su enfoque está bien siempre que indexe todas las columnas.

¿Por qué no hacer índices de su tabla de reglas por los valores? Entonces tú puedes

SELECT myvalue FROM RULES_A

Parece que lo que realmente tienes son reglas y conjuntos de reglas. Modelarlo de esa manera no solo hará que esta codificación en particular sea mucho más simple, sino que también hará que el modelo se pueda ampliar cuando decida que necesita un 16 columnas.

Por ejemplo:

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

Algunos datos que coincidirían con sus reglas dadas:

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 prueba que confirma la misma respuesta que espera:

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 puede presentar los datos de entrada en una forma basada en establecimientos en lugar de como parámetros individuales, la instrucción SQL final puede ser más dinámica y no tendría que crecer a medida que agrega columnas adicionales.

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

Dependiendo de sus RBDM, esto podría o no ser más eficiente, aunque no por mucho:

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

En MySQL (su RBDMS puede hacer esto de manera diferente), esta consulta permite una index escanear en lugar de un ref_or_null escanear, si hay un índice aplicable. Si el índice cubre todas las columnas, permite que se use todo el índice (y de hecho, si el índice cubre todas las columnas, el índice es la mesa).

Con tu consulta, un ref_or_null El acceso se realiza en lugar de un index Acceso, y solo se utiliza la primera columna en un índice de múltiples columnas. Con ref_or_null, MySQL tiene que buscar partidos en el índice, luego buscar nuevamente nulos. Entonces usamos el índice dos veces, pero nunca usamos el índice completo.

Pero con fusele, tiene la sobrecarga de ejecutar la función de fuselón en cada valor de columna. Lo cual es más rápido probablemente depende de cuántas reglas tenga, cuántas columnas en cada fila y el índice utilizado, si las hay.

Si es más legible es una cuestión de opinión.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top