Einwilligungsstatus je Ereignis im GA4-BigQuery-Export auslesen

Inhalt
In GA4-Berichten erscheint die Einwilligung als Zusammenfassung, wenn überhaupt. Im BigQuery-Export wird der Status für jedes Ereignis in einem verschachtelten Datensatz neben den Ereignisdaten festgehalten. Er beschreibt den Zustand zum Zeitpunkt der Aufzeichnung.
Das ist ein deutlich besserer Gegenstand zum Arbeiten, und er bringt eine Falle mit, die eine falsche Zahl erzeugt statt eines Fehlers.
Wo er steht
Der Datensatz heißt privacy_info und speichert die Speicherzustände als Zeichenketten, nicht als Wahrheitswerte. Auf die verschachtelten Felder greifst Du mit Punktnotation zu; die Vergleichswerte stehen als Zeichenketten in Anführungszeichen. Weil Werte fehlen können, muss vor der Berechnung einer Quote feststehen, wie ein fehlender Wert behandelt wird.
SELECT
event_date,
privacy_info.analytics_storage AS analytics,
privacy_info.ads_storage AS ads,
COUNT(*) AS ereignisse
FROM `projekt.analytics_XXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260801' AND '20260831'
GROUP BY 1, 2, 3
ORDER BY 1, 4 DESC
-- vor jedem Filter laufen lassen. Der Zweck ist zu sehen,
-- wie viele Zeilen in den dritten Topf fallen, denn diese
-- Zahl entscheidet, ob die Falle unten ueberhaupt greiftErst gruppieren und dann filtern ist hier die ganze Disziplin. Das kostet eine Abfrage, auf einem einzelnen Monat fast nichts, und beantwortet die Frage, von der jede weitere Abfrage abhängt.
Die Falle
Zwei Filter, die sich wie Gegensätze lesen, sind keine. analytics_storage = 'No' wählt Ereignisse mit ausdrücklicher Ablehnung. analytics_storage != 'Yes' wählt Ablehnungen plus alles ohne festgehaltenen Zustand – denn in SQL ergibt ein Vergleich gegen einen Leerwert wieder einen Leerwert und nicht wahr, sodass der erste Filter die leeren Zeilen still fallen lässt und der zweite sie behält.
Eine Einwilligungsquote auf dem ersten fällt höher aus als eine auf dem zweiten, auf denselben Daten, für denselben Tag. Von außen ist keine der beiden offensichtlich falsch; beide liefern einen plausiblen Prozentsatz. Welche stimmt, hängt davon ab, was die Leerwerte tatsächlich sind, und das ist eine Frage an die Umsetzung, nicht an SQL.
Leerwerte bedeuten meistens, dass beim Auslösen des Ereignisses kein Einwilligungssignal beim Tag ankam – ein Treffer vor der Entscheidung am Banner, eine Seite, auf der die Einwilligungsplattform nicht lud, ein serverseitiges Ereignis, das ohne durchgereichten Zustand gebaut wurde. Das sind drei verschiedene Lagen mit drei verschiedenen Bedeutungen, und sie mit ausdrücklichen Ablehnungen in einen Topf zu werfen, überzeichnet die Ablehnung.
Was damit möglich wird
Der Zustand liegt für jedes Ereignis vor, nicht nur für die gesamte Property. Deshalb lässt er sich zusammen mit den anderen Feldern derselben Ereigniszeile auswerten.
nach Seite welche Seiten am haeufigsten abgelehnt werden
meist die, auf denen das Banner mit dem Inhalt
um dieselbe Bildschirmflaeche ringt
nach Geraet Mobil und Rechner unterscheiden sich weit
genug, dass eine Gesamtzahl keines von beiden
beschreibt
nach Quelle Verkehr aus einem Link innerhalb einer App
verhaelt sich anders als ein Direktbesuch
nach Stunde eine Auslieferung, die das Banner zerlegt,
zeigt sich als Stufe zu einer bestimmten
Stunde - was ein Tageswert vollstaendig
glaettetDer Schnitt nach Seite lohnt sich am schnellsten, weil er aus einer Zahl, mit der niemand etwas anfangen kann, eine Liste konkreter Seiten macht.
Zwei Dinge, die über die Zeile zu wissen sind
Ein einzelner Besuch kann Ereignisse in mehr als einem Zustand enthalten. Wer ein Banner mitten in einer Sitzung annimmt, hinterlässt unter derselben Pseudokennung eine Spur abgelehnter Ereignisse, gefolgt von erteilten – was richtig ist und jede Abfrage leise zerlegt, die einen Zustand je Nutzer annimmt.
Und das Exportschema wächst. Der Ereignistabelle wurden schon Felder hinzugefügt und werden wieder welche hinzugefügt, was das Argument gegen SELECT * in allem Nachgelagerten ist: Eine unangekündigt eintreffende Spalte ist harmlos für eine Abfrage, die ihre Spalten benennt, und ein gebrochener Ladeauftrag für eine, die es nicht tut.
Fragen und Antworten
Warum enthält ein Export manchmal keine einzige Ablehnung?
Meist, weil der Consent Mode im einfachen Modus läuft. Dort lädt das Tag erst nach der Einwilligung; wer ablehnt, sendet nichts, und im Export stehen nur Ereignisse mit erteilter Einwilligung oder ohne festgehaltenen Zustand. Eine Einwilligungsquote lässt sich aus einem solchen Export nicht bilden, sie muss dann aus den Aufzeichnungen der Einwilligungsplattform kommen.
Wie lassen sich die Leerwerte den drei Lagen aus dem Artikel zuordnen?
Über die übrigen Felder derselben Zeile, denn jede der drei Lagen hinterlässt ein eigenes Muster:
- Treffer vor der Entscheidung am Banner ballen sich am Anfang einer Sitzung. Werden die Ereignisse je Sitzung nach event_timestamp geordnet, stehen die Leerwerte vorn, gefolgt von Ereignissen mit festgehaltenem Zustand.
- Eine Seite, auf der die Einwilligungsplattform nicht lud, zeigt sich im Schnitt nach Seite oder nach Stunde: Leerwerte gehäuft auf bestimmten Seiten oder Seitenvorlagen oder als Stufe ab dem Zeitpunkt einer Auslieferung.
- Serverseitige Ereignisse ohne durchgereichten Zustand lassen sich am Ereignisnamen erkennen, wenn nur der Server sie sendet; sie sind dann durchgehend leer, unabhängig von Seite und Uhrzeit.
In der Abfrage hilft es, fehlende Werte als dritte Gruppe sichtbar zu halten, statt ihre Behandlung dem Vergleich zu überlassen. COALESCE(privacy_info.analytics_storage, 'leer') ersetzt den Leerwert durch eine eigene Ausprägung, die ausdrücklich in jeder Gruppierung und jedem Filter erscheint. Was „leer“ bedeutet, wird damit einmal an einer Stelle entschieden und nicht stillschweigend in jedem WHERE.
Warum grenzt die Abfrage über _TABLE_SUFFIX ab und nicht allein über event_date?
Aus zwei Gründen. Erstens rechnet BigQuery Abfragen im On-Demand-Tarif nach gelesenen Daten ab, und nur ein Filter auf _TABLE_SUFFIX bestimmt, welche Tagestabellen überhaupt gelesen werden; ein Filter allein auf event_date liest zunächst jede Tabelle, die der Platzhalter erfasst, und verwirft die Zeilen erst danach.
Zweitens erfasst events_* auch die Tabellen des Streaming-Exports, events_intraday_…, deren Suffix mit intraday_ beginnt. Ein Bereich aus Datumswerten wie ‘20260801’ bis ‘20260831’ schließt sie aus. Reicht ein Zeitraum bis an den laufenden Tag heran, liest ein Filter nur auf event_date dagegen deren vorläufige Zeilen mit.