Pregunta

Nuestra base de datos de análisis web de MySQL contiene una tabla resumen que se actualiza durante todo el día ha sido importada nueva actividad. Utilizamos EN DUPLICADO KEY UPDATE con el fin de que el resumen sobrescribe los cálculos anteriores, pero teniendo dificultades porque una de las columnas en la tabla de resumen único Key es una FK opcional, y contiene valores NULL.

Estos valores NULL están destinadas a significar "no está presente, y todos estos casos son equivalentes". Por supuesto, MySQL por lo general trata a NULL en el sentido de "desconocido, y todos estos casos no son equivalentes".

La estructura básica es la siguiente:

Una tabla "Actividad" que contiene una entrada para cada sesión, cada perteneciente a una campaña, con IDs de filtro y de transacción opcionales para algunas entradas.

CREATE TABLE `Activity` (
    `session_id` INTEGER AUTO_INCREMENT
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `transaction_id` INTEGER DEFAULT NULL
    , PRIMARY KEY (`session_id`)
);

Una tabla "Resumen" que contiene los paquetes acumulativos diarias de número total de sesiones en la tabla de actividad, una d el número total de esas sesiones que contienen un ID de transacción. Estos resúmenes se separaron, con uno para cada combinación de campaña y filtro (opcional). Esta es una tabla no transaccional utilizando MyISAM.

CREATE TABLE `Summary` (
    `day` DATE NOT NULL
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `sessions` INTEGER UNSIGNED DEFAULT NULL
    , `transactions` INTEGER UNSIGNED DEFAULT NULL
    , UNIQUE KEY (`day`, `campaign_id`, `filter_id`)
) ENGINE=MyISAM;

La consulta el resumen real es algo así como lo siguiente, contando el número de sesiones y transacciones, a continuación, la agrupación por campaña y (opcional) filtro.

