Domanda

Il nostro database di analisi web MySQL contiene una tabella riassuntiva che viene aggiornato durante il giorno come nuova attività viene importata. Usiamo ON DUPLICATE KEY UPDATE affinché il riepilogo sovrascrive i calcoli precedenti, ma avendo difficoltà perché una delle colonne chiave univoca della tabella riassuntiva è un FK facoltativa, e contiene i valori NULL.

Queste NULL intendono significare "non è presente, e tutti questi casi sono equivalenti". Naturalmente, MySQL di solito tratta NULL nel senso "sconosciuto, e tutti questi casi non sono equivalenti".

Struttura di base è la seguente:

Un "attività" tabella contenente una voce per ogni sessione, ogni appartenente ad una campagna, con gli ID di filtro e di transazione opzionali per alcune voci.

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

A "Sommario" tabella contenente rollup quotidiani di numero totale di sessioni in tabella di attività, un d il numero totale di quelle sessioni che contengono un ID di transazione. Queste sintesi sono suddivisi, con uno per ogni combinazione di campagna e di filtro (opzionale). Si tratta di un tavolo non transazionale utilizzando 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 query reale riepilogo è qualcosa di simile alla seguente, contando il numero di sessioni e transazioni, poi il raggruppamento da campagna e (opzionale) 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`)
;

Tutto grandi opere, tranne che per la sintesi dei casi in cui la filter_id è NULL. In questi casi, la clausola KEY UPDATE ON DUPLICATE non corrisponde alla riga esistente, e una nuova riga viene scritta ogni volta. Ciò è dovuto al fatto che "NULL! = NULL". Quello che ci serve, tuttavia, è "NULL = NULL" quando si confrontano le chiavi univoche.

Sto cercando idee per soluzioni alternative o suggerimenti relativi a quelle che abbiamo messo a punto finora. Soluzioni alternative ci hanno pensato finora seguire.

  1. Elimina tutte le voci di sintesi contenenti un valore NULL chiave prima di eseguire il riepilogo. (Questo è quello che stiamo facendo ora) Questo ha l'effetto collaterale negativo del ritorno risultati con i dati mancanti se una query viene eseguita durante il processo di riepilogo.

  2. Modificare la colonna NULL DEFAULT per default 0, che permette la chiave univoca da abbinare in modo coerente. Questo ha l'effetto collaterale negativo eccessivamente complicare lo sviluppo di query sul tabella riassuntiva. Ci costringe a usare un sacco di "CASE filter_id = 0 THEN NULL END ELSE filter_id", e fa per unire imbarazzante dato che tutti gli altri tavoli hanno NULL effettivi per il filter_id.

  3. Creare una vista che restituisce "CASE filter_id = 0 THEN NULL END ELSE filter_id", e l'utilizzo di questo punto di vista, invece della tabella direttamente. La tabella riassuntiva contiene poche centinaia di migliaia di file, e mi è stato detto vista delle prestazioni è piuttosto scarsa.

  4. Lasciare le voci duplicate da creare e cancellare le vecchie voci dopo riepilogo completa. Ha problemi simili a eliminarle prima del tempo.

  5. Aggiungere una colonna surrogata che contiene 0 per NULL, e l'uso che surrogata nella chiave univoca (in realtà potremmo usare PRIMARY KEY se tutte le colonne sono NOT NULL).
    Questa soluzione sembra ragionevole, eccetto che l'esempio di cui sopra è solo un esempio; il database effettivo contiene tabelle di sintesi una mezza dozzina, uno dei quali contiene quattro colonne nullable nella chiave univoca. C'è preoccupazione da parte di alcuni che il sovraccarico è troppo.

Hai una soluzione migliore, la struttura delle tabelle, processo di aggiornamento o MySQL buone pratiche che possono aiutare?

EDIT: Per chiarire il "significato di null"

I dati nelle righe di riepilogo contenenti colonne NULL sono considerati appartenenti insieme solo nel senso quella di essere una singola riga "catch-all" in rapporti di sintesi, che riassume gli elementi per i quali tale punto dati non esiste o è sconosciuto . Così nel contesto della tabella di riepilogo in sé, il significato è "la somma di quelle voci di cui si conosce alcun valore". All'interno delle tabelle relazionali, d'altra parte, these veramente sono risultati nulli.

L'unica ragione della loro messa in una chiave univoca sulla tabella riassuntiva è quello di consentire l'aggiornamento automatico (da ON DUPLICATE KEY UPDATE) quando ri-calcolo dei report di riepilogo.

Forse un modo migliore per descriverlo è l'esempio specifico che uno dei gruppi di tabelle di sintesi dei risultati geograficamente da parte del codice di avviamento postale prefisso dell'indirizzo commerciale proposta dal convenuto. Non tutti gli intervistati forniscono un indirizzo commerciale, in modo che il rapporto tra la tabella di transazione e indirizzi è giustamente NULL. Nella tabella riassuntiva per questi dati, una riga viene generato per ogni prefisso CAP, contenente la sintesi dei dati all'interno di tale zona. Una riga aggiuntiva viene generato per mostrare il riepilogo dei dati di cui si conosce alcun prefisso codice postale.

Alterare il resto delle tabelle di dati per avere un "THERE_IS_NO_ZIP_CODE" 0-valore esplicito, e mettendo un record speciale nella tabella ZipCodePrefix che rappresenta questo valore, è improprio -. Quel rapporto è veramente NULL

È stato utile?

Soluzione

Credo che qualcosa sulla falsariga di (2) è davvero la scommessa migliore - o, almeno, sarebbe se si dovesse partire da zero. In SQL, NULL significa sconosciuto. Se volete qualche altro significato, si dovrebbe davvero utilizzare un valore speciale per questo, e 0 è certamente una scelta OK.

Si dovrebbe fare questo in tutto il intero di database, non solo questo tavolo. Allora non si dovrebbe concludere con casi particolari strani. In realtà, si dovrebbe essere in grado di sbarazzarsi di un sacco di vostri attuali (ad esempio: attualmente, se si desidera che la riga di riepilogo in cui non v'è alcun filtro, si ha il caso particolare "filtro è nullo" in contrasto con il caso normale "Filtro =?").

Si dovrebbe anche andare avanti e creare una voce "non presente" nel cui da tavolo pure, per mantenere il vincolo FK valida (ed evitare casi particolari).

PS:. Tabelle w / o una chiave primaria non sono tabelle relazionali e in realtà dovrebbe essere evitati

Modifica 1

Hmmm, in questo caso, non è effettivamente necessario l'aggiornata chiave duplicata? Se stai facendo un INSERT ... SELECT, allora probabilmente fare. Ma se la vostra applicazione è fornire i dati, basta farlo a mano -. Fare l'aggiornamento (mappatura zip = null a zip is null), controllare il numero di righe sono state modificate (MySQL restituisce questo), se 0 fare un inserto

Altri suggerimenti

  

Modificare la colonna NULL DEFAULT per default 0, che permette la chiave univoca da abbinare in modo coerente. Questo ha l'effetto collaterale negativo eccessivamente complicare lo sviluppo di query sul tabella riassuntiva. Ci costringe a usare un sacco di "CASE filter_id = 0 THEN NULL END ELSE filter_id", e fa per unire imbarazzante dato che tutti gli altri tavoli hanno NULL effettivi per il filter_id.

     

Creare una vista che restituisce "CASE filter_id = 0 THEN NULL END ELSE filter_id", e l'utilizzo di questo punto di vista, invece della tabella direttamente. La tabella riassuntiva contiene poche centinaia di migliaia di file, e mi è stato detto vista delle prestazioni è piuttosto scarsa.

Visualizza le prestazioni in MySQL 5.x andrà bene, come la vista non fa altro che sostituire uno zero con un nullo. Se non si utilizza aggregati / tipi in una vista, la maggior parte qualsiasi query sulla vista sarà riscritta da Query Optimizer per colpire solo la tabella sottostante.

E, naturalmente, dal momento che è un FK, si dovrà creare una voce nel cui-a tavola con un id pari a zero.

Con le versioni moderne di MariaDB (ex MySQL), upserts può essere fatto semplicemente con inserto sulle istruzioni di aggiornamento di chiave duplicati se si va con colonna surrogata percorso # 5. L'aggiunta di colonne memorizzate generati di MySQL o MariaDB colonne virtuali persistenti per applicare il vincolo di unicità sui campi Null mantiene indirettamente i dati senza senso fuori dalla base di dati in cambio di qualche gonfiare.

per es.

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

Per MariaDB replace memorizzati con PERSISTENTE, indici richiedono la persistenza.

MySQL colonne generate MariaDB colonne virtuali

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