Tutorial: stany zgody GA4 w BigQuery i Looker Studio

Spis treści
Raport zgód z GA4 odpowiada na konkretne pytanie: jaki odsetek wyeksportowanych zdarzeń ma dany stan zgody? Wartość 70% dla analytics_storage = Yes oznacza taki stan w 70 na 100 zarejestrowanych zdarzeń. Nie oznacza akceptacji banera przez 70% odwiedzających.
Ten tutorial przygotowuje dzienny raport stanów w BigQuery oraz dane do Looker Studio. Zgoda na przechowywanie danych analitycznych pozostaje oddzielona od zgody na przechowywanie danych reklamowych. Raport zachowuje nieznane stany i pokazuje brakujące tabele dzienne. Zmiany stanowią punkt wyjścia do analizy, a nie dowód błędu banera lub utraconych konwersji.

1. Definicja wskaźnika i wymagania
Punktem wyjścia jest istniejące połączenie GA4 z BigQuery, włączony eksport dzienny oraz uprawnienia do odczytu i wykonywania zapytań. Consent Mode musi prawidłowo przekazywać wartości domyślne oraz aktualizacje. Połączenie z BigQuery nie odtwarza wcześniejszych decyzji w banerze.
Schemat zdarzeń zawiera privacy_info.analytics_storage i privacy_info.ads_storage. Udokumentowane wartości to Yes, No i Unset. Zapytanie dodatkowo zbiera wartości null i nieoczekiwane treści w kategorii other. Brak ustawionego stanu nie oznacza jednoznacznej odmowy. Te dwa pola nie opisują wszystkich ustawień Consent Mode: przechowywanie danych reklamowych różni się od zgody na dane użytkowników do celów reklamowych i na personalizację reklam.
Mianownik obejmuje wszystkie wyeksportowane zdarzenia w wybranych tabelach dziennych. Aktywna wizyta wnosi więcej zdarzeń niż krótka wizyta. Stan domyślny przed decyzją w banerze może różnić się od późniejszego stanu. W podstawowym wdrożeniu zablokowany ruch nie trafia do GA4. Wdrożenie zaawansowane może wysyłać pingi bez ciasteczek przy odmowie przechowywania. Blokady przeglądarki i błędy zbierania tworzą kolejne luki. Dzielenie dostępnych wierszy nie odzyskuje brakujących danych.
2. Trzydzieści zakończonych dni kalendarzowych
Przykład używa Europe/Warsaw; właściwa jest strefa czasowa usługi GA4. Nazwy projektu i zbioru danych wymagają dopasowania. Wynik zawiera jeden wiersz na dzień, od dnia sprzed 30 dni do wczoraj włącznie. Tabele intraday i bieżący dzień pozostają wykluczone.
-- Standard SQL. Replace project/dataset and property timezone.
WITH days AS (
SELECT day FROM UNNEST(GENERATE_DATE_ARRAY(
DATE_SUB(CURRENT_DATE('Europe/Warsaw'), INTERVAL 30 DAY),
DATE_SUB(CURRENT_DATE('Europe/Warsaw'), INTERVAL 1 DAY))) AS day
), available AS (
SELECT table_name FROM `project.analytics_XXXXXXXXX.INFORMATION_SCHEMA.TABLES`
WHERE table_type = 'BASE TABLE'
AND REGEXP_CONTAINS(table_name, r'^events_[0-9]{8}$')
), counts AS (
SELECT PARSE_DATE('%Y%m%d', _TABLE_SUFFIX) AS day,
COUNT(*) AS total_events,
COUNTIF(privacy_info.analytics_storage = 'Yes') AS analytics_yes,
COUNTIF(privacy_info.analytics_storage = 'No') AS analytics_no,
COUNTIF(privacy_info.analytics_storage = 'Unset') AS analytics_unset,
COUNTIF(COALESCE(privacy_info.analytics_storage NOT IN ('Yes','No','Unset'), TRUE)) AS analytics_other,
COUNTIF(privacy_info.ads_storage = 'Yes') AS ads_yes,
COUNTIF(privacy_info.ads_storage = 'No') AS ads_no,
COUNTIF(privacy_info.ads_storage = 'Unset') AS ads_unset,
COUNTIF(COALESCE(privacy_info.ads_storage NOT IN ('Yes','No','Unset'), TRUE)) AS ads_other
FROM `project.analytics_XXXXXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN
FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE('Europe/Warsaw'), INTERVAL 30 DAY))
AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE('Europe/Warsaw'), INTERVAL 1 DAY))
GROUP BY day
)
SELECT d.day, a.table_name IS NOT NULL AS table_present,
IF(a.table_name IS NULL, NULL, COALESCE(c.total_events, 0)) AS total_events,
IF(a.table_name IS NULL, NULL, COALESCE(c.analytics_yes, 0)) AS analytics_yes,
IF(a.table_name IS NULL, NULL, COALESCE(c.analytics_no, 0)) AS analytics_no,
IF(a.table_name IS NULL, NULL, COALESCE(c.analytics_unset, 0)) AS analytics_unset,
IF(a.table_name IS NULL, NULL, COALESCE(c.analytics_other, 0)) AS analytics_other,
IF(a.table_name IS NULL, NULL, COALESCE(c.ads_yes, 0)) AS ads_yes,
IF(a.table_name IS NULL, NULL, COALESCE(c.ads_no, 0)) AS ads_no,
IF(a.table_name IS NULL, NULL, COALESCE(c.ads_unset, 0)) AS ads_unset,
IF(a.table_name IS NULL, NULL, COALESCE(c.ads_other, 0)) AS ads_other
FROM days AS d
LEFT JOIN available AS a
ON a.table_name = CONCAT('events_', FORMAT_DATE('%Y%m%d', d.day))
LEFT JOIN counts AS c USING (day)
ORDER BY day;table_present = false oznacza brak odpowiedniej dziennej tabeli bazowej. Liczniki zachowują wtedy NULL. Istniejąca pusta tabela daje zero zdarzeń. Ani obecność tabeli, ani wynik zerowy nie dowodzą kompletności zbierania. Zapytanie wymaga co najmniej jednej pasującej tabeli eksportu i zgodnych schematów. Błędy dostępu lub schematu pozostają błędami, zamiast zamieniać się w pozorne zera.
Filtr liczbowego sufiksu ogranicza wybrane tabele dzienne. Przed uruchomieniem harmonogramu próbne sprawdzenie BigQuery (dry run) i limit rozliczanych bajtów pomagają kontrolować koszt. Tabele dzienne mogą otrzymywać spóźnione zdarzenia do trzech dni po dacie zdarzenia, dlatego najnowsze wyniki pozostają wstępne. Tabela raportowa wymaga zastępowania lub operacji upsert dla odpowiednich dat. Codzienne dopisywanie całego wyniku z 30 dni powiela liczniki. Mała tabela raportowa ogranicza ponowne skanowanie surowych danych, ale nie gwarantuje stałego rachunku.
3. Dashboard bez mylącego lejka
Looker Studio obsługuje natywne wykresy lejkowe. Układ „wszystkie zdarzenia → Analytics Yes → Ads Yes” nadal nie pasuje do tej wizualizacji: ostatnie dwa zbiory nie muszą zawierać się jeden w drugim. Oddzielne słupki lub szeregi czasowe pokazują obie kategorie bez tworzenia fikcyjnej sekwencji.
Tabela raportowa łączy się przez konektor BigQuery. day jest wymiarem daty, a pola licznikowe korzystają z agregacji Suma. Dwie karty wyników obliczają udziały zdarzeń z sum:
CASE WHEN SUM(total_events) > 0 THEN SUM(analytics_yes) / SUM(total_events) ELSE NULL END
Pola otrzymują format procentowy, bez dodatkowego mnożenia przez 100. Odpowiednia formuła reklamowa używa ads_yes. Zerowy mianownik daje pusty wynik. Dzienny szereg czasowy może stosować to samo obliczenie z grupowaniem według day. Dodatkowa tabela pokazuje liczniki Yes, No, Unset i Other dla każdej kategorii, łączną liczbę zdarzeń i table_present. Brakujące dni pozostają widocznymi lukami.
Porównania okresów wykorzystują okna tej samej długości i pokazują brakujące dni obok wyniku. Średnia dziennych procentów jednakowo waży małe i duże dni: 1 z 1 zdarzeń oraz 0 z 99 zdarzeń dają średnio 50%. Prawidłowy łączny udział wynosi jednak 1%. Brakujące tabele nie mogą znikać niezauważenie z porównania okresów.
4. Interpretacja i ograniczenia
Zmiana może wynikać ze struktury zdarzeń, źródeł ruchu, stanów domyślnych, konfiguracji banera, kolejności tagów lub problemów eksportu. Kontrolowane sprawdzenie banera i żądań tagów, historia wdrożeń oraz kontrola eksportu pomagają rozróżnić te przyczyny. Jednoczesny spadek udziału i wolumenu nie dowodzi związku przyczynowego.
Raport nie wskazuje dostępności ani aktywności modelowania behawioralnego GA4. Kryteria Google obejmują bezwzględne liczby zdarzeń i użytkowników w ciągu wielu dni, wymagania wdrożenia oraz dodatkowe kontrole jakości modelu. Sam procent zdarzeń nie sprawdza tych warunków. Modelowanie zachowań i modelowanie konwersji odpowiadają ponadto na różne pytania.
Wynik zawiera dzienne agregaty bez identyfikatorów użytkowników. Nadal potrzebne są ograniczenia dostępu i ostrożność przy małych grupach; agregacja nie gwarantuje automatycznej anonimowości. Wskaźnik akceptacji oparty na decyzjach wymaga osobno zdefiniowanej miary CMP, z zasadami dotyczącymi ponownych pytań, zmienionych decyzji i banerów bez odpowiedzi.
Pytania i odpowiedzi
Czy udział bliski 100% dowodzi skuteczności banera?
Nie. Jeśli do GA4 trafia tylko ruch ze zgodą, niemal wszystkie zapisane zdarzenia mogą mieć stan Yes, mimo wielu nieobecnych wizyt z odmową. Raport opisuje dostępne dane o zdarzeniach, a nie wszystkie interakcje z banerem.
Czy Unset należy do kategorii No?
Nie. Oddzielne kategorie dla Unset oraz brakujących i nieoczekiwanych wartości ujawniają luki konfiguracji. Wyświetlany udział Yes nadal używa wszystkich zarejestrowanych zdarzeń jako mianownika.
Czy brak dnia oznacza brak zdarzeń?
Nie. Brak tabeli eksportu jest problemem dostępności danych. Zapytanie zwraca dla takiego dnia NULL; liczba 0 dotyczy istniejącej pustej tabeli.