INSERT INTO `Summary` 
    (`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`)
    SELECT `day`, `campaign_id`, `filter_id
        , COUNT(`session_id`) AS `sessions`
        , COUNT(`transaction_id` IS NOT NULL) AS `transactions`
    FROM Activity
    GROUP BY `day`, `campaign_id`, `filter_id`
ON DUPLICATE KEY UPDATE
    `sessions` = VALUES(`sessions`)
    , `transactions` = VALUES(`transactions`)
;

Todo funciona muy bien, excepto por el resumen de casos en que el filter_id es NULL. En estos casos, la cláusula FOR UPDATE ON DUPLICATE KEY no coincide con la fila existente, y una nueva fila se escribe cada vez. Esto se debe al hecho de que "NULL! = NULL". Lo que necesitamos, sin embargo, es "NULL = NULL" cuando se comparan las claves únicas.

Estoy buscando ideas para soluciones o comentarios sobre los que hemos llegado con hasta el momento. Soluciones que han pensado en lo que va seguir.

  1. Eliminar todas las entradas de resumen que contienen un valor de clave NULL antes de ejecutar el resumen. (Esto es lo que estamos haciendo ahora) Esto tiene el efecto secundario negativo de devolver resultados con los datos que faltan, si se ejecuta una consulta durante el proceso de síntesis.

  2. Cambiar columna NULL el DEFAULT en DEFAULT 0, lo que permite la clave única, que se ajustará de forma coherente. Esto tiene el efecto secundario negativo de complicar demasiado el desarrollo de consultas en la tabla de resumen. Nos obliga a utilizar una gran cantidad de "CASE filter_id = 0 THEN ELSE NULL FIN filter_id", y lo hace para unirse incómoda ya que todas las otras tablas tienen valores NULL reales para el filter_id.

  3. Crea una vista que devuelve "CASE filter_id = 0 THEN NULL END ELSE filter_id", y el uso de este punto de vista en lugar de la tabla directamente. La tabla de resumen contiene unos pocos cientos de miles de filas, y me han dicho vista del rendimiento es bastante pobre.

  4. Permitir que las entradas duplicadas para crear y borrar las entradas antiguas después de resumen completa. Tiene problemas similares a eliminarlos antes de tiempo.

  5. Añadir una columna sustituta que contiene 0 a NULL, y el uso que sustituta de la clave exclusiva (en realidad podríamos utilizar PRIMARY KEY si todas las columnas no son nulos).
    Esta solución parece razonable, excepto que el ejemplo anterior es sólo un ejemplo; la base de datos actual contiene tablas de resumen de media docena, uno de los cuales contiene cuatro columnas con valores nulos de la clave exclusiva. Existe la preocupación de algunos de que la sobrecarga es demasiado.

¿Tiene una mejor solución, estructura de la tabla, el proceso de actualización o MySQL mejores prácticas que pueden ayudar?

EDIT: Para aclarar el "significado de null"

Los datos de las filas de resumen que contengan columnas NULL se considera que pertenecen juntos sólo en el sentido de que de ser una sola fila "catch-all" en los informes de resumen, que resume los elementos para los que no existe ese punto de datos o se desconoce . Así, en el contexto de la propia tabla de resumen, el significado es "la suma de las entradas para los cuales no se conoce el valor". Dentro de las tablas relacionales, por el contrario, THESE realmente son nulos resultados.

La única razón de su puesta en una clave única de la tabla de resumen es permitir la actualización automática (por DUPLICADO EN KEY UPDATE) al volver a calcular los informes de resumen.

Tal vez una mejor manera de describirlo es por el ejemplo específico que uno de los grupos de tablas de resumen de resultados geográficamente por el prefijo código postal de la dirección de la empresa propuesta por el demandado. No todos los encuestados proporcionan una dirección comercial, por lo que la relación entre la tabla de transacciones y direcciones es bastante correctamente NULL. En la tabla resumen de estos datos, se genera una fila para cada prefijo de código zip, que contiene el resumen de los datos dentro de esa área. Se genera una fila adicional para mostrar el resumen de los datos para los que se conoce ningún prefijo código postal.

La alteración del resto de las tablas de datos que tienen una "THERE_IS_NO_ZIP_CODE" 0-valor explícito, y la colocación de un registro especial en la mesa ZipCodePrefix que representa este valor, es impropio -. Esa relación realmente es NULL

¿Fue útil?

Solución

Creo que algo en la línea de (2) es realmente la mejor apuesta - o, al menos, lo que sería si estuviera empezando desde cero. En SQL, NULL significa desconocido. Si desea algún otro significado, que realmente debería usar un valor especial para eso, y 0 es sin duda una buena elección.

Usted debe hacer esto a través de la toda base de datos, no sólo por esta tabla. Entonces no debe terminar con los casos especiales extraños. De hecho, usted debería ser capaz de deshacerse de una gran cantidad de sus seres actual (ejemplo: en la actualidad, si desea que la fila de resumen donde no hay un filtro, que tiene el caso especial de "filtro es nulo", en contraposición a los casos normales "filter =?".)

También debería seguir adelante y crear una entrada de "no presente" en lo referido a la mesa, así, mantener la restricción FK válida (y evitar casos especiales).

PS:. Tablas w / o una clave principal no son tablas relacionales y realmente se debe evitar

Editar 1

Hmmm, en ese caso, es lo que realmente necesita la actualización de clave duplicada? Si estás haciendo un INSERT ... SELECT, entonces es probable que hacer. Pero si su aplicación es el suministro de los datos, sólo lo hacen con la mano -. Hacer la actualización (zip = null mapeo a zip is null), comprobar cuántas filas se han cambiado (MySQL devuelve este), si es 0 hacer una inserción

Otros consejos

  

Cambiar columna NULL el DEFAULT en DEFAULT 0, lo que permite la clave única, que se ajustará de forma coherente. Esto tiene el efecto secundario negativo de complicar demasiado el desarrollo de consultas en la tabla de resumen. Nos obliga a utilizar una gran cantidad de "CASE filter_id = 0 THEN ELSE NULL FIN filter_id", y lo hace para unirse incómoda ya que todas las otras tablas tienen valores NULL reales para el filter_id.

     

Crea una vista que devuelve "CASE filter_id = 0 THEN NULL END ELSE filter_id", y el uso de este punto de vista en lugar de la tabla directamente. La tabla de resumen contiene unos pocos cientos de miles de filas, y me han dicho vista del rendimiento es bastante pobre.

Ver el rendimiento de MySQL 5.x va a estar bien, ya que la vista no hace más que reemplazar un cero con un nulo. A menos que utilice agregados / tipo en una vista, la mayoría de cualquier consulta en la vista será re-escrito por el optimizador de consultas acaba de golpear la tabla subyacente.

Y, por supuesto, ya que es una FK, usted tiene que crear una entrada en la que se refiere a la mesa con un id de cero.

Con las versiones modernas de MariaDB (antes de MySQL), upserts se puede hacer simplemente con inserción de instrucciones de actualización de clave duplicados si vas con la columna sustituta ruta # 5. Adición de columnas almacenados generados de MySQL o columnas virtuales persistentes MariaDB aplicar la restricción de unicidad en los campos anulables indirectamente mantiene los datos sin sentido fuera de la base de datos a cambio de alguna hinchazón.

por ejemplo.

CREATE TABLE IF NOT EXISTS bar (
    id INT PRIMARY KEY AUTO_INCREMENT,
    datebin DATE NOT NULL,
    baz1_id INT DEFAULT NULL,
    vbaz1_id INT AS (COALESCE(baz1_id, -1)) STORED,
    baz2_id INT DEFAULT NULL,
    vbaz2_id INT AS (COALESCE(baz2_id, -1)) STORED,
    blam DOUBLE NOT NULL,
    UNIQUE(datebin, vbaz1_id, vbaz2_id)
);

INSERT INTO bar (datebin, baz1_id, baz2_id, blam)
    VALUES ('2016-06-01', null, null, 777)
ON DUPLICATE KEY UPDATE
    blam = VALUES(blam);

Para MariaDB replace almacenado con PERSISTENTE, índices requieren persistencia.

MySQL columnas generadas MariaDB Columnas virtuales

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