Praktyczny przewodnik po schemacie eksportu GA4 do BigQuery

Gdy pierwszy raz otwierasz eksport GA4 w BigQuery i odpalasz SELECT * FROM events_*, wygląda zwodniczo prosto, dopóki nie spróbujesz odczytać jednego adresu URL strony i nie zorientujesz się, że jest zakopany trzy poziomy głęboko w czymś, co nazywa się event_params. To moment, w którym większość ludzi się zacina, i to nie Twój SQL jest problemem. Rzecz w tym, że schemat GA4 ma inny kształt niż wszystko, co dotąd odpytywałeś.

Oto model myślowy, który to naprawia. Eksport GA4 do BigQuery to jeden wiersz na zdarzenie, trzymany w dziennych tabelach, z ciekawymi szczegółami schowanymi w zagnieżdżonych, powtarzalnych polach, które musisz UNNEST, żeby je odczytać. Gdy to zaskoczy, cały schemat przestaje przerażać, a zaczyna być najpotężniejszą wersją Twoich danych analitycznych, jakiej kiedykolwiek dotkniesz.

Ten przewodnik przechodzi przez strukturę: tabele, kolumny najwyższego poziomu, zagnieżdżone pola, na których wszyscy się potykają, i jak faktycznie wyciągnąć wartość. Jeśli jeszcze nie włączyłeś eksportu, przewodnik konfiguracji GA4 z BigQuery to obejmuje; ten zakłada, że dane już wpadają i potrzebujesz zrozumieć ich kształt.

Najważniejsze: eksport GA4 trzyma jeden wiersz na zdarzenie w dzielonych dziennie tabelach events_YYYYMMDD. Kolumny, na których zależy Ci najbardziej, adresy URL, identyfikatory sesji, parametry niestandardowe, żyją wewnątrz powtarzalnych pól RECORD jak event_params, a czytasz je podzapytaniem UNNEST. Opanuj ten jeden wzorzec, a reszta schematu jest prosta.

Kształt eksportu

Gdy łączysz GA4 z BigQuery, dostajesz zbiór danych o nazwie analytics_<property_id>, a w nim zestaw tabel według stałego wzorca nazewnictwa.

  • events_YYYYMMDD: jedna tabela na dzień, eksport dzienny. Tu trafia prawie każde Twoje zapytanie.
  • events_intraday_YYYYMMDD: tabela tego samego dnia, strumieniowa, jeśli włączyłeś eksport streamingowy. Jest ciągle aktualizowana i zostaje zastąpiona finalną tabelą dzienną, gdy dzień się zamknie.
  • pseudonymous_users_* i users_*: tabele na poziomie użytkownika, dostępne przy nowszych opcjach eksportu, trzymające najnowszy stan na użytkownika, a nie na zdarzenie.

Tabele dzienne są dzielone (sharded), a nie partycjonowane, co ma znaczenie dla tego, jak odpytujesz po datach. Żeby przeskanować zakres dat, używasz tabeli z symbolem wieloznacznym i filtrujesz po sufiksie:

SELECT event_name, event_date
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260131'

_TABLE_SUFFIX to część YYYYMMDD każdej nazwy tabeli. Filtrowanie po nim trzyma BigQuery z dala od skanowania całej Twojej historii (i całego budżetu) przy każdym zapytaniu.

Jeden wiersz to jedno zdarzenie

To serce schematu. Każda interakcja, każde page_view, scroll, purchase, zdarzenie niestandardowe, to pojedynczy wiersz. Nie ma "tabeli sesji" ani "tabeli odsłon", jak miało Universal Analytics. Sesję rekonstruujesz ze zdarzeń, które dzielą ten sam ga_session_id.

Kolumny najwyższego poziomu opisują samo zdarzenie. To te, które odczytasz wprost, bez rozpakowywania:

KolumnaTypCo to jest
event_dateSTRINGData, jako YYYYMMDD
event_timestampINTEGERKiedy się odpaliło, w mikrosekundach od epoki
event_nameSTRINGpage_view, purchase, Twoja nazwa niestandardowa
user_pseudo_idSTRINGIdentyfikator urządzenia/klienta
user_idSTRINGTwój własny ID zalogowanego, jeśli go ustawisz
event_paramsRECORD (powtarzalne)Parametry na zdarzenie (zagnieżdżone)
user_propertiesRECORD (powtarzalne)Właściwości na użytkownika (zagnieżdżone)
itemsRECORD (powtarzalne)Pozycje e-commerce (zagnieżdżone)
device, geo, traffic_sourceRECORDUstrukturyzowany kontekst (zagnieżdżony, niepowtarzalny)

Zauważ, że wszystko naprawdę przydatne, parametry, właściwości i pozycje, to RECORD. Tak BigQuery nazywa strukturę zagnieżdżoną, a te powtarzalne to tablice. To część, przy której warto zwolnić.

Zagnieżdżone pola, na których wszyscy się potykają

event_params to to, z czym powalczysz najpierw. To powtarzalny rekord, tablica, w której każdy wpis to para klucz/wartość. Więc pojedynczy wiersz page_view niesie tablicę event_params, która może trzymać page_location, page_title, ga_session_id, engagement_time_msec i dowolne parametry niestandardowe, które wysłałeś.

Haczyk, który łapie każdego: value nie jest zwykłym polem. To samo w sobie rekord z czterema typowanymi podpolami, a tylko jedno z nich jest wypełnione na parametr:

  • value.string_value
  • value.int_value
  • value.float_value
  • value.double_value

page_location żyje w string_value. ga_session_id żyje w int_value. Wybierz złe, a dostajesz NULL, bez błędu, który by Ci powiedział dlaczego. user_properties działa dokładnie tak samo, klucz plus typowana wartość, a items to tablica rekordów z własnymi polami jak item_id, item_name, price i quantity.

Jak faktycznie odczytać parametr

Do event_params nie sięgasz notacją z kropką. UNNEST w podzapytaniu i filtrujesz po kluczu, którego chcesz. To pojedynczo najważniejszy wzorzec w całym schemacie:

SELECT
  event_date,
  event_name,
  (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location,
  (SELECT value.int_value    FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX = '20260410'
  AND event_name = 'page_view'

Każde (SELECT ... FROM UNNEST(event_params) WHERE key = '...') wyciąga jeden parametr do własnej kolumny, dobierając właściwe typowane podpole. Gdy napiszesz to dwa razy, wchodzi w pamięć mięśniową. Zbuduj bazowe zapytanie, które spłaszcza pięć czy sześć parametrów, których używasz bez przerwy, i zapisz je; wykorzystasz je w każdym projekcie.

Kiedy Daniel, analityk nowy w eksporcie, próbował zsumować niestandardowy parametr value zwykłym SUM(event_params.value), BigQuery rzucił błędy typu, a gdy wymusił, dostał nulle. Dane były w porządku. Sumował strukturę, zamiast sięgnąć do value.double_value przez UNNEST. Dziesięć minut zamieszania, jeden wzorzec do nauczenia, nigdy więcej problemu.

Gdzie faktycznie lądują Twoje wymiary niestandardowe

To pytanie, które prędzej czy później pojawia się w każdej konfiguracji GA4: zarejestrowałeś wymiary niestandardowe w interfejsie, więc gdzie są w eksporcie? Odpowiedź zależy w całości od zakresu (scope), który im nadałeś, a schemat odzwierciedla ten zakres dokładnie.

Zakres wymiaru niestandardowegoGdzie lądujeJak odczytujesz
Na zdarzenie (event-scoped)event_paramsUNNEST(event_params) po nazwie parametru
Na użytkownika (user-scoped)user_propertiesUNNEST(user_properties) po nazwie właściwości
Na pozycję (item-scoped)itemsUNNEST(items), potem pole pozycji

Więc niestandardowy parametr, który wysyłasz ze zdarzeniem, powiedzmy content_group albo logged_in_status, pojawia się jako klucz wewnątrz event_params, czytany tym samym wzorcem UNNEST co dowolny wbudowany parametr. Wymiar na użytkownika jak membership_tier ląduje za to w user_properties, bo opisuje osobę, a nie pojedyncze zdarzenie. A wymiary na pozycję jadą wewnątrz tablicy items, obok item_id i price.

Praktyczny wniosek: nie ma osobnej tabeli "wymiarów niestandardowych", w którą trzeba zajrzeć, i nic nie jest dla Ciebie przemianowane. Pod jaką nazwą wysłałeś parametr, taki dokładnie key filtrujesz w BigQuery. Jeśli nie możesz znaleźć wymiaru, prawie zawsze patrzysz w rekord złego zakresu, a nie brakuje danych. Sprawdź, jak został zarejestrowany w GA4, i rozpakuj pasujące pole.

Pułapki warte poznania, zanim zaufasz liczbie

Schemat jest logiczny, gdy go znasz, ale garść szczegółów po cichu wykrzywi wyniki, jeśli je przeoczysz.

PułapkaCzemu gryzieCo zrobić
Dzielone, nie partycjonowaneWHERE event_date = ... i tak skanuje każdą tabelęFiltruj po _TABLE_SUFFIX z events_*
Mikrosekundowe znaczniki czasuevent_timestamp to mikro, nie milisekundyPodziel przez 1 000 000 albo użyj TIMESTAMP_MICROS()
Typowane podpola valueZłe podpole zwraca NULL po cichuDopasuj typ: string vs int vs double
Intraday plus dziennaOdpytanie obu liczy dziś podwójnieOdpytuj tabele dzienne; intraday traktuj jako prowizoryczne
Dane przychodzące z opóźnieniemDzisiejsza tabela dopełnia się przez ~72 godzinyDo raportowania używaj zamkniętych dni
Brak unikalnego ID zdarzeniaDeduplikacja nie jest wbudowanaZbuduj klucz z user_pseudo_id + timestamp + nazwa

Ta z intraday plus dzienną łapie ludzi odpalających events_* bez zastanowienia. Jeśli istnieją i events_20260410, i events_intraday_20260410, skan z symbolem wieloznacznym czyta obie, a Twój bieżący dzień wygląda na zawyżony. Do czegokolwiek, na czym będziesz działać, odpytuj zamknięte tabele dzienne, a intraday zostaw do debugowania na żywo.

Dwa wzorce, które pokrywają większość pracy

Poza spłaszczaniem parametrów dwa kształty zapytań robią większość ciężkiej roboty. Po pierwsze, odtworzenie metryki sesji: policz distinct ga_session_id na user_pseudo_id, żeby dostać sesje, skoro schemat sam Ci ich nie podaje. Po drugie, e-commerce z items: CROSS JOIN UNNEST(items), żeby rozłożyć każdy zakup na pozycje, a potem agreguj przychód po item_name albo item_category. Te dwa, plus bazowe zapytanie spłaszczające parametry, to 80% tego, jak wygląda prawdziwa analiza na eksporcie.

Jeśli Twoje zapytania zaczną robić się drogie przy tej zabawie, to osobna umiejętność, a przewodnik po optymalizacji kosztów BigQuery obejmuje trzymanie rachunku w ryzach podczas eksploracji. A jeśli samo BigQuery jest dla Ciebie wciąż nowe, wyjaśnienie BigQuery po ludzku to miejsce na start przed schematem.

Kiedy naprawdę potrzebujesz surowego eksportu

Nie odpytujesz BigQuery do wszystkiego. Standardowe raporty GA4 są szybsze do codziennych pytań i nic nie kosztują. Surowy eksport zarabia na swoje miejsce, gdy trafiasz na ściany, które stawia interfejs: potrzebujesz danych niepróbkowanych, chcesz złączyć GA4 z CRM albo danymi o kosztach, modelujesz niestandardową atrybucję albo potrzebujesz szczegółu na poziomie zdarzeń, który raporty agregują. Oficjalna dokumentacja schematu eksportu GA4 jest warta zakładki dla pełnej listy pól.

Dla większości zespołów eksport staje się niezbędny w chwili, gdy pytanie zaczyna się od "czy możemy połączyć nasze dane GA4 z...". To też moment, w którym warto mieć kogoś, kto żył w tym schemacie, żeby ustawił Twoje bazowe zapytania i model danych. To część tego, czym zajmuje się konsultant GA4, i oszczędza tygodni nulli i podwójnych liczeń. Jeśli wciąż rozeznajesz się w samym GA4, wyjaśnienie GA4 pokrywa model, na którym zbudowany jest eksport.

FAQ

Czym jest schemat eksportu GA4 do BigQuery?

To struktura, której GA4 używa, gdy strumieniuje Twoje surowe dane zdarzeń do BigQuery: zbiór danych o nazwie analytics_<property_id> zawierający dzienne tabele events_YYYYMMDD, gdzie każdy wiersz to pojedyncze zdarzenie, a szczegółowe parametry są trzymane w zagnieżdżonych, powtarzalnych polach RECORD jak event_params, user_properties i items.

Po co mi UNNEST, żeby odpytać GA4 w BigQuery?

Bo parametry, których chcesz, jak page_location czy ga_session_id, żyją wewnątrz event_params, które jest powtarzalnym rekordem (tablicą), a nie zwykłą kolumną. UNNEST spłaszcza tę tablicę, żebyś mógł odfiltrować konkretny klucz i odczytać jego wartość, zwykle jako podzapytanie zwracające jeden parametr na kolumnę.

Dlaczego event_params.value zwraca NULL?

Bo value to rekord z czterema typowanymi podpolami, string_value, int_value, float_value i double_value, a wypełnione jest tylko to pasujące do typu parametru. Jeśli czytasz value.int_value dla parametru zapisanego jako string, dostajesz NULL bez błędu. Dopasuj podpole do faktycznego typu parametru.

Jaka jest różnica między tabelami events a events_intraday?

events_YYYYMMDD to sfinalizowana tabela dzienna, a events_intraday_YYYYMMDD to ciągle aktualizowana tabela tego samego dnia, która istnieje tylko, jeśli włączyłeś eksport streamingowy. Tabela intraday jest prowizoryczna i zostaje zastąpiona tabelą dzienną, gdy dzień się zamknie, więc odpytanie obu naraz liczy bieżący dzień podwójnie.

Tabele GA4 w BigQuery są partycjonowane czy dzielone?

Są dzielone (sharded), czyli jedna fizyczna tabela na dzień z sufiksem daty, a nie pojedyncza tabela partycjonowana. Żeby wydajnie odpytać zakres dat, używasz tabeli z symbolem wieloznacznym (events_*) i filtrujesz po _TABLE_SUFFIX, co ogranicza, ile tabel dziennych skanuje BigQuery, i trzyma koszty zapytań w ryzach.

event_timestamp jest w sekundach czy milisekundach?

W żadnym, jest w mikrosekundach od epoki Uniksa. Żeby zamienić go na czytelny znacznik czasu, użyj TIMESTAMP_MICROS(event_timestamp) albo podziel przez 1 000 000, jeśli potrzebujesz sekund. Traktowanie go jak milisekund to częsty błąd, który wrzuca Twoje zdarzenia tysiące lat w przyszłość.

Toniesz w nullach i podwójnych liczeniach?

Ustawiam eksport GA4 do BigQuery, bazowe zapytania i modele danych, żeby Twoje surowe dane zdarzeń były naprawdę użyteczne, a nie ścianą zagnieżdżonych pól. Zobacz, jak to działa.

Zobacz usługi analityczne
Piotr Litwa

Piotr Litwa

GTM & Analytics Specialist

Piotr buduje pipeline'y danych GA4 i BigQuery dla firm w całej Europie, zamieniając surowe eksporty zdarzeń w raportowanie, któremu można ufać. 120+ klientów, 10+ lat, zero ściemy.