Spis treści
- Jak GA4 definiuje sesję i jak przenieść tę definicję do SQL
- Skąd brać źródło ruchu w eksporcie GA4
- Tabela sesji w SQL: jedna sesja, jeden wiersz
- Sesje z zaangażowaniem i współczynnik zaangażowania
- Jak odtworzyć domyślne grupowanie kanałów w SQL
- Jak odtworzyć raport pozyskania ruchu krok po kroku
- Dlaczego liczby nie zgadzają się z interfejsem
- Najczęściej zadawane pytania
Sesje GA4 w BigQuery odtwarzasz w SQL, grupując zdarzenia według pary user_pseudo_id i ga_session_id, a źródło ruchu sesji pobierasz z rekordu session_traffic_source_last_click, który od 2024 roku odzwierciedla atrybucję sesji z interfejsu. Z takiej tabeli – jedna sesja, jeden wiersz – policzysz sesje z zaangażowaniem, współczynnik zaangażowania, strony docelowe, przychód i domyślne grupy kanałów. W tym poradniku budujemy ją krok po kroku i wyjaśniamy, dlaczego wynik nigdy nie będzie identyczny z GA4, ale może być bardzo bliski.
Najważniejsze w skrócie
- Identyfikator sesji to połączenie
user_pseudo_idi parametruga_session_id; samga_session_idnie jest unikalny. - Sesja z zaangażowaniem ma parametr
session_engagedrówny 1 na co najmniej jednym zdarzeniu. - Do odtworzenia wymiaru „Źródło/medium sesji” używaj rekordu
session_traffic_source_last_click.cross_channel_campaign, a nie pierwszego pozyskania ztraffic_source. - Domyślne grupowanie kanałów musisz odtworzyć instrukcją CASE; uproszczona wersja pokrywa typowy sklep, ale nie rozpozna wszystkich typów kampanii Google Ads.
- Różnice rzędu kilku procent względem interfejsu są normalne i wynikają głównie z szacowania liczby unikalnych wartości, modelowania i progów danych.
Jak GA4 definiuje sesję i jak przenieść tę definicję do SQL
W GA4 sesja zaczyna się zdarzeniem session_start i kończy po okresie bezczynności, domyślnie 30 minut. Każde zdarzenie w sesji dostaje parametr ga_session_id (znacznik czasu rozpoczęcia sesji w sekundach) i ga_session_number (numer kolejnej sesji użytkownika). Ponieważ dwóch użytkowników może rozpocząć sesję w tej samej sekundzie, unikalny klucz tworzysz dopiero z identyfikatorem użytkownika – szerzej o samym pojęciu piszemy w słowniku przy haśle sesja.
Trzy sytuacje wymagają decyzji przy budowie tabeli:
- Sesja przez północ. Zdarzenia jednej sesji trafiają wtedy do dwóch tabel dziennych. Jeśli policzysz sesje osobno dla każdego dnia, ta sama sesja pojawi się dwa razy. W tabeli sesji przypisz ją do daty rozpoczęcia.
- Zdarzenia bez zgody. W trybie zaawansowanym Consent Mode GA4 wysyła pingi bez plików cookie. W eksporcie zwykle nie mają one
user_pseudo_id, więc nie złożysz z nich sesji. Interfejs uzupełnia tę lukę modelowaniem, którego w BigQuery nie ma. - Zakres dat. Sesja rozpoczęta 31 października o 23:50 kończy się w tabeli z 1 listopada. Pobieraj dane z jednodniowym zapasem po obu stronach zakresu, a filtruj dopiero gotową tabelę sesji.
Jeśli nie masz jeszcze danych w hurtowni, zacznij od przewodnika, jak połączyć GA4 z BigQuery, a podstawowe techniki wyciągania parametrów znajdziesz w zestawie 10 zapytań do eksportu GA4.
Skąd brać źródło ruchu w eksporcie GA4
Eksport zawiera kilka rekordów ze źródłem ruchu i każdy odpowiada na inne pytanie. Najczęstszy błąd to użycie traffic_source do raportu sesji – ten rekord opisuje pierwsze pozyskanie użytkownika i nie zmienia się przy kolejnych wizytach.
| Rekord | Zakres | Co zawiera | Kiedy używać |
|---|---|---|---|
traffic_source |
Użytkownik (pierwsza wizyta) | Źródło, medium i kampania, które pozyskały użytkownika | Analizy kohort i wartości klienta według kanału pozyskania |
collected_traffic_source |
Zdarzenie | Surowe wartości UTM, gclid, dclid, srsltid zebrane z adresu |
Diagnostyka tagowania, własne modele atrybucji |
session_traffic_source_last_click |
Sesja | Atrybucja sesji według ostatniego kliknięcia: kampanie UTM, Google Ads, kampanie wielokanałowe i platformy Google Marketing Platform | Odtwarzanie raportu pozyskania ruchu |
parametry source, medium, campaign |
Zdarzenie | Wartości zapisywane przy zdarzeniach w starszych danych | Tylko w tabelach sprzed wprowadzenia nowych rekordów |
Rekord session_traffic_source_last_click ma kilka podrekordów. manual_campaign przechowuje kampanie opisane parametrami UTM, google_ads_campaign – nazwę konta, kampanii i grupy reklam z Google Ads, a cross_channel_campaign – wynik atrybucji między tymi kontekstami, czyli najbliższy odpowiednik wymiaru sesji z interfejsu. Dzięki niemu kliknięcia z autotagowaniem i identyfikatorem GCLID nie lądują w ruchu bezpośrednim ani w google / organic, co było zmorą starszych zapytań. Różnicę między autotagowaniem a ręcznymi UTM-ami opisujemy w tekście o autotagowaniu Google Ads a UTM.
W praktyce rekord collected_traffic_source pokazuje więcej ruchu bezpośredniego niż interfejs. Wynika to z tego, że GA4 w raportach sesji przypisuje wizytę bez źródła do wcześniejszego źródła niebezpośredniego tego użytkownika, a surowe dane z adresu takiego przeniesienia nie zawierają.
Tabela sesji w SQL: jedna sesja, jeden wiersz
Poniższe zapytanie buduje tabelę sesji z najważniejszymi atrybutami. Wartości, które mogą się zmieniać w trakcie sesji (strona docelowa, źródło), bierzemy z pierwszego zdarzenia, w którym są wypełnione.
WITH zdarzenia AS (
SELECT
PARSE_DATE('%Y%m%d', event_date) AS data,
user_pseudo_id,
CONCAT(user_pseudo_id, '.', CAST(
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id')
AS STRING)) AS sesja_id,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_number') AS numer_sesji,
event_name,
event_timestamp,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS strona,
(SELECT COALESCE(value.string_value, CAST(value.int_value AS STRING))
FROM UNNEST(event_params) WHERE key = 'session_engaged') AS zaangazowana,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'engagement_time_msec') AS czas_ms,
device.category AS urzadzenie,
session_traffic_source_last_click.cross_channel_campaign.source AS zrodlo,
session_traffic_source_last_click.cross_channel_campaign.medium AS medium,
session_traffic_source_last_click.cross_channel_campaign.campaign_name AS kampania,
ecommerce.transaction_id,
ecommerce.purchase_revenue
FROM `twoj-projekt.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260930' AND '20261027'
)
SELECT
sesja_id,
MIN(data) AS data,
ANY_VALUE(user_pseudo_id) AS user_pseudo_id,
MAX(numer_sesji) AS numer_sesji,
ARRAY_AGG(IF(event_name = 'page_view', REGEXP_REPLACE(strona, r'\?.*$', ''), NULL)
IGNORE NULLS ORDER BY event_timestamp LIMIT 1)[SAFE_OFFSET(0)] AS strona_docelowa,
ANY_VALUE(urzadzenie) AS urzadzenie,
ARRAY_AGG(zrodlo IGNORE NULLS ORDER BY event_timestamp LIMIT 1)[SAFE_OFFSET(0)] AS zrodlo,
ARRAY_AGG(medium IGNORE NULLS ORDER BY event_timestamp LIMIT 1)[SAFE_OFFSET(0)] AS medium,
ARRAY_AGG(kampania IGNORE NULLS ORDER BY event_timestamp LIMIT 1)[SAFE_OFFSET(0)] AS kampania,
MAX(IF(zaangazowana = '1', 1, 0)) AS zaangazowana,
ROUND(SUM(IFNULL(czas_ms, 0)) / 1000, 1) AS czas_zaangazowania_s,
COUNTIF(event_name = 'page_view') AS odslony,
COUNT(DISTINCT IF(event_name = 'purchase', transaction_id, NULL)) AS transakcje,
SUM(IF(event_name = 'purchase', purchase_revenue, 0)) AS przychod
FROM zdarzenia
WHERE sesja_id IS NOT NULL
GROUP BY sesja_id;
Zakres tabel obejmuje dzień przed i dzień po interesującym Cię miesiącu, żeby sesje przez północ były kompletne. Wynik zapisz jako tabelę, a w raportach filtruj ją po kolumnie data. Jak zrobić to tanio, z partycjonowaniem i dopisywaniem tylko nowych dni, opisujemy w artykule o kosztach BigQuery i tabelach pośrednich.
Sesje z zaangażowaniem i współczynnik zaangażowania
GA4 uznaje sesję za zaangażowaną, gdy trwa dłużej niż 10 sekund (próg można wydłużyć w ustawieniach strumienia danych), zawiera zdarzenie kluczowe albo co najmniej dwie odsłony. Nie musisz odtwarzać tych warunków samodzielnie – GA4 oznacza takie sesje parametrem session_engaged o wartości 1. W zależności od zdarzenia wartość bywa zapisana jako tekst albo liczba, stąd COALESCE w zapytaniu powyżej.
Na gotowej tabeli sesji metryki interfejsu policzysz jednym zapytaniem:
SELECT
data,
COUNT(*) AS sesje,
SUM(zaangazowana) AS sesje_z_zaangazowaniem,
ROUND(100 * SAFE_DIVIDE(SUM(zaangazowana), COUNT(*)), 1) AS wspolczynnik_zaangazowania_proc,
ROUND(SAFE_DIVIDE(SUM(czas_zaangazowania_s), SUM(zaangazowana)), 1) AS sredni_czas_na_sesje_zaangazowana_s
FROM `twoj-projekt.raporty.sesje`
WHERE data BETWEEN '2026-10-01' AND '2026-10-26'
GROUP BY data
ORDER BY data;
Współczynnik odrzuceń w GA4 to po prostu dopełnienie współczynnika zaangażowania do 100 procent, więc nie musisz liczyć go osobno.
Jak odtworzyć domyślne grupowanie kanałów w SQL
Eksport nie zawiera gotowej kolumny z domyślną grupą kanałów, więc trzeba ją zbudować z reguł opartych na źródle i medium. Pełne reguły GA4 korzystają z długich list serwisów (wyszukiwarki, serwisy społecznościowe, serwisy wideo), dlatego poniższa wersja jest uproszczona, ale pokrywa typowy ruch polskiego sklepu.
SELECT
*,
CASE
WHEN zrodlo = '(direct)' AND medium IN ('(none)', '(not set)') THEN 'Direct'
WHEN medium = 'ai-assistant'
OR REGEXP_CONTAINS(LOWER(zrodlo), r'chatgpt|openai|gemini\.google|claude|perplexity|copilot')
AND NOT REGEXP_CONTAINS(medium, r'^(.*cp.*|ppc|retargeting|paid.*)$') THEN 'AI Assistant'
WHEN REGEXP_CONTAINS(medium, r'^(.*cp.*|ppc|retargeting|paid.*)$')
AND REGEXP_CONTAINS(LOWER(zrodlo), r'google|bing|yahoo|duckduckgo|ecosia') THEN 'Paid Search'
WHEN REGEXP_CONTAINS(medium, r'^(.*cp.*|ppc|retargeting|paid.*)$')
AND REGEXP_CONTAINS(LOWER(zrodlo), r'facebook|instagram|^fb$|^ig$|meta|tiktok|linkedin|pinterest|threads') THEN 'Paid Social'
WHEN medium IN ('display', 'banner', 'expandable', 'interstitial', 'cpm') THEN 'Display'
WHEN medium = 'organic'
OR REGEXP_CONTAINS(LOWER(zrodlo), r'google|bing|yahoo|duckduckgo|ecosia') THEN 'Organic Search'
WHEN medium IN ('social', 'social-network', 'social-media', 'sm', 'social network', 'social media')
OR REGEXP_CONTAINS(LOWER(zrodlo), r'facebook|instagram|linkedin|tiktok|pinterest|threads') THEN 'Organic Social'
WHEN REGEXP_CONTAINS(LOWER(CONCAT(zrodlo, medium)), r'email|e-mail|e_mail|newsletter') THEN 'Email'
WHEN medium = 'affiliate' THEN 'Affiliates'
WHEN medium = 'sms' OR zrodlo = 'sms' THEN 'SMS'
WHEN medium = 'referral' THEN 'Referral'
ELSE 'Unassigned'
END AS kanal
FROM `twoj-projekt.raporty.sesje`;
Trzy uwagi do tej reguły. Po pierwsze, kolejność warunków ma znaczenie: płatne reguły muszą stać przed organicznymi, bo google występuje w obu. Po drugie, od 13 maja 2026 GA4 ma osobny kanał „AI Assistant” dla ruchu z ChatGPT, Gemini, Claude i podobnych usług, a takim sesjom przypisuje medium ai-assistant; jeśli ta wartość pojawia się w Twoich tabelach, reguła działa od razu, a w starszych danych rozpoznasz ten ruch po źródle. Jak raportować ten kanał, wyjaśniamy w tekście o ruchu z ChatGPT, Gemini i Claude w GA4. Po trzecie, uproszczona reguła nie odróżni kampanii Performance Max i Demand Gen od kampanii w sieci wyszukiwania – jeśli nazwy takich kampanii mają stały człon, dodaj warunek na kolumnie kampania.
Reguła jest tak dobra, jak opis ruchu. Jeśli w danych pojawiają się warianty Facebook, fb i facebook.com, uporządkuj je u źródła według zasad z artykułu o parametrach UTM w GA4.
Jak odtworzyć raport pozyskania ruchu krok po kroku
Mając tabelę sesji i regułę kanałów, możesz zbudować odpowiednik raportu „Pozyskanie ruchu” z GA4 i porównać go z interfejsem.
- Zbuduj tabelę sesji z zapasem jednego dnia po obu stronach zakresu i zapisz ją w osobnym zbiorze danych, np.
raporty. - Dodaj kolumnę kanału jako widok na tabeli sesji, żeby zmiana reguły nie wymagała przeliczania całej historii.
- Policz metryki według kanału: sesje, sesje z zaangażowaniem, współczynnik zaangażowania, transakcje i przychód.
- Porównaj z GA4 dla tego samego zakresu, ustawiając w interfejsie wymiar „Domyślna grupa kanałów sesji”. Zacznij od sumy sesji, potem przejdź do kanałów.
- Zbadaj kanały z największą różnicą. Rozbieżność w Direct zwykle oznacza problem z przypisaniem sesji, a w Unassigned – wartości medium spoza reguł.
- Zapisz wynik porównania razem z datą i wersją reguły, żeby przy kolejnej zmianie wiedzieć, od czego zaczynałeś.
Typowy wynik dobrze skonfigurowanej usługi to różnica łącznej liczby sesji w granicach kilku procent i podobny rozkład kanałów. Jeśli w GA4 widzisz dużo „(not set)”, a w BigQuery dużo pustych źródeł, przyczyny są te same – omawiamy je w poradniku o „(not set)” i ruchu bezpośrednim w GA4.
Dlaczego liczby nie zgadzają się z interfejsem
Interfejs GA4 liczy sesje i użytkowników algorytmem szacującym liczbę unikalnych wartości, który przy dużych wolumenach daje niewielki błąd statystyczny. BigQuery liczy dokładnie. Do tego dochodzą dane modelowane w raportach, progi ukrywające małe wiersze i różnice w przypisaniu sesji przez północ.
Osobną kwestią jest model atrybucji. Rekord sesji opisuje ostatnie kliknięcie w obrębie sesji, a raporty konwersji w GA4 domyślnie używają modelu opartego na danych. Jeśli porównujesz przychód według kanału z raportem atrybucji, różnice mogą być duże i są zamierzone – tłumaczymy je w artykule o atrybucji opartej na danych i ostatnim kliknięciu. Pojęcie atrybucji i jej modele znajdziesz też w słowniku.
Gotową tabelę sesji podłączysz do raportu w Looker Studio bez kosztownego skanowania surowych zdarzeń – przykładowy układ pokazujemy w poradniku o dashboardzie e-commerce w Looker Studio. Spójne nazwy kampanii ułatwią potem łączenie sesji z kosztami; zasady opisuje konwencja UTM dla zespołu. Więcej zapytań i technik znajdziesz w podtemacie BigQuery i SQL.
Najczęściej zadawane pytania
Jak policzyć sesje GA4 w BigQuery?
Połącz user_pseudo_id z wartością parametru ga_session_id i policz unikalne kombinacje. Najlepiej zbuduj tabelę, w której jedna sesja to jeden wiersz, i przypisz każdą sesję do daty jej rozpoczęcia. Dzięki temu sesje trwające przez północ nie zostaną policzone dwukrotnie, a metryki dzienne będą spójne.
Którego pola użyć do źródła i medium sesji?
Do odtworzenia wymiaru „Źródło/medium sesji” najlepiej nadaje się rekord session_traffic_source_last_click.cross_channel_campaign. Uwzględnia on zarówno kampanie opisane UTM-ami, jak i kliknięcia z Google Ads z autotagowaniem. Rekord traffic_source opisuje pierwsze pozyskanie użytkownika i nie nadaje się do raportów sesji.
Dlaczego w BigQuery mam więcej ruchu bezpośredniego niż w GA4?
Najczęściej dlatego, że źródło pobierasz z surowych danych zdarzeń, np. collected_traffic_source. Interfejs GA4 przypisuje sesje bez źródła do wcześniejszego źródła niebezpośredniego użytkownika, a surowe dane takiego przeniesienia nie zawierają. Rekord session_traffic_source_last_click daje wyniki znacznie bliższe raportom.
Jak sprawdzić, czy sesja była zaangażowana?
GA4 oznacza takie sesje parametrem session_engaged o wartości 1 na zdarzeniach w sesji. Wystarczy sprawdzić, czy w sesji występuje choć jedno zdarzenie z tą wartością. Wartość może być zapisana jako tekst lub liczba, dlatego bezpieczniej sprowadzić ją do tekstu funkcją COALESCE przed porównaniem.
Czy mogę odtworzyć domyślne grupowanie kanałów GA4 dokładnie?
W przybliżeniu tak, dokładnie zwykle nie. Reguły GA4 korzystają z rozbudowanych list serwisów i danych z połączonych kont reklamowych, których eksport nie zawiera w całości. Instrukcja CASE oparta na źródle i medium pokrywa jednak większość ruchu, a pozostałe różnice możesz ograniczyć regułami opartymi na nazwach kampanii.