Database
 sql >> Datenbank >  >> RDS >> Database

Ein Abonnement-Geschäftsdatenmodell

In den vorherigen zwei Teilen haben wir das Live-Datenbankmodell für ein abonnementbasiertes Unternehmen und ein Data Warehouse (DWH) vorgestellt, das wir für die Berichterstellung verwenden könnten. Obwohl es offensichtlich ist, dass sie zusammenarbeiten sollten, gab es keine Verbindung zwischen diesen beiden Modellen. Heute machen wir den nächsten Schritt und schreiben den Code, um Daten aus der Live-Datenbank in unser DWH zu übertragen.

Die Datenmodelle

Bevor wir in den Code eintauchen, erinnern wir uns an die beiden Modelle, mit denen wir arbeiten werden. Das erste ist das Transaktionsdatenmodell, das wir verwenden werden, um unsere Echtzeitdaten zu speichern. Da wir ein abonnementbasiertes Geschäft betreiben, müssen wir Kunden- und Abonnementdaten, Kundenbestellungen und den Bestellstatus speichern.

Wir könnten diesem Modell wirklich viel hinzufügen, z. B. das Verfolgen von Zahlungen und das Speichern historischer Daten (insbesondere Änderungen von Kunden- und Abonnementdaten). Um den ETL-Prozess (Extrahieren, Transformieren und Laden) hervorzuheben, möchte ich dieses Modell jedoch so einfach wie möglich halten.




Die Verwendung eines Transaktionsdatenmodells als Berichtsdatenbank funktioniert möglicherweise in einigen Fällen, aber nicht in allen Fällen. Das haben wir bereits erwähnt, aber es lohnt sich, es zu wiederholen. Wenn wir unsere Berichtsaufgaben von unseren Echtzeitprozessen trennen wollen, sollten wir eine Art Berichtsdatenbank erstellen. Ein Data Warehouse ist eine Lösung.

Unser DWH basiert auf vier Faktentabellen. Die ersten beiden verfolgen die Anzahl der Kunden und Abonnements auf täglicher Ebene. Die verbleibenden zwei verfolgen die Anzahl der Lieferungen und die in diesen Lieferungen enthaltenen Produkte.

Ich gehe davon aus, dass wir unseren ETL-Prozess einmal am Tag ausführen werden. Zuerst füllen wir Dimensionstabellen mit neuen Werten (falls erforderlich). Danach füllen wir Faktentabellen.




Um unnötige Wiederholungen zu vermeiden, zeige ich nur den Code, der die ersten beiden Dimensionstabellen und die ersten beiden Faktentabellen füllt. Die verbleibenden Tabellen können mit sehr ähnlichem Code gefüllt werden. Ich ermutige Sie, den Code selbst aufzuschreiben. Es gibt keinen besseren Weg, etwas Neues zu lernen, als es auszuprobieren.

Die Idee:Maßtabellen

Die allgemeine Idee besteht darin, gespeicherte Prozeduren zu erstellen, die wir regelmäßig verwenden können, um die DWH-Dimensionstabellen sowie Faktentabellen zu füllen. Diese Prozeduren übertragen Daten zwischen zwei Datenbanken auf demselben Server. Das bedeutet, dass einige Abfragen innerhalb dieser Prozeduren Tabellen aus beiden Datenbanken verwenden. Dies wird erwartet; Wir müssen den Zustand des DWH mit der Live-DB vergleichen und Änderungen am DWH entsprechend den Vorgängen in der Live-DB vornehmen.

Wir haben vier Dimensionstabellen in unserem DWH:dim_time , dim_city , dim_product und dim_delivery_status .

Die Zeitdimension wird durch Hinzufügen des vorherigen Datums ausgefüllt. Die Hauptannahme ist, dass wir dieses Verfahren täglich nach Geschäftsschluss durchführen.

Die Städte- und Produktdimensionen hängen von den aktuellen Werten ab, die in city und product Wörterbücher in der Live-Datenbank. Wenn wir etwas zu diesen Wörterbüchern hinzufügen, werden beim nächsten DWH-Update neue Werte zu den Dimensionstabellen hinzugefügt.

Die letzte Dimensionstabelle ist der dim_delivery_status Tisch. Es wird nicht aktualisiert, da es nur drei Standardwerte enthält. Eine Lieferung ist entweder unterwegs, storniert oder zugestellt.

Die Idee:Faktentabellen

Das Füllen von Faktentabellen ist eigentlich die eigentliche Aufgabe. Während die Wörterbücher in der Live-Datenbank kein Zeitstempelattribut enthalten, tun es Tabellen mit Daten, die als Ergebnis unserer Operationen eingefügt wurden. Sie werden zwei Zeitstempelattribute bemerken, time_inserted und time_updated , im Datenmodell.

