MySQL auf doppelter Schlüsselaktualisierung mit nullierbarer Spalte in eindeutiger Schlüssel

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

  •  18-09-2019
  •  | 
  •  

Frage

Unsere MySQL Web Analytics -Datenbank enthält eine Zusammenfassungstabelle, die den ganzen Tag über aktualisiert wird, wenn neue Aktivitäten importiert werden. Wir verwenden auf doppelter Schlüsselaktualisierung, damit die Zusammenfassung frühere Berechnungen überschreibt, aber Schwierigkeiten haben, da eine der Spalten in der eindeutigen Taste der Zusammenfassung Tabelle ein optionales FK ist und Nullwerte enthält.

Diese Nulls sollen "nicht vorhanden sind und alle diese Fälle äquivalent". Natürlich behandelt MySQL Nulls normalerweise als "unbekannt, und alle diese Fälle sind nicht gleichwertig".

Die Grundstruktur ist wie folgt:

Eine "Aktivität" -Tabelle mit einem Eintrag für jede Sitzung, die jeweils zu einer Kampagne gehört, mit optionalen Filter- und Transaktions -IDs für einige Einträge.

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

Eine "Zusammenfassungs" -Tabelle mit täglichen Rollups mit Gesamtzahl der Sitzungen in der Aktivitätstabelle und der Gesamtzahl der Sitzungen, die eine Transaktions -ID enthalten. Diese Zusammenfassungen werden mit einer für jede Kombination aus Kampagnen- und (optionalem) Filter aufgeteilt. Dies ist eine nicht-transaktionale Tabelle mit 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;

Die tatsächliche Zusammenfassung der Summarisierungsabfrage ist so etwas wie die folgende, die Anzahl der Sitzungen und Transaktionen, dann nach Kampagnen- und (optionaler) Filter gruppiert.

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

Alles funktioniert hervorragend, bis auf die Zusammenfassung der Fälle, in denen der Filter_ID null ist. In diesen Fällen stimmt die On -Doppelklausel nicht mit der vorhandenen Zeile überein, und jedes Mal wird eine neue Zeile geschrieben. Dies liegt an der Tatsache, dass "null! = Null". Was wir jedoch brauchen, ist "null = null" beim Vergleich der einzigartigen Schlüssel.

Ich suche Ideen für Problemumgehungen oder Feedback zu denen, die wir bisher ausgedacht haben. Problemumgehungen, an die wir bisher nachgedacht haben, folgen.

  1. Löschen Sie alle Zusammenfassungseinträge, die vor dem Ausführen der Zusammenfassung einen Nullschlüsselwert enthalten. (Dies ist das, was wir jetzt tun) Dies hat den negativen Nebeneffekt der Rückgabe von Ergebnissen mit fehlenden Daten, wenn während des Zusammenfassungsprozesses eine Abfrage ausgeführt wird.

  2. Ändern Sie die Standard -Null -Spalte in Standard 0, wodurch der eindeutige Schlüssel konsequent übereinstimmt. Dies hat den negativen Nebeneffekt, die Entwicklung von Abfragen gegen die Zusammenfassung Tabelle zu komplizieren. Es zwingt uns, eine Menge "Fall filter_id = 0, dann null sonst filter_id End" zu verwenden, und sorgt für unangenehme Verbindungen, da alle anderen Tabellen tatsächliche Nulls für die Filter_ID haben.

  3. Erstellen Sie eine Ansicht, die zurückgibt "Fall filter_id = 0, dann null sonst filter_id End" und diese Ansicht anstelle der Tabelle direkt verwendet. Die zusammenfassende Tabelle enthält ein paar hunderttausend Zeilen, und mir wurde gesagt, dass View Performance ziemlich schlecht ist.

  4. Lassen Sie die doppelten Einträge erstellen und löschen Sie die alten Einträge nach Abschluss der Zusammenfassung. Hat ähnliche Probleme beim Löschen im Voraus.

  5. Fügen Sie eine Ersatzspalte hinzu, die 0 für NULL enthält, und verwenden Sie diesen Ersatz im eindeutigen Schlüssel (tatsächlich können wir den Primärschlüssel verwenden, wenn alle Spalten nicht null sind).
    Diese Lösung erscheint vernünftig, außer dass das obige Beispiel nur ein Beispiel ist; Die tatsächliche Datenbank enthält ein halbes Dutzend Zusammenfassungstabellen, von denen eine vier nullbare Spalten im eindeutigen Schlüssel enthält. Einige sind besorgt, dass der Overhead zu viel ist.

Haben Sie eine bessere Problemumgehung, Tabellenstruktur, Aktualisierungsprozess oder MySQL -Best Practice, die helfen kann?

Bearbeiten: Um die "Bedeutung von Null" zu verdeutlichen

Die Daten in den zusammenfassenden Zeilen, die Null-Spalten enthalten, gelten nur in dem Sinne, dass es in zusammenfassenden Berichten eine einzige "Catch-All" -Reile ist und die Elemente zusammenfasst, für die dieser Datenpunkt nicht existiert oder nicht bekannt ist. Im Kontext der Zusammenfassungstabelle selbst lautet die Bedeutung "die Summe der Einträge, für die kein Wert bekannt ist". In den relationalen Tabellen hingegen sind diese wirklich Nullergebnisse.

Der einzige Grund, sie in einen eindeutigen Schlüssel in der Zusammenfassungstabelle zu bringen, besteht darin, eine automatische Aktualisierung (nach dem doppelten Schlüsselaktualisierung) beim erneuten Abbau der Zusammenfassungsberichte zu ermöglichen.

Eine bessere Möglichkeit, es besser zu beschreiben, ist das spezifische Beispiel, dass eine der zusammenfassenden Tabellengruppen geografisch durch das vom Befragten angegebene Postleitzahl -Präfix der Geschäftsadresse zu Ergebnissen führen. Nicht alle Befragten geben eine Geschäftsadresse an, daher ist die Beziehung zwischen der Transaktion und der Adresstabelle ganz richtig null. In der zusammenfassenden Tabelle für diese Daten wird für jedes Zip -Code -Präfix eine Zeile generiert, die die Zusammenfassung der Daten in diesem Bereich enthält. Eine zusätzliche Zeile wird generiert, um die Zusammenfassung der Daten anzuzeigen, für die kein Präfix für Postleitzahl bekannt ist.

Die Änderung der restlichen Datentabellen auf einen expliziten "there_is_no_zip_code" 0-Wert und die Aufstellung eines speziellen Datensatzes in der ZipCodeprefix-Tabelle, die diesen Wert darstellt, ist unangemessen-die Beziehung ist wirklich null.

War es hilfreich?

Lösung

Ich denke, etwas im Sinne von (2) ist wirklich die beste Wette - oder zumindest wäre es, wenn Sie von vorne anfangen würden. In SQL bedeutet Null unbekannt. Wenn Sie eine andere Bedeutung haben möchten, sollten Sie wirklich einen besonderen Wert dafür verwenden, und 0 ist sicherlich eine OK -Wahl.

Sie sollten dies über die über die gesamte Datenbank, nicht nur diese eine Tabelle. Dann sollten Sie nicht mit seltsamen Sonderfällen enden. In der Tat sollten Sie in der Lage sein, viele Ihrer aktuellen zu entfernen (Beispiel: Wenn Sie die Zusammenfassungszeile, in der es keinen Filter gibt, möchten Sie den Sonderfall "Filter is null" im Gegensatz zum normalen Fall "filter =?".)

Sie sollten auch einen "nicht vorhandenen" Eintrag in der genannten Tabelle erstellen, um die FK-Einschränkung gültig zu halten (und Sonderfälle zu vermeiden).

PS: Tabellen mit OA -Primärschlüssel sind keine relationalen Tabellen und sollten wirklich vermieden werden.

bearbeiten 1

Hmmm benötigen Sie in diesem Fall tatsächlich das doppelte Schlüssel Update? Wenn Sie einen Einsatz machen ... auswählen, dann tun Sie es wahrscheinlich. Wenn Ihre App jedoch die Daten liefert, tun Sie sie einfach von Hand - machen Sie das Update (Mapping zip = null zu zip is null), prüfen Sie, wie viele Zeilen geändert wurden (MySQL gibt dies zurück), wenn 0 ein Einfügen durchführen.

Andere Tipps

Ändern Sie die Standard -Null -Spalte in Standard 0, wodurch der eindeutige Schlüssel konsequent übereinstimmt. Dies hat den negativen Nebeneffekt, die Entwicklung von Abfragen gegen die Zusammenfassung Tabelle zu komplizieren. Es zwingt uns, eine Menge "Fall filter_id = 0, dann null sonst filter_id End" zu verwenden, und sorgt für unangenehme Verbindungen, da alle anderen Tabellen tatsächliche Nulls für die Filter_ID haben.

Erstellen Sie eine Ansicht, die zurückgibt "Fall filter_id = 0, dann null sonst filter_id End" und diese Ansicht anstelle der Tabelle direkt verwendet. Die zusammenfassende Tabelle enthält ein paar hunderttausend Zeilen, und mir wurde gesagt, dass View Performance ziemlich schlecht ist.

Die Leistung in MySQL 5.x ist in Ordnung, da die Ansicht nur eine Null durch ein Null ersetzt. Sofern Sie keine Aggregate/Sorten in einer Ansicht verwenden, werden die meisten Abfragen gegen die Ansicht vom Abfrageoptimierer erneut geschrieben, um nur die zugrunde liegende Tabelle zu treffen.

Und natürlich, da es sich um eine FK handelt, müssen Sie einen Eintrag in der genannten Tabelle mit einer ID von Null erstellen.

Mit modernen Versionen von Mariadb (ehemals MySQL) können Upserts einfach mit Einfügen auf doppelte Schlüsselaktualisierungsanweisungen erfolgen, wenn Sie sich für die Ersatzspaltenroute Nr. 5 entscheiden. Das Hinzufügen von MySQL -erzeugten gespeicherten Spalten oder Mariadb -persistenten virtuellen Spalten, um die Einzigartigkeitsbeschränkung auf den nullbaren Feldern indirekt anzutragen, hält Unsinnsdaten im Austausch gegen einige Aufblähten aus der Datenbank heraus.

z.B

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

Für Mariadb ersetzen Sie gespeichert durch persistent, die Indizes erfordern Ausdauer.

MySQL erzeugte Spalten Mariadb Virtuelle Spalten

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top