Eksport GA4 do BigQuery to w 2026 roku nie jest już opcja dla „zaawansowanych”, tylko standard dla każdego, kto chce analizować dane dłużej niż 14 miesięcy, bez progów thresholdingu i bez sampling w eksploracjach. Darmowy tier pokrywa 10 GB storage i 1 TB zapytań miesięcznie – dla średniego sklepu e-commerce wystarcza to na 2-3 lata danych bez kosztów. Ten artykuł jest kolekcją zapytań SQL, które pisaliśmy w boju – w audytach, analizach post mortem, raportach dla zarządu i w pracy z atrybucją multi-touch.
Nie znajdziesz tu „witaj w BigQuery” ani tłumaczenia, czym jest user_pseudo_id. Zakładamy, że link GA4 ↔ BigQuery działa, a tabele events_YYYYMMDD pojawiają się codziennie. Zaczynamy od tego, jak je analizować, by odpowiadać na realne pytania biznesowe: który kanał faktycznie sprowadza kupujących, czemu ścieżki w panelu nie zgadzają się z raportem, jak wyłowić ruch z LLM oraz jak policzyć LTV w 4 linijkach SQL zamiast 4 godzin w Excelu.
Artykuł jest częścią klastra analityka marketingowa 2026. Jeżeli konfigurację właśnie robisz od zera, zacznij od tekstu o GA4 zaawansowanym i sąsiedniego o zdarzeniach GA4. Architekturę GTM pod zaawansowany stack rozwijamy w GTM od zera do produkcji.
W skrócie
- Darmowy tier BigQuery: 10 GB storage + 1 TB zapytań/miesiąc. Dla 95% sklepów e-commerce w Polsce wystarcza bez kosztów.
- Tabela
events_YYYYMMDDjest partycjonowana po dacie, ale nie klastrowana — zawsze używaj_TABLE_SUFFIXBETWEEN w WHERE, inaczej skanujesz całą historię i zużywasz quota. - Typowe zapytania na GA4 w BigQuery wymagają UNNEST na
event_paramsiuser_properties– bez tego parametry są niedostępne. - Koszty rosną liniowo ze skanowanymi danymi (5 USD / TB). Klient z 2 mln zdarzeń dziennie i 20 zapytaniami/dzień płaci ~30-80 USD/miesiąc – akceptowalnie.
- Analizy, których nie da się zrobić w panelu GA4: retencja kohortowa > 14 mies., path exploration > 10 kroków, atrybucja custom, LTV, analiza zdarzeń bez thresholdingu.
Spis treści
- Po co w ogóle BigQuery dla marketingu
- Schemat tabeli events_YYYYMMDD – co gdzie leży
- Koszty zapytań i jak ich nie generować za dużo
- Podstawowe zapytania: DAU, MAU, top strony, zdarzenia
- Zapytania ecommerce: przychód, AOV, top produktów
- Atrybucja custom: first-click, last-click, position-based
- LTV, retencja kohortowa, churn
- Identyfikacja ruchu z AI – ChatGPT, Perplexity, AI Overviews
- Lejek analysis bez ograniczeń GA4 Explorations
- Zapytania audytowe: duplikaty, luki, anomalie
- Optymalizacja zapytań i monitoring kosztów
- Połączenie BigQuery z Looker Studio i Google Sheets
- FAQ — najczęstsze pytania
- Co dalej
Po co w ogóle BigQuery dla marketingu
Panel GA4 jest zaprojektowany pod marketera i raporty operacyjne. Nie pod analityka, nie pod CFO, nie pod analizy historyczne. Sześć konkretnych przypadków, w których panel GA4 Ci nie wystarczy i wchodzisz w BigQuery:
- Retencja ponad 14 miesięcy. GA4 standard ma twardy limit 14 miesięcy. BigQuery trzyma dane tak długo, jak płacisz za storage — czyli zwykle latami.
- Thresholding. W raportach GA4 Google ukrywa wartości dla małych grup (≤10-50 użytkowników). BigQuery zwraca surowe dane bez ukrywania.
- Sampling w Explorations. Po przekroczeniu 10 mln zdarzeń (standard) GA4 próbkuje eksploracje. BigQuery nigdy.
- Custom attribution. GA4 oferuje tylko kilka gotowych modeli. W BigQuery napiszesz własny model — np. „markov chain” lub „time-decay z wagami branży”.
- Połączenie z danymi CRM. W BigQuery zjoinujesz GA4 z tabelą klientów z Salesforce, fakturami z ERP, ticketami z Zendesk. W panelu GA4 to niemożliwe.
- Analiza rok-do-roku z pełną granularnością. Panel agreguje wszystko po miesiącach. BigQuery pozwala zejść do poziomu minuty sprzed 24 miesięcy.
Alternatywą jest zakup GA4 360 (od ~150 tys. USD/rok), który znosi większość ograniczeń, ale dla 99% polskich sklepów BigQuery wystarczy, a koszt miesięczny jest o trzy rzędy wielkości niższy.
Kiedy BigQuery to przesada
Uczciwie: dla blogów i małych sklepów z < 50 tys. sesji miesięcznie BigQuery bywa przesadą. Panel GA4 wystarczy, a konfiguracja BigQuery to 2-4 godziny dev time. Próg, poniżej którego inwestycja się nie zwraca, to zwykle 100-200 tys. sesji/miesiąc. Powyżej – każdy miesiąc bez BigQuery to tracone dane i utracona możliwość analiz. Zagadnienie to omawiamy szerzej w zdarzenia GA4.
Schemat tabeli events_YYYYMMDD – co gdzie leży
GA4 eksportuje jedną tabelę dziennie, nazwaną events_YYYYMMDD, plus aktualizowaną events_intraday_YYYYMMDD dla bieżącego dnia. Schemat jest płaski na najwyższym poziomie, ale trzy kolumny są typu REPEATED RECORD – i właśnie one wymagają UNNEST.
Kluczowe kolumny
| Kolumna | Typ | Zawartość |
|---|---|---|
event_date | STRING | YYYYMMDD, lokalny czas properta |
event_timestamp | INTEGER | Mikrosekundy epoch UTC |
event_name | STRING | Nazwa zdarzenia (page_view, purchase itd.) |
event_params | REPEATED RECORD | Parametry zdarzenia jako array |
user_pseudo_id | STRING | Anonimowy identyfikator cookie |
user_id | STRING | Własny ID z login (opcjonalny) |
user_properties | REPEATED RECORD | Właściwości użytkownika |
device | RECORD | category, operating_system, browser |
geo | RECORD | country, region, city |
traffic_source | RECORD | first-touch source (attribution_source dla każdego event) |
collected_traffic_source | RECORD | Session-level source (nowsze, dokładniejsze) |
items | REPEATED RECORD | Produkty w zdarzeniach ecommerce |
Pułapka 1: event_params są kluczowe, ale ukryte
Wszystkie parametry zdarzenia – page_location, ga_session_id, page_title, value, custom parametry – żyją w event_params jako array. Nie odnajdziesz ich jako zwykłych kolumn. Przykład wydobycia page_location:
SELECT
event_timestamp,
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 `project.analytics_123.events_20260301`
WHERE event_name = 'page_view'
LIMIT 100;Pułapka 2: typ wartości zależy od parametru
Każdy parametr w event_params ma cztery możliwe pola wartości: string_value, int_value, float_value, double_value. Trzy z nich zwykle są NULL. Jeśli zapytasz o int_value z parametru tekstowego, dostaniesz NULL zamiast błędu — i przez to całe analizy wyglądają puste. Przykład: value dla purchase to double_value, nie int_value. Więcej o tym zagadnieniu znajdziesz w GA4 zaawansowanego.
Pułapka 3: _TABLE_SUFFIX czy FROM single?
Zawsze filtruj zakres dat przez _TABLE_SUFFIX:
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'Bez tego BigQuery skanuje wszystkie tabele eksportu – także te sprzed 2 lat. Dla average sklepu to różnica między 5 GB a 500 GB skanu (i 0,02 USD vs 2,50 USD za jedno zapytanie).
Koszty zapytań i jak ich nie generować za dużo
BigQuery rozlicza się za skanowane dane w query, nie za storage. 1 TB skanu = 5 USD. W darmowym tierze masz 1 TB/miesiąc gratis. To wystarcza, jeśli zapytasz mądrze – nie wystarcza, jeśli budujesz dashboard odświeżany co godzinę. Praktyczne wskazówki znajdziesz w GTM od zera do produkcji.
Cztery zasady taniego analityka
- Zawsze _TABLE_SUFFIX w WHERE. Brak filtra po dacie = skan całej historii.
- Select tylko potrzebne kolumny.
SELECT *skanuje wszystkie kolumny w tabeli. UNNEST event_params jest najdroższą operacją – używaj precyzyjnie. - Buduj materialized views dla dashboardów. Raz zmaterializowany widok liczy koszt raz, a nie każde odświeżenie.
- Ustaw budżet alert. Billing → Budżety & alerts → alert przy 10/50/90% miesięcznego limitu.
Optymalizacja: partycjonowanie i klastrowanie
Tabele GA4 są partycjonowane po dacie (_TABLE_SUFFIX), ale nie klastrowane. Dla dużych wolumenów (> 10 mln zdarzeń dziennie) warto stworzyć własną tabelę pochodną z klastrowaniem po event_name i user_pseudo_id:
CREATE TABLE `project.mart.events_clustered`
PARTITION BY DATE(TIMESTAMP_MICROS(event_timestamp))
CLUSTER BY event_name, user_pseudo_id
AS
SELECT * FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260301';Efekt: zapytania filtrujące po event_name = 'purchase' redukują skan z 100 GB do 3-5 GB — 20-30× taniej.
Podstawowe zapytania: DAU, MAU, top strony, zdarzenia
Zanim przejdziemy do zaawansowanej atrybucji, zbiór dziesięciu zapytań, które używasz codziennie.
DAU, WAU, MAU
-- Daily Active Users
SELECT
PARSE_DATE('%Y%m%d', event_date) AS day,
COUNT(DISTINCT user_pseudo_id) AS dau
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
GROUP BY day
ORDER BY day;Dla MAU zamień GROUP BY na DATE_TRUNC(day, MONTH), dla tygodnia – DATE_TRUNC(day, WEEK).
Top 20 stron (page_views)
SELECT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_location') AS page,
COUNT(*) AS views,
COUNT(DISTINCT user_pseudo_id) AS uu
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'page_view'
GROUP BY page
ORDER BY views DESC
LIMIT 20;Sesje i czas trwania
-- Liczba sesji w okresie
SELECT
COUNT(DISTINCT CONCAT(user_pseudo_id,
CAST((SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS STRING))) AS sessions
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228';Session ID sam nie jest unikalny globalnie – dopiero w kombinacji z user_pseudo_id. Dwóch użytkowników może mieć ten sam session_id w tym samym momencie.
Top eventy według liczby
SELECT
event_name,
COUNT(*) AS events,
COUNT(DISTINCT user_pseudo_id) AS users
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
GROUP BY event_name
ORDER BY events DESC;Używaj tego zapytania co tydzień do audytu. Jeśli widzisz event, którego nie rozpoznajesz, albo nagły wzrost/spadek eventu > 30%, wskazuje to albo na błąd wdrożenia, albo na realną zmianę w produkcie. Oba warte sprawdzenia. Szczegóły opisujemy w analityka marketingowa 2026.
Traffic source na poziomie sesji
SELECT
collected_traffic_source.manual_source AS source,
collected_traffic_source.manual_medium AS medium,
collected_traffic_source.manual_campaign_name AS campaign,
COUNT(DISTINCT CONCAT(user_pseudo_id,
CAST((SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS STRING))) AS sessions
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'session_start'
GROUP BY source, medium, campaign
ORDER BY sessions DESC;Używaj collected_traffic_source, nie traffic_source. Pierwsze to source dla tej sesji, drugie to first-touch source z pierwszej sesji użytkownika — różnica kluczowa dla raportów kampanijnych.
Zapytania ecommerce: przychód, AOV, top produktów
Dla sklepów BigQuery wygrywa z panelem, gdy potrzebujesz krzyżowych analiz produktowo-marketingowych, LTV per segment i analiz koszykowych.
Przychód, liczba transakcji, AOV
SELECT
PARSE_DATE('%Y%m%d', event_date) AS day,
COUNT(DISTINCT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'transaction_id')) AS transactions,
SUM((SELECT value.double_value FROM UNNEST(event_params)
WHERE key = 'value')) AS revenue,
SAFE_DIVIDE(
SUM((SELECT value.double_value FROM UNNEST(event_params)
WHERE key = 'value')),
COUNT(DISTINCT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'transaction_id'))
) AS aov
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'purchase'
GROUP BY day
ORDER BY day;Top 20 produktów
SELECT
item.item_name,
item.item_id,
item.item_category,
COUNT(*) AS times_sold,
SUM(item.quantity) AS units,
SUM(item.price * item.quantity) AS gross_revenue
FROM `project.analytics_123.events_*`,
UNNEST(items) AS item
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'purchase'
GROUP BY item.item_name, item.item_id, item.item_category
ORDER BY gross_revenue DESC
LIMIT 20;Market basket analysis — produkty kupowane razem
WITH orders AS (
SELECT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'transaction_id') AS transaction_id,
ARRAY_AGG(item.item_name ORDER BY item.item_name) AS items_bought
FROM `project.analytics_123.events_*`,
UNNEST(items) AS item
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'purchase'
GROUP BY transaction_id
)
SELECT
items_bought,
COUNT(*) AS orders_count
FROM orders
WHERE ARRAY_LENGTH(items_bought) >= 2
GROUP BY items_bought
ORDER BY orders_count DESC
LIMIT 20;Analiza pokaże, które produkty klienci najczęściej kupują razem – to dane pod cross-sell w mailingu, rekomendacje i bundle.
Konwersja per kanał
WITH sessions AS (
SELECT
CONCAT(user_pseudo_id,
CAST((SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS STRING)) AS session_id,
MAX(IF(event_name = 'purchase', 1, 0)) AS had_purchase,
ANY_VALUE(collected_traffic_source.manual_source) AS source,
ANY_VALUE(collected_traffic_source.manual_medium) AS medium
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
GROUP BY session_id
)
SELECT
source, medium,
COUNT(*) AS sessions,
SUM(had_purchase) AS conversions,
SAFE_DIVIDE(SUM(had_purchase), COUNT(*)) AS cr
FROM sessions
GROUP BY source, medium
HAVING sessions >= 100
ORDER BY cr DESC;Atrybucja custom: first-click, last-click, position-based, time-decay
W panelu GA4 masz kilka gotowych modeli atrybucji. W BigQuery — dowolny. Tutaj trzy zapytania, które pokrywają 90% przypadków enterprise.
Przygotowanie: tabela touchpointów
WITH touchpoints AS (
SELECT
user_pseudo_id,
event_timestamp,
collected_traffic_source.manual_source AS source,
collected_traffic_source.manual_medium AS medium,
CONCAT(collected_traffic_source.manual_source, ' / ',
collected_traffic_source.manual_medium) AS channel,
MAX(IF(event_name = 'purchase',
(SELECT value.double_value FROM UNNEST(event_params)
WHERE key = 'value'), 0))
OVER (PARTITION BY user_pseudo_id) AS user_revenue
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260228'
AND event_name IN ('session_start', 'purchase')
)Last-click attribution
SELECT
channel,
SUM(user_revenue) AS revenue_last_click
FROM (
SELECT
user_pseudo_id,
LAST_VALUE(channel IGNORE NULLS) OVER (
PARTITION BY user_pseudo_id
ORDER BY event_timestamp
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS channel,
user_revenue,
ROW_NUMBER() OVER (PARTITION BY user_pseudo_id ORDER BY event_timestamp DESC) AS rn
FROM touchpoints
WHERE source IS NOT NULL
)
WHERE rn = 1
GROUP BY channel
ORDER BY revenue_last_click DESC;Linear attribution
WITH user_paths AS (
SELECT
user_pseudo_id,
channel,
user_revenue,
COUNT(*) OVER (PARTITION BY user_pseudo_id) AS path_length
FROM touchpoints
WHERE source IS NOT NULL AND user_revenue > 0
)
SELECT
channel,
SUM(user_revenue / path_length) AS revenue_linear
FROM user_paths
GROUP BY channel
ORDER BY revenue_linear DESC;Time-decay (waga 2^(-days_before_purchase/7))
WITH user_paths AS (
SELECT
user_pseudo_id,
channel,
event_timestamp,
user_revenue,
MAX(IF(event_name = 'purchase', event_timestamp, NULL))
OVER (PARTITION BY user_pseudo_id) AS purchase_ts
FROM touchpoints
),
weighted AS (
SELECT
channel,
user_revenue,
POW(2, -TIMESTAMP_DIFF(TIMESTAMP_MICROS(purchase_ts),
TIMESTAMP_MICROS(event_timestamp), DAY) / 7.0) AS weight
FROM user_paths
WHERE purchase_ts IS NOT NULL AND event_timestamp < purchase_ts
)
SELECT
channel,
SUM(user_revenue * weight / SUM(weight) OVER (PARTITION BY user_pseudo_id)) AS revenue_decay
FROM weighted
GROUP BY channel
ORDER BY revenue_decay DESC;Porównaj trzy modele obok siebie. Kanały, które wyglądają świetnie w last-click i słabo w linear, to zwykle kanały „zbierające konwersje” (brand, direct) – ktoś inny je rozgrzał. Szczegóły rozkładu wag omawiamy w pillarze analityki marketingowej 2026.
LTV, retencja kohortowa, churn
Analizy LTV w panelu GA4 są bardzo ograniczone – raport Life Cycle pokazuje agregaty. W BigQuery policzysz LTV per kohorta pozyskania, per kanał, per produkt, per segment.
LTV per kohorta pozyskania (miesiąc)
WITH users AS (
SELECT
user_pseudo_id,
DATE_TRUNC(MIN(DATE(TIMESTAMP_MICROS(event_timestamp))), MONTH) AS cohort_month
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260228'
GROUP BY user_pseudo_id
),
purchases AS (
SELECT
user_pseudo_id,
SUM((SELECT value.double_value FROM UNNEST(event_params)
WHERE key = 'value')) AS total_revenue
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260228'
AND event_name = 'purchase'
GROUP BY user_pseudo_id
)
SELECT
u.cohort_month,
COUNT(u.user_pseudo_id) AS users_in_cohort,
COUNTIF(p.total_revenue > 0) AS paying_users,
SAFE_DIVIDE(SUM(p.total_revenue), COUNT(u.user_pseudo_id)) AS avg_ltv_all,
SAFE_DIVIDE(SUM(p.total_revenue), COUNTIF(p.total_revenue > 0)) AS avg_ltv_paying
FROM users u
LEFT JOIN purchases p USING (user_pseudo_id)
GROUP BY cohort_month
ORDER BY cohort_month;Retencja kohortowa (rolling)
WITH first_visit AS (
SELECT
user_pseudo_id,
MIN(DATE(TIMESTAMP_MICROS(event_timestamp))) AS first_day
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260228'
GROUP BY user_pseudo_id
),
all_visits AS (
SELECT DISTINCT
user_pseudo_id,
DATE(TIMESTAMP_MICROS(event_timestamp)) AS visit_day
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260228'
)
SELECT
DATE_TRUNC(first_day, MONTH) AS cohort,
DATE_DIFF(visit_day, first_day, DAY) AS days_since,
COUNT(DISTINCT user_pseudo_id) AS retained
FROM first_visit
JOIN all_visits USING (user_pseudo_id)
WHERE DATE_DIFF(visit_day, first_day, DAY) IN (0, 1, 7, 14, 30, 60, 90, 180)
GROUP BY cohort, days_since
ORDER BY cohort, days_since;Prawdopodobieństwo kolejnego zakupu
WITH purchases_per_user AS (
SELECT
user_pseudo_id,
COUNT(*) AS purchase_count
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260228'
AND event_name = 'purchase'
GROUP BY user_pseudo_id
)
SELECT
purchase_count,
COUNT(*) AS users,
COUNT(*) / LAG(COUNT(*)) OVER (ORDER BY purchase_count) AS retention_to_next
FROM purchases_per_user
GROUP BY purchase_count
ORDER BY purchase_count;Tabela pokaże, ile użytkowników kupiło 1x, 2x, 3x, 4x… Kolumna retention_to_next to stosunek liczby „kupili N razy” do „kupili N-1 razy” – klasyczny repeat purchase rate. Dla zdrowych sklepów DTC wynosi 25-45%. Poniżej 15% – LTV jest ograniczona i trzeba rozwijać retencję.
Identyfikacja ruchu z AI – ChatGPT, Perplexity, AI Overviews
W 2026 LLM stały się zauważalnym źródłem ruchu (2-8% dla B2B informacyjnego). GA4 nie ma dla nich dedykowanego kanału – wpadają do Referral lub Direct. W BigQuery łatwo je wyłowisz po referrer pattern.
Sesje z LLM per miesiąc
WITH llm_sessions AS (
SELECT
DATE_TRUNC(PARSE_DATE('%Y%m%d', event_date), MONTH) AS month,
CASE
WHEN REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_referrer'),
r'chat.openai.com|chatgpt.com'
) THEN 'ChatGPT'
WHEN REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_referrer'),
r'perplexity.ai'
) THEN 'Perplexity'
WHEN REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_referrer'),
r'gemini.google.com|bard.google.com'
) THEN 'Gemini'
WHEN REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_referrer'),
r'claude.ai'
) THEN 'Claude'
WHEN REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_referrer'),
r'copilot.microsoft.com'
) THEN 'Copilot'
WHEN REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_location'),
r'udm=50'
) THEN 'Google AI Overviews'
ELSE NULL
END AS ai_source,
user_pseudo_id,
(SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS session_id
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260228'
AND event_name = 'session_start'
)
SELECT
month,
ai_source,
COUNT(DISTINCT CONCAT(user_pseudo_id, CAST(session_id AS STRING))) AS sessions
FROM llm_sessions
WHERE ai_source IS NOT NULL
GROUP BY month, ai_source
ORDER BY month DESC, sessions DESC;Które strony ściągają ruch z AI
SELECT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_location') AS page,
COUNT(DISTINCT user_pseudo_id) AS users_from_llm
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'page_view'
AND REGEXP_CONTAINS(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_referrer'),
r'chat.openai|perplexity|gemini.google|claude.ai|copilot.microsoft'
)
GROUP BY page
ORDER BY users_from_llm DESC
LIMIT 30;Artykuły, które są często cytowane przez LLM, zaczną wyłaniać się na szczycie. Monitoruj tę listę co kwartał — jest mocnym sygnałem, które materiały mają autorytet w modelach.
Lejek analysis bez ograniczeń GA4 Explorations
Panel GA4 Explorations ogranicza funnele do 10 kroków. W BigQuery nie ma limitu — liczysz 20, 50, dowolnie.
Lejek e-commerce (view_item → add_to_cart → begin_checkout → purchase)
WITH events AS (
SELECT
user_pseudo_id,
event_name,
event_timestamp
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name IN ('view_item', 'add_to_cart', 'begin_checkout', 'purchase')
),
funnel AS (
SELECT
user_pseudo_id,
MAX(IF(event_name = 'view_item', 1, 0)) AS step1,
MAX(IF(event_name = 'add_to_cart', 1, 0)) AS step2,
MAX(IF(event_name = 'begin_checkout', 1, 0)) AS step3,
MAX(IF(event_name = 'purchase', 1, 0)) AS step4
FROM events
GROUP BY user_pseudo_id
)
SELECT
SUM(step1) AS view_item,
SUM(step2) AS add_to_cart,
SUM(step3) AS begin_checkout,
SUM(step4) AS purchase,
SAFE_DIVIDE(SUM(step2), SUM(step1)) AS ctr_view_to_cart,
SAFE_DIVIDE(SUM(step3), SUM(step2)) AS ctr_cart_to_checkout,
SAFE_DIVIDE(SUM(step4), SUM(step3)) AS ctr_checkout_to_buy,
SAFE_DIVIDE(SUM(step4), SUM(step1)) AS overall
FROM funnel;Path analysis – najczęstsze sekwencje kliknięć
WITH ordered AS (
SELECT
user_pseudo_id,
event_timestamp,
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_location') AS page,
ROW_NUMBER() OVER (PARTITION BY user_pseudo_id ORDER BY event_timestamp) AS step
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'page_view'
)
SELECT
STRING_AGG(page, ' → ' ORDER BY step LIMIT 5) AS path_first_5,
COUNT(DISTINCT user_pseudo_id) AS users
FROM ordered
WHERE step <= 5
GROUP BY user_pseudo_id
ORDER BY users DESC
LIMIT 30;Zapytania audytowe: duplikaty, luki, anomalie
Wdrożenie GA4 rzadko jest idealne. Trzy zapytania, które uruchamiaj co miesiąc, żeby wyłapywać błędy zanim zepsują raporty kwartalne. Pełen obraz tematu znajdziesz w kompletnym przewodniku analityka marketingowa 2026.
Duplikaty transakcji
SELECT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'transaction_id') AS transaction_id,
COUNT(*) AS occurrences
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260201' AND '20260228'
AND event_name = 'purchase'
GROUP BY transaction_id
HAVING occurrences > 1
ORDER BY occurrences DESC;Jeśli widzisz transakcje raportowane > 1 raz, sprawdź: czy GTM wysyła purchase z thank-you-page, który użytkownicy odświeżają? Czy masz duplikaty przez dwa tagi?
Brakujące parametry purchase
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS day,
COUNT(*) AS total_purchases,
COUNTIF((SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'transaction_id') IS NULL) AS missing_txn_id,
COUNTIF((SELECT value.double_value FROM UNNEST(event_params)
WHERE key = 'value') IS NULL) AS missing_value,
COUNTIF(ARRAY_LENGTH(items) = 0) AS missing_items
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260228'
AND event_name = 'purchase'
GROUP BY day
ORDER BY day DESC;Anomalie w eventach (nowe lub nagłe spadki)
WITH daily AS (
SELECT
event_name,
DATE(TIMESTAMP_MICROS(event_timestamp)) AS day,
COUNT(*) AS events
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260228'
GROUP BY event_name, day
)
SELECT
event_name,
day,
events,
LAG(events, 1) OVER (PARTITION BY event_name ORDER BY day) AS prev_day,
SAFE_DIVIDE(events - LAG(events, 1) OVER (PARTITION BY event_name ORDER BY day),
LAG(events, 1) OVER (PARTITION BY event_name ORDER BY day)) AS change_pct
FROM daily
QUALIFY ABS(change_pct) > 0.3 AND events > 100
ORDER BY day DESC, ABS(change_pct) DESC;Zapytanie wyłapie każdy event, który dzień do dnia zmienił wolumen o ponad 30% (i ma co najmniej 100 zdarzeń). Uruchamiaj tygodniowo — wcześniejsze wyłapywanie błędów wdrożenia oszczędza tygodnie na ponowne zbieranie danych.
Optymalizacja zapytań i monitoring kosztów
Cztery sposoby na redukcję skanowanych bajtów
- Materialized views dla często używanych agregatów (DAU, sesje, przychód). BigQuery odświeża je automatycznie przy zmianach źródła, za koszt skanów tylko nowych danych.
- Własne tabele agregacyjne z scheduled queries: zapis daily summary do tabeli
mart.daily_summaryi joinowanie z nią zamiast raw events. - Partycjonowanie i klastrowanie — już omawialiśmy; dla dashboardów operacyjnych obowiązkowe.
- Limit kolumn w SELECT — BigQuery nalicza za skan kolumn, nie wierszy.
SELECT event_name, event_timestamp FROM …kosztuje 1/10SELECT *.
Monitoring wydatków
-- Top zapytań po koszcie (ostatnie 7 dni)
SELECT
user_email,
query,
total_bytes_billed / POW(10, 12) AS tb_billed,
total_slot_ms / 1000 / 60 AS minutes_slot
FROM `region-eu`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
AND job_type = 'QUERY'
ORDER BY total_bytes_billed DESC
LIMIT 20;Uruchamiaj raz w tygodniu. Pokaże użytkowników i zapytania, które najwięcej kosztują – często okazuje się, że jeden dashboard Looker Studio odświeża się 500 razy dziennie i generuje 80% kosztów.
Połączenie BigQuery z Looker Studio i Google Sheets
BigQuery surowo analizowany przez analityka wystarcza zespołom data-first. Większość organizacji potrzebuje wizualizacji — tu wchodzą Looker Studio i Sheets.
Looker Studio – jak nie zabić kosztów
- Unikaj custom query jako data source. Każde odświeżenie raportu wykonuje query ponownie. Zamiast tego twórz materialized view w BigQuery i podłączaj jako table source.
- Używaj Extract Data. Looker Studio może wyekstraktować dane raz dziennie do wewnętrznego cache – dashboardy ładują się szybciej i nie generują kosztu BQ.
- Ogranicz zakres dat w raporcie. Domyślny „last 30 days” na źródle z 2 lat danych skanuje cały period – zablokuj date range na poziomie source.
- Filtry po klastrowanych kolumnach. Jeśli tabela jest klastrowana po
event_name, filtry w Looker Studio po event_name znacznie redukują skan.
Google Sheets connector
Dla raportów managerskich często wystarczy Google Sheets. Connected Sheets (Data → Data connectors → BigQuery) pozwala pracować na tabeli do 100 mln wierszy jak na zwykłym arkuszu. Extract function co 24h – dashboardy, pivot tables, wykresy odświeżają się na snapshot. Koszt BQ: jeden query dziennie.
Szczegóły automatyzacji raportów w Looker Studio dla klientów pokrywamy w pillarze analityka marketingowa 2026 i w materiale zdarzenia GA4, gdzie omawiamy, jak poprawnie nazwane zdarzenia automatyzują raportowanie.
Najczęstsze błędy przy pracy z GA4 w BigQuery
- Brak
_TABLE_SUFFIXw WHERE – skan całej historii, drogo. - SELECT * FROM events_* – kosztowne; zawsze wybierz potrzebne kolumny.
- Używanie
traffic_sourcezamiastcollected_traffic_source— pierwsze to user-level, drugie session-level. - Ignorowanie strefy czasowej.
event_timestampjest UTC.event_datejest w strefie properta. Miksowanie powoduje rozjazdy ±1 dzień. - Próba odtworzenia atrybucji GA4. GA4 DDA używa modelu Markov chain — odtworzenie go w SQL jest trudne. Jeśli zależy Ci na zgodności 1:1, zostań w panelu GA4 albo użyj GA4 API, nie BQ.
- Brak honorowania consent mode. Dla użytkowników bez zgody GA4 w advanced mode zapisuje placeholder eventy bez
user_pseudo_id. Ignoruj je w analizie retention. - Liczenie sesji przez sum(session_start). To użyteczne przy debugu, ale realną liczbę sesji daje
COUNT DISTINCT(user_pseudo_id, session_id). - Brak monitoringu kosztów — jeden zepsuty dashboard Looker Studio potrafi spalić 500 USD w tydzień.
FAQ – najczęstsze pytania
Ile kosztuje BigQuery dla średniego sklepu e-commerce w 2026?
Dla sklepu z 500k-1M zdarzeń dziennie i 10-30 zapytaniami/dzień: storage ~5-15 GB = 0,10-0,30 USD/miesiąc, zapytania 200-600 GB skanu = 1,00-3,00 USD/miesiąc. Łącznie 1-5 USD miesięcznie – zwykle mieści się w darmowym tier (10 GB storage + 1 TB zapytań gratis). Sklep z 5M zdarzeń dziennie i dashboardem Looker Studio odświeżanym co godzinę to 30-150 USD/miesiąc. Powyżej – rozważ reserved slots (flat rate 2000 USD/miesiąc za 500 slotów), jeśli masz dedykowany team analityczny z ciągłymi zapytaniami.
Dlaczego liczby w BigQuery nie zgadzają się z panelem GA4?
Różnice 1-5% są normalne i wynikają z: (1) GA4 panel aplikuje thresholding – ukrywa wartości dla małych segmentów, BigQuery nie; (2) GA4 sampling w Explorations powyżej progu, BigQuery nigdy; (3) modelowanie konwersji w Consent Mode v2 – GA4 modeluje utracone zdarzenia, BQ pokazuje tylko faktycznie zebrane; (4) różne okna atrybucji; (5) strefy czasowe – BQ event_timestamp jest UTC, panel używa strefy properta. Różnica powyżej 10% zwykle sygnalizuje błąd – najczęściej zły filter zakresu dat lub wyciąganie session_id bez user_pseudo_id.
Czy potrzebuję GA4 360, żeby sensownie korzystać z BigQuery?
Nie. Standardowy GA4 eksportuje do BigQuery bez limitu wielkości — dla większości sklepów to wystarcza. Jedyne, co zmienia 360: (1) limit 1 mln zdarzeń dziennie w eksporcie jest podniesiony do 4 mld, (2) dodatkowa tabela pseudonymous_users_*, (3) streaming export (real-time, nie 24h delayed), (4) SLA. Dla 99% polskich sklepów standard + BigQuery daje 90% możliwości GA4 360 za ułamek ceny (0 USD miesięcznie zamiast 12 500 USD).
Jak często uruchamiać scheduled queries dla raportów?
Zależy od tempa decyzji biznesowych. Dla zarządu wystarczy dziennie rano (ok. 7:00). Dla zespołu marketingu: co 4-6 godzin. Dla działu operacyjnego (fraud detection, inventory): co 15-30 minut. Unikaj częstości poniżej 15 min – intraday tabela GA4 się aktualizuje z pewnym opóźnieniem, szybsze odświeżanie to tylko generowanie kosztów bez wzrostu użyteczności. Ustaw scheduled queries w BigQuery Console → Scheduled Queries; one zapisują wynik do dedykowanej tabeli, na której pracują dashboardy.
Czy mogę złączyć GA4 BigQuery z moim CRM?
Tak, i to jest jeden z głównych powodów eksportu do BQ. Scenariusz: eksportujesz codzienną tabelę klientów z Salesforce/HubSpot do BigQuery (Fivetran, Airbyte, natywne connectory Google Cloud) i joinujesz po user_id. Warunek: musisz przekazywać user_id do GA4 dla zalogowanych użytkowników (w innym przypadku user_pseudo_id nie zmatchuje się z ID w CRM). Typowe zapytania: CRM lead score × GA4 zachowanie, fakturowana wartość kontraktu × ścieżka akwizycji, churn w CRM × ostatnia aktywność w produkcie z GA4.
Jak zbudować model atrybucji Markov chain w BigQuery?
Markov chain oblicza removal effect – prawdopodobieństwo, że kanał X jest potrzebny do konwersji. Implementacja wymaga: (1) zbudowania macierzy przejść między kanałami (channel A → channel B dla każdej ścieżki), (2) iteracyjnego rozwiązania systemu prawdopodobieństw, (3) usunięcia każdego kanału i porównania finalnej probability konwersji. W SQL-u to 60-80 linii; łatwiej użyć BigQuery ML lub Pythona z pandas + numpy na wyekstraktowanych danych. Gotowe template na GitHub: wyszukaj „ga4 markov attribution bigquery”. Dla budżetów powyżej 500 tys. PLN/rok warto zrobić to poprawnie — dla mniejszych time-decay lub position-based dają podobną jakość decyzji przy dziesięciokrotnie niższym koszcie implementacji.
Czy warto eksportować do BigQuery dane z GA4 dla starych, zamkniętych już propertów?
Nie. Eksport BigQuery działa tylko od momentu skonfigurowania linku — danych historycznych Google nie backfilluje. Jeśli potrzebujesz analiz historycznych sprzed eksportu, masz dwie opcje: (1) Reporting API + Python do pobrania zagregowanych danych (traci granulację per-user), (2) eksport Data Studio do BQ (tylko dla GA4 360). Dlatego tak ważne jest włączenie eksportu od pierwszego dnia istnienia properta – koszt zera, a retencja nieograniczona. Dla nowych propertów klientów zawsze ustawiaj BigQuery link w pierwszej godzinie po utworzeniu.
Co dalej
BigQuery to nie narzędzie analityka, to platforma, wokół której buduje się cały stack danych marketingowych. Kolejne kroki:
Nie ma magicznego zapytania SQL, które rozwiąże problem biznesowy. Są jednak zapytania, które oszczędzają dni pracy analityka i dają odpowiedzi, których panel GA4 nie zapewni. Im wcześniej zbudujesz bibliotekę takich zapytań dla swojej firmy, tym szybciej analityka przestanie być wąskim gardłem, a stanie się regularną pętlą: pytanie biznesowe → query → decyzja. Warto dokumentować zapytania w repo Git obok kodu produktowego i traktować je jak pierwszy klasy artefakt — z PR, review i testami.