Auch hier gehe ich davon aus, dass wir den DWH-Import einmal am Tag erfolgreich durchführen werden. Dies ermöglicht es uns, die Daten auf Tagesebene zu aggregieren. Wir zählen die Anzahl der aktiven und gekündigten Kunden und Abonnements sowie die Lieferungen und gelieferten Produkte für dieses Datum.

Unser Live-Modell funktioniert gut, wenn wir nach dem COB (Closing of Business) ein Insert-Verfahren durchführen. Wenn wir jedoch mehr Flexibilität wünschen, sollten wir einige Änderungen am Modell vornehmen. Eine solche Änderung könnte eine separate Verlaufstabelle sein, um den genauen Zeitpunkt zu verfolgen, an dem sich Daten in Bezug auf Kunden oder Abonnements geändert haben. Mit unserer aktuellen Organisation wissen wir, dass die Änderung stattgefunden hat, aber wir wissen nicht, ob es vor dieser Änderung Änderungen gab (z. B. ein Kunde hat gestern gekündigt, sein Konto nach Mitternacht wieder aktiviert und dann heute wieder gekündigt). .

Dimensionstabellen füllen

Wie bereits erwähnt, gehe ich davon aus, dass wir den DWH-Import genau einmal am Tag durchführen. Wenn dies nicht der Fall ist, benötigen wir zusätzlichen Code, um neu eingefügte Daten aus den Dimensions- und Faktentabellen zu löschen. Für die Dimensionstabellen würde sich dies auf das Löschen des angegebenen Datums beschränken.

Zuerst prüfen wir, ob das angegebene Datum in dim_time Tisch. Wenn nicht, fügen wir der Tabelle eine neue Zeile hinzu; Wenn ja, müssen wir nichts tun. In den meisten Fällen werden alle Daten während der anfänglichen Produktionsbereitstellung eingefügt. Aber ich werde dieses Beispiel zu Bildungszwecken verwenden.

Für dim_city und dim_product Dimensionen, füge ich nur alle neuen Werte hinzu, die ich in city und product Tische. Ich werde keine Löschungen vornehmen, da auf zuvor eingefügte Werte in einer Faktentabelle verwiesen werden könnte. Wir könnten mit einer sanften Löschung gehen, z. ein „Aktiv“-Flag haben, das wir ein- und ausschalten können.

Für die letzte Tabelle dim_delivery_status , werde ich nichts tun, da es immer die gleichen drei Werte enthalten wird.

Der folgende Code erstellt eine Prozedur, die die Dimensionstabellen dim_time und dim_city .

Für die Zeitdimension füge ich das gestrige Datum hinzu. Ich gehe davon aus, dass der ETL-Prozess direkt nach Mitternacht beginnt. Ich überprüfe, ob diese Dimension bereits vorhanden ist, und wenn nicht, füge ich das neue Datum in die Tabelle ein.

Für die Stadtdimension verwende ich einen LEFT JOIN, um Daten aus der Live-Datenbank und der DWH-Datenbank zu verknüpfen, um festzustellen, welche Zeilen fehlen. Dann füge ich nur alle fehlenden Daten zur Dimensionstabelle hinzu. Es ist erwähnenswert, dass es einige Möglichkeiten gibt, zu überprüfen, ob Daten geändert wurden. Dieser Vorgang wird als Change Data Capture oder CDC bezeichnet. Eine gängige Methode ist die Suche nach aktualisierten Zeitstempeln oder Versionen. Es gibt einige zusätzliche Möglichkeiten, die jedoch den Rahmen dieses Artikels sprengen würden.

Sehen wir uns jetzt den Code an, der mit MySQL-Syntax geschrieben wurde .

DROP PROCEDURE IF EXISTS p_update_dimensions//

CREATE PROCEDURE p_update_dimensions ()
BEGIN
	SET @time_exists = 0;
    SET @time_date = DATE_ADD(DATE(NOW()), INTERVAL -1 DAY);
    -- procedure populates dimension tables with new values
    
    
    -- dim_time
    SET @time_exists = (SELECT COUNT(*) FROM subscription_dwh.dim_time dim_time WHERE dim_time.time_date = @time_date);
    IF (@time_exists = 0) THEN
        INSERT INTO subscription_dwh.`dim_time`(`time_date`, `time_year`, `time_month`, `time_week`, `time_weekday`, `ts`)

        SELECT 

            @time_date AS time_date,
            YEAR(@time_date) AS time_year,
            MONTH(@time_date) AS time_month,
            WEEK(@time_date) AS time_week,
            WEEKDAY(@time_date) AS time_weekday,
            NOW() AS ts;  
    END IF;
    
        
    -- dim_city
    INSERT INTO subscription_dwh.`dim_city`(`city_name`, `postal_code`, `country_name`, `ts`)
    
    SELECT
        city_live.city_name,
        city_live.postal_code,
        country_live.country_name,
        Now()
    FROM subscription_live.city city_live
    INNER JOIN subscription_live.country country_live 
        ON city_live.country_id = country_live.id
    LEFT JOIN subscription_dwh.dim_city city_dwh 
        ON city_live.city_name = city_dwh.city_name
        AND city_live.postal_code = city_dwh.postal_code
        AND country_live.country_name = city_dwh.country_name
    WHERE city_dwh.id IS NULL;
END//

-- CALL p_update_dimensions ()

Diese Prozedur ausführen – was wir mit der kommentierten Prozedur CALL tun -- fügt ein neues Datum und alle fehlenden Städte in die Dimensionstabellen ein. Versuchen Sie, Ihren eigenen Code hinzuzufügen, um die verbleibenden zwei Dimensionstabellen mit neuen Werten zu füllen.

Der ETL-Prozess in einem Data Warehouse

Die Hauptidee hinter Data Warehousing besteht darin, aggregierte Daten im gewünschten Format zu enthalten. Natürlich sollten wir dieses Format kennen, bevor wir überhaupt mit dem Bau des Lagers beginnen. Wenn wir alles wie geplant gemacht haben, können wir alle Vorteile nutzen, die uns ein DWH bietet. Der Hauptvorteil ist die verbesserte Leistung beim Ausführen von Abfragen. Unsere Abfragen arbeiten mit weniger Datensätzen (weil sie aggregiert sind) und werden in der Berichtsdatenbank ausgeführt (und nicht in der Live-Datenbank).

Aber bevor wir abfragen können, müssen wir Fakten in unserer Datenbank speichern. Wie wir das machen, hängt davon ab, was wir später mit unseren Daten machen müssen. Wenn wir kein gutes Gesamtbild haben, bevor wir mit dem Bau unseres DWH beginnen, könnten wir schnell in Schwierigkeiten geraten! bald.

Der Name dieses Prozesses ist ETL:E =Extrahieren, T =Transformieren, L =Laden. Es greift die Daten, transformiert sie passend zur DWH-Struktur und lädt sie in das DWH. Um genau zu sein, der eigentliche Prozess, den wir verwenden werden, ist ELT:Extrahieren, Laden, Transformieren. Da wir gespeicherte Prozeduren verwenden, extrahieren wir Daten, laden sie und transformieren sie dann entsprechend unseren Anforderungen. Es ist gut zu wissen, dass ETL und ELT zwar etwas unterschiedlich sind, die Begriffe aber manchmal synonym verwendet werden.

Befüllen der Faktentabellen

Das Füllen von Faktentabellen ist der Grund, warum wir wirklich hier sind. Heute fülle ich zwei Faktentabellen aus, die fact_customer_subscribed Tabelle und den fact_subscription_status Tisch. Die verbleibenden zwei Faktentabellen können Sie als Hausaufgabe ausprobieren.

Bevor wir mit dem Füllen der Faktentabelle fortfahren, müssen wir davon ausgehen, dass die Dimensionstabellen mit neuen Werten gefüllt werden. Das Füllen der Faktentabellen folgt dem gleichen Muster. Da sie die gleiche Struktur haben, erkläre ich beide zusammen.

Wir gruppieren Daten nach zwei Dimensionen:Zeit und Stadt. Die Zeitdimension wird auf gestern gesetzt und wir finden die ID des zugehörigen Datensatzes in dim_time Tabelle durch Vergleichen von Datumsangaben (der letzte INNER JOIN in beiden Abfragen).

Die ID von dim_city wird extrahiert, indem alle Attribute zusammengefügt werden, die eine EINZIGARTIGE Kombination in der Dimensionstabelle bilden (Stadtname, Postleitzahl und Ländername).

In dieser Abfrage testen wir Werte mit CASE und summieren sie dann. Für aktive und inaktive Kunden habe ich das Datum nicht getestet. Ich habe jedoch Istwerte für diese Felder ausgewählt. Für neue und gekündigte Konten habe ich die aktualisierte Zeit getestet.

DROP PROCEDURE IF EXISTS p_update_facts//

CREATE PROCEDURE p_update_facts ()
BEGIN

    SET @time_date = DATE_ADD(DATE(NOW()), INTERVAL -1 DAY);
    -- procedure populates fact tables with new values
    
    
    -- fact_customer_subscribed    
    INSERT INTO `fact_customer_subscribed`(`dim_city_id`, `dim_time_id`, `total_active`, `total_inactive`, `daily_new`, `daily_canceled`, `ts`)
    
    SELECT 
        city_dwh.id AS dim_ctiy_id,
        time_dwh.id AS dim_time_id,
        SUM(CASE WHEN customer_live.active = 1 THEN 1 ELSE 0 END) AS total_active,
        SUM(CASE WHEN customer_live.active = 0 THEN 1 ELSE 0 END) AS total_inactive,
        SUM(CASE WHEN customer_live.active = 1 AND DATE(customer_live.time_updated) = @time_date THEN 1 ELSE 0 END) AS daily_new,
        SUM(CASE WHEN customer_live.active = 0 AND DATE(customer_live.time_updated) = @time_date THEN 1 ELSE 0 END) AS daily_canceled,
        MIN(NOW()) AS ts
    FROM subscription_live.`customer` customer_live
    INNER JOIN subscription_live.`city` city_live ON customer_live.city_id = city_live.id
    INNER JOIN subscription_live.`country` country_live ON city_live.country_id = country_live.id
    INNER JOIN subscription_dwh.dim_city city_dwh
        ON city_live.city_name = city_dwh.city_name
        AND city_live.postal_code = city_dwh.postal_code
        AND country_live.country_name = city_dwh.country_name
    INNER JOIN subscription_dwh.dim_time time_dwh ON time_dwh.time_date = @time_date
    GROUP BY
        city_dwh.id,
        time_dwh.id;


    -- fact_subscription_status   
    INSERT INTO `fact_subscription_status`(`dim_city_id`, `dim_time_id`, `total_active`, `total_inactive`, `daily_new`, `daily_canceled`, `ts`)
    
    SELECT 
        city_dwh.id AS dim_ctiy_id,
        time_dwh.id AS dim_time_id,
        SUM(CASE WHEN subscription_live.active = 1 THEN 1 ELSE 0 END) AS total_active,
        SUM(CASE WHEN subscription_live.active = 0 THEN 1 ELSE 0 END) AS total_inactive,
        SUM(CASE WHEN subscription_live.active = 1 AND DATE(subscription_live.time_updated) = @time_date THEN 1 ELSE 0 END) AS daily_new,
        SUM(CASE WHEN subscription_live.active = 0 AND DATE(subscription_live.time_updated) = @time_date THEN 1 ELSE 0 END) AS daily_canceled,
        MIN(NOW()) AS ts
    FROM subscription_live.`customer` customer_live
    INNER JOIN subscription_live.`subscription` subscription_live ON subscription_live.customer_id = customer_live.id
    INNER JOIN subscription_live.`city` city_live ON customer_live.city_id = city_live.id
    INNER JOIN subscription_live.`country` country_live ON city_live.country_id = country_live.id
    INNER JOIN subscription_dwh.dim_city city_dwh
        ON city_live.city_name = city_dwh.city_name
        AND city_live.postal_code = city_dwh.postal_code
        AND country_live.country_name = city_dwh.country_name
    INNER JOIN subscription_dwh.dim_time time_dwh ON time_dwh.time_date = @time_date
    GROUP BY
        city_dwh.id,
        time_dwh.id;
END//

-- CALL p_update_facts ()

Noch einmal habe ich die letzte Zeile auskommentiert. Entfernen Sie den Kommentar und Sie können diese Zeile verwenden, um die Prozedur aufzurufen und neue Werte einzufügen. Bitte beachten Sie, dass ich keine vorhandenen alten Werte gelöscht habe, sodass dieses Verfahren nicht funktioniert, wenn wir bereits Werte für dieses Datum und diese Stadt haben. Dies kann gelöst werden, indem vor dem Einfügen Löschungen durchgeführt werden.

Denken Sie daran, dass wir die verbleibenden Faktentabellen in unserem DWH füllen müssen. Ich ermutige Sie, das selbst zu versuchen!

Eine andere Sache, die ich auf jeden Fall empfehlen würde, ist, den gesamten Prozess innerhalb einer Transaktion zu platzieren. Das würde sicherstellen, dass entweder alle Einfügungen erfolgreich sind oder keine vorgenommen werden. Dies ist sehr wichtig, wenn wir vermeiden möchten, dass Daten teilweise eingefügt werden, z. wenn wir mehrere Verfahren zum Einfügen von Dimensionen und Fakten haben und einige von ihnen ihren Job machen, während andere versagen.

Was denken Sie?

Heute haben wir gesehen, wie wir den ELT/ETL-Prozess durchführen und Daten aus einer Live-Datenbank in ein Data Warehouse laden können. Obwohl der von uns gezeigte Prozess ziemlich vereinfacht ist, enthält er alle erforderlichen Elemente, um die Daten zu extrahieren, in ein geeignetes Format zu transformieren und schließlich in das DWH zu laden. Was denkst du? Bitte teilen Sie uns Ihre Erfahrungen in den Kommentaren unten mit.