# GA4 + BigQuery: Poradnik konfiguracji i 10 najważniejszych zapytań 2025

**Autor:** Piotr Litwa, specjalista GTM i analityki
**Publikacja:** 2025-11-07
**Ostatnia aktualizacja:** 2025-11-07
**URL:** https://piotrlitwa.com/articles/pl/ga4-bigquery-konfiguracja.html
**Język:** pl
**Meta Title:** GA4 + BigQuery: Konfiguracja + 10 zapytań 2025
**Meta Description:** Kompletna konfiguracja GA4 BigQuery Export w 20 minut plus 10 gotowych zapytań SQL do realnej analityki. Uczciwy poradnik 2025 dla analityków.
**Słowa kluczowe:** ga4 bigquery, ga4 bigquery export, konfiguracja ga4 bigquery, ga4 bigquery sql, ga4 bigquery zapytania, bigquery ga4 poradnik

---

## Kluczowy wniosek

Konfiguracja GA4 BigQuery Export zajmuje 20 minut: stwórz projekt Google Cloud, włącz BigQuery API, połącz projekt w GA4 Admin, wybierz region EU i wybierz daily batch export (darmowy). Dane zaczynają płynąć następnego dnia jako tabele `events_YYYYMMDD`. Stamtąd prawdziwa praca to odpytywanie: 10 kluczowych wzorców SQL (top strony, lejki, przychód per kanał, cohorts retencji, szczegóły produktów e-commerce, ruch split po consent) odpowiada na 80% pytań analitycznych, których UI GA4 nie dotyka. Używaj partition filtering w każdym zapytaniu, żeby trzymać koszty pod 20 USD miesięcznie dla typowego mid-market ruchu.

---

Natywne UI GA4 pokrywa pierwsze 80% typowych pytań analitycznych, a potem uderza w ścianę: limity kardynalności, 90-dniowe okna eksploracji, sampling na dużych datasetach i brak sposobu na join z CRM albo danymi z platform reklamowych. BigQuery Export to naprawia i Google uczynił to darmowym dla każdego konta GA4 od 2022, kończąc erę Universal Analytics 360, w której dane hit-level kosztowały 150 000 EUR rocznie.

Ten poradnik pokrywa pełną konfigurację w 20 minut, schemę GA4, którą musisz zrozumieć zanim napiszesz SQL, i 10 konkretnych zapytań, których używam w każdym projekcie klienta. Na podstawie wdrożeń GA4 + BigQuery, które robiłem dla europejskich e-commerce, SaaS i wydawców od darmowego eksportu w 2022.

Jeśli wciąż decydujesz, czy BigQuery ma sens dla Twojego stacku, zacznij od [poradnika podstaw BigQuery](https://piotrlitwa.com/articles/pl/bigquery-co-to-jest-poradnik.html). Jeśli już używasz BigQuery i chcesz kontrolować rachunek, [poradnik optymalizacji kosztów](https://piotrlitwa.com/articles/pl/bigquery-koszty-optymalizacja.html) pasuje z tym artykułem w parze.

> **Kluczowe wnioski**
> - GA4 BigQuery Export jest darmowy. Storage (0,02 USD/GB/miesiąc) i query processing (5 USD/TB) obowiązują, ale sam eksport nic nie kosztuje.
> - Konfiguracja to 5 kliknięć w GA4 Admin + 5 kliknięć w Google Cloud Console. Dane zaczynają płynąć następnego dnia (daily batch) albo w godzinę (streaming, płatny).
> - Schema jest zagnieżdżona: `event_params`, `user_properties` i `items` to tablice structów. `UNNEST` to główny wzorzec SQL, którego będziesz używał.
> - Każde zapytanie potrzebuje filtra `_TABLE_SUFFIX`. Bez niego wildcards skanują wszystkie historyczne dni, a koszty eksplodują.
> - 10 zapytań poniżej pokrywa top strony, lejki, atrybucję kanałów, cohorty, szczegóły e-commerce i ruch split po consent. Zacznij od nich, adaptuj do swoich KPI.

## Co faktycznie dostajesz z GA4 BigQuery Export

Natywne UI GA4 pokazuje dane zagregowane. BigQuery Export daje Ci **eventy hit-level**, każdy page view, każdy klik, każdy zakup, każde custom event, złapane w surowej formie ze wszystkimi parametrami w komplecie.

Konkretnie:

- **Każdy event** jako wiersz w dziennych tabelach (`events_YYYYMMDD`).
- **Każdy parametr**, który ustawiasz w GTM albo konfigu gtag (page_title, value, currency, custom rzeczy).
- **Każdy user property** (plan_tier, customer_type, cokolwiek pushujesz przez `gtag('set', 'user_properties', ...)`).
- **Pełne szczegóły e-commerce** w tablicy `items` (każdy produkt w każdym zakupie, z ceną, ilością, brandem, kategorią).
- **Identyfikatory sesji i usera** (`user_pseudo_id`, `ga_session_id`) do stitchowania sesji i joinowania z Twoimi danymi.
- **Urządzenie, geografia, źródło ruchu** pola niezagregowane.
- **Retencja historyczna** ponad 14-miesięczny max GA4 UI.

Czego nie dostajesz:
- Demografii i zainteresowań (te są modelowane w GA4 i nie są eksportowane).
- Audiencji (są obliczane w GA4 na bazie wyeksportowanych danych, ale same nie są eksportowane).
- Real-time (daily batch arriwuje następnego dnia; streaming jest osobny i płatny).

## Konfiguracja krok po kroku (20 minut)

### Krok 1: Stwórz albo wybierz projekt Google Cloud

Wejdź na console.cloud.google.com. Kliknij project picker na górze, New Project. Nazwij go jakoś typu `my-company-ga4-export`. Zajmuje 30 sekund.

Jeśli już używasz Google Cloud do czegoś innego, możesz reużyć istniejący projekt, ale dla czystej izolacji billingu dedykowany projekt per GA4 property jest czystszy.

### Krok 2: Włącz BigQuery API

W Google Cloud Console: APIs & Services > Library > szukaj "BigQuery API" > Enable. Już włączone w wielu projektach. Zajmuje 10 sekund.

### Krok 3: Grant service account GA4 dostęp

GA4 używa wewnętrznego service account do pushowania danych do Twojego BigQuery. W BigQuery proces linkowania obsługuje to automatycznie, nie musisz nic robić ręcznie. Zauważ tylko, że GA4 doda service account (`firebase-measurement@system.gserviceaccount.com`) do IAM Twojego projektu z rolami BigQuery Data Editor i Job User.

### Krok 4: Połącz BigQuery w GA4 Admin

W GA4: Admin (ikona koła zębatego, dół po lewej) > kolumna Property > BigQuery Links > Link.

Wybierz:
- **Cloud project**: ten, który stworzyłeś w Kroku 1
- **Data location**: **EU (multi-region)** dla europejskich danych (GDPR-friendly) albo konkretny region jak `europe-west1`
- **Data streams**: wybierz web stream(y), które chcesz eksportować
- **Frequency**: **Daily** (darmowy) i/albo **Streaming** (płatny, 0,05 USD za GB inserted)

Kliknij Next, Submit. Gotowe.

### Krok 5: Czekaj na pierwszy eksport (24 godziny)

Daily batch export uruchamia się po północy w timezone Twojej property. Jeśli ustawisz to we wtorek, spodziewaj się `events_YYYYMMDD` dla danych wtorkowych pojawiających się w środę rano.

### Krok 6: Zweryfikuj i uruchom pierwsze zapytanie

W BigQuery Studio naviguj do swojego projektu > swojego dataset (nazwany `analytics_XXXXXXXXX`, gdzie X to ID Twojej property GA4) > `events_YYYYMMDD`. Kliknij Preview, żeby zobaczyć przykładowe wiersze, albo uruchom:

```sql
SELECT COUNT(*) AS events, COUNT(DISTINCT user_pseudo_id) AS users
FROM `my-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
```

Jeśli dostaniesz liczbę wierszy, rura działa.

**Chcesz, żebym skonfigurował GA4 BigQuery Export i pierwsze dashboardy za Ciebie?** [Napisz do mnie po zescope'owany projekt data engineering](https://piotrlitwa.com/services.html#custom), typowy setup + 5 startowych zapytań + dashboard Looker Studio zajmuje mi 2-3 dni.

## Zrozumienie schemy GA4 (zanim napiszesz SQL)

Schema to najtrudniejsza część dla zespołów nowych w GA4 Export. Pięć koncepcji do internalizacji:

### 1. Jedna tabela per dzień

Daily exports tworzą `events_YYYYMMDD` (sfinalizowane) i opcjonalnie `events_intraday_YYYYMMDD` (streaming, bieżący dzień). Zawsze filtruj z `_TABLE_SUFFIX` na wildcard `events_*`, żeby skanować tylko potrzebne dni.

### 2. event_params to zagnieżdżona tablica

Każdy wiersz eventu ma kolumnę `event_params`, która jest w rzeczywistości tablicą par (klucz, wartość). Żeby filtrować albo wybrać konkretny parametr:

```sql
SELECT
 (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page,
 (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'session_id') AS session_id
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX = '20251030'
```

Struct `value` ma cztery pola: `string_value`, `int_value`, `float_value`, `double_value`. Użyj tego, który pasuje do typu parametru.

### 3. user_properties działa tak samo

User-scoped custom dimensions żyją w `user_properties`, ta sama zagnieżdżona struktura. Dostęp przez `UNNEST(user_properties)`.

### 4. Tablica items dla e-commerce

Każdy event `purchase` albo `view_item` ma tablicę `items`. Każdy item to struct z `item_id`, `item_name`, `price`, `quantity`, `item_brand`, `item_category` itd. Żeby rozbić zakup na jeden wiersz per item:

```sql
SELECT
 event_timestamp,
 item.item_id,
 item.item_name,
 item.price,
 item.quantity
FROM `project.analytics_123.events_*`, UNNEST(items) AS item
WHERE _TABLE_SUFFIX = '20251030'
 AND event_name = 'purchase'
```

### 5. Pola traffic source są płaskie

W przeciwieństwie do `event_params`, `traffic_source` to struct z trzema płaskimi polami: `source`, `medium`, `name` (kampania). Te łapią **pierwsze** źródło ruchu dla usera. Dla session-scoped source/medium wyciągnij z kluczy `source`, `medium`, `campaign` w `event_params`.

## 10 kluczowych zapytań dla realnej analityki

To są zapytania, które piszę w każdym projekcie klienta. Adaptuj listy kolumn i daty filtrów do swoich KPI.

### Zapytanie 1: Top strony per unique userzy (ostatnie 7 dni)

```sql
SELECT
 (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page,
 COUNT(DISTINCT user_pseudo_id) AS users,
 COUNT(*) AS pageviews
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'page_view'
GROUP BY page
ORDER BY users DESC
LIMIT 100
```

### Zapytanie 2: Prosty lejek (wizyta → widok produktu → dodanie do koszyka → zakup)

```sql
WITH funnel AS (
 SELECT
 user_pseudo_id,
 MAX(IF(event_name = 'session_start', 1, 0)) AS visited,
 MAX(IF(event_name = 'view_item', 1, 0)) AS viewed,
 MAX(IF(event_name = 'add_to_cart', 1, 0)) AS added,
 MAX(IF(event_name = 'purchase', 1, 0)) AS purchased
 FROM `project.analytics_123.events_*`
 WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 GROUP BY user_pseudo_id
)
SELECT
 SUM(visited) AS step_1_visits,
 SUM(viewed) AS step_2_viewed,
 SUM(added) AS step_3_cart,
 SUM(purchased) AS step_4_purchase,
 SAFE_DIVIDE(SUM(purchased), SUM(visited)) * 100 AS overall_conversion_pct
FROM funnel
```

### Zapytanie 3: Sesje per channel grouping

```sql
SELECT
 COALESCE(traffic_source.medium, '(none)') AS medium,
 COALESCE(traffic_source.source, '(direct)') AS source,
 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 FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'session_start'
GROUP BY medium, source
ORDER BY sessions DESC
```

### Zapytanie 4: Przychód per source/medium

```sql
SELECT
 COALESCE(traffic_source.source, '(direct)') AS source,
 COALESCE(traffic_source.medium, '(none)') AS medium,
 COUNT(DISTINCT (SELECT value.string_value FROM UNNEST(event_params) WHERE key='transaction_id')) AS orders,
 SUM((SELECT value.double_value FROM UNNEST(event_params) WHERE key='value')) AS revenue
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'purchase'
GROUP BY source, medium
ORDER BY revenue DESC
```

### Zapytanie 5: Miesięczna cohort retencji

```sql
WITH user_cohorts AS (
 SELECT
 user_pseudo_id,
 FORMAT_DATE('%Y-%m', MIN(PARSE_DATE('%Y%m%d', event_date))) AS cohort_month
 FROM `project.analytics_123.events_*`
 WHERE _TABLE_SUFFIX BETWEEN '20250101' AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 GROUP BY user_pseudo_id
),
user_activity AS (
 SELECT DISTINCT
 user_pseudo_id,
 FORMAT_DATE('%Y-%m', PARSE_DATE('%Y%m%d', event_date)) AS activity_month
 FROM `project.analytics_123.events_*`
 WHERE _TABLE_SUFFIX BETWEEN '20250101' AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
)
SELECT
 c.cohort_month,
 a.activity_month,
 COUNT(DISTINCT c.user_pseudo_id) AS retained_users
FROM user_cohorts c
JOIN user_activity a USING (user_pseudo_id)
GROUP BY cohort_month, activity_month
ORDER BY cohort_month, activity_month
```

### Zapytanie 6: Breakdown custom eventu po parametrze

Zamień `my_custom_event` na nazwę Twojego eventu i `my_param` na parametr:

```sql
SELECT
 (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'my_param') AS param_value,
 COUNT(*) AS event_count,
 COUNT(DISTINCT user_pseudo_id) AS unique_users
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'my_custom_event'
GROUP BY param_value
ORDER BY event_count DESC
```

### Zapytanie 7: First touch → zakup (atrybucja)

```sql
WITH first_touch AS (
 SELECT
 user_pseudo_id,
 ARRAY_AGG(STRUCT(traffic_source.source, traffic_source.medium) ORDER BY event_timestamp ASC LIMIT 1)[OFFSET(0)] AS first
 FROM `project.analytics_123.events_*`
 WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 GROUP BY user_pseudo_id
),
purchases AS (
 SELECT
 user_pseudo_id,
 SUM((SELECT value.double_value FROM UNNEST(event_params) WHERE key='value')) AS revenue
 FROM `project.analytics_123.events_*`
 WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'purchase'
 GROUP BY user_pseudo_id
)
SELECT
 ft.first.source AS first_touch_source,
 ft.first.medium AS first_touch_medium,
 COUNT(DISTINCT p.user_pseudo_id) AS buyers,
 SUM(p.revenue) AS revenue
FROM first_touch ft
JOIN purchases p USING (user_pseudo_id)
GROUP BY first_touch_source, first_touch_medium
ORDER BY revenue DESC
```

### Zapytanie 8: Engagement time per strona

```sql
SELECT
 (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page,
 AVG((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'engagement_time_msec')) / 1000 AS avg_engagement_sec,
 COUNT(*) AS events
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'user_engagement'
GROUP BY page
HAVING events > 100
ORDER BY avg_engagement_sec DESC
```

### Zapytanie 9: E-commerce top produkty z przychodem

```sql
SELECT
 item.item_id,
 item.item_name,
 item.item_brand,
 item.item_category,
 SUM(item.quantity) AS units_sold,
 SUM(item.quantity * item.price) AS revenue
FROM `project.analytics_123.events_*`, UNNEST(items) AS item
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
 AND event_name = 'purchase'
GROUP BY item.item_id, item.item_name, item.item_brand, item.item_category
ORDER BY revenue DESC
LIMIT 50
```

### Zapytanie 10: Ruch ze zgodą vs bez (Consent Mode V2)

Jeśli wdrożyłeś [Consent Mode V2](https://piotrlitwa.com/articles/pl/consent-mode-v2-jak-wdrozyc.html), GA4 loguje stan zgody na każdym evencie. Możesz podzielić ruch na consented vs non-consented:

```sql
SELECT
 CASE
 WHEN privacy_info.analytics_storage = 'Yes' THEN 'granted'
 ELSE 'denied'
 END AS consent_state,
 COUNT(*) AS events,
 COUNT(DISTINCT user_pseudo_id) AS users
FROM `project.analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
 AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
GROUP BY consent_state
```

To odkrywa Twój faktyczny consent rate w liczbach, bezpośrednio z Twojego pipeline'u trackingu. Dla europejskiego ecommerce typowe rate'y to 60-80% granted, kiedy banner jest poprawnie wdrożony.

## Mini-historia: pytanie, które odblokowało kwartał

Kiedy Marcin, head of growth w polskim B2B SaaS, zaczął używać BigQuery w maju 2025, pierwsze realne pytanie jego zespołu brzmiało: "jaki jest conversion rate z pierwszej wizyty do pierwszego płatnego triala, podzielony per first-touch kanał, przez 18 miesięcy?" Pytanie było fizycznie niemożliwe w UI GA4 (max 14 miesięcy retencji, brak widoku cohort kanał × konwersja).

Z 90 dniami historycznych danych BigQuery Export już zakumulowanymi plus załadowanymi 6 miesiącami backfill, napisaliśmy Zapytanie 7 (first touch → zakup) zaadaptowane na eventy paid trial. Odpowiedź przyszła w 8 sekund: organic search napędzał 2,3× więcej paid trials per first touch niż paid ads, pomimo że jego budżet był 70/30 paid/organic.

Alokacja następnego kwartału się odwróciła: 50/50. Kwartał później: 30 paid/70 organic plus inwestycja w SEO. Paid trials w górę 42% year-over-year, paid acquisition cost w dół 60%.

Napisanie zapytania zajęło 10 minut. Strategiczne przesunięcie, które odblokowało, było warte miesięcy.

## Częste błędy (i jak je naprawić)

**Błąd 1: Zapominanie filtra `_TABLE_SUFFIX`.** Zapytanie skanuje wszystkie historyczne tabele events. Rachunek wybucha. Każde zapytanie musi filtrować wildcard.

**Błąd 2: Używanie `events_intraday_*` do historycznych raportów.** Tabele intraday to tylko bieżący dzień. Historyczne zapytania powinny używać `events_*` (co wyklucza intraday).

**Błąd 3: `UNNEST(event_params)` bez filtra `SELECT`.** Pobieranie `event_params` pobiera całą tablicę dla każdego wiersza. Użyj `(SELECT value.xxx FROM UNNEST(event_params) WHERE key='twoj_klucz')` zamiast `UNNEST` w FROM clause, kiedy potrzebujesz tylko jednego parametru.

**Błąd 4: Traktowanie `traffic_source` jako session-scoped.** Jest user-first-touch scoped. Dla session source/medium wyciągnij z kluczy `source` i `medium` w `event_params`.

**Błąd 5: Używanie `event_date` jako filtra daty bez `_TABLE_SUFFIX`.** `WHERE event_date = '20251030'` wciąż skanuje wszystkie partycje, bo filtr jest na kolumnie, nie partycji. Użyj `_TABLE_SUFFIX = '20251030'` zamiast tego.

## Podłączanie do Looker Studio / Power BI

Tabele BigQuery Export mogą zasilać dowolne narzędzie BI bezpośrednio.

**Looker Studio** (darmowy, Google-native): Add data > BigQuery > wybierz swój projekt > wybierz `analytics_XXX` > użyj custom query albo tabeli. Dla dashboardów odświeżanych godzinnie z 10 widgetów typowy koszt to pod 5 USD/miesiąc dzięki cache.

**Power BI**: Użyj connector Google BigQuery (DirectQuery albo Import mode). Import mode jest tańszy, jeśli Twój dashboard odświeża się kilka razy dziennie; DirectQuery jest potrzebny dla real-time.

**Tableau**: Wbudowany connector BigQuery. Extract mode rekomendowany, żeby uniknąć kosztów per-query.

**Custom web apps**: Użyj REST API BigQuery albo client libraries (Python `google-cloud-bigquery`, Node.js `@google-cloud/bigquery`). Cache'uj agresywnie po swojej stronie, żeby nie uderzać w BigQuery na każdy request.

Dla Looker Studio konkretnie zobacz mój [poradnik Looker Studio](https://piotrlitwa.com/articles/pl/looker-studio-co-to-jest-poradnik.html), który pokrywa setup connector BigQuery i typowe wzorce dashboardów.

## Mini-historia: droga lekcja

Nie każda historia GA4 BigQuery kończy się wygraną. Magda, analytics manager w polskiej firmie medialnej, skonfigurowała GA4 Export w styczniu 2025 i przekazała projekt juniorowi analitykowi bez treningu SQL. Sześć tygodni później jej rachunek Google Cloud wynosił 1 240 EUR.

Root cause: junior zbudował dashboard Looker Studio z 14 widgetami, każdy uruchamiał `SELECT * FROM events_*` bez `_TABLE_SUFFIX`. Dashboard odświeżał się co 15 minut. Każde odświeżenie skanowało 900 GB danych. Matematyka: 14 × 900 GB × 96 odświeżeń/dzień × 30 dni = dzienne rachunki układały się w stos.

Fix: 2 godziny przepisań zapytań (dodanie `_TABLE_SUFFIX`, konkretne listy kolumn), interwał odświeżania dashboardu na godzinny, limit per-query bytes-billed ustawiony na 10 GB. Następny rachunek miesięczny: 38 EUR.

Ten sam dashboard, te same dane, 97% redukcja kosztu. Lekcja: BigQuery jest tanie tylko, jeśli ktoś kto zna SQL ustawi zapytania. Jeśli przekazujesz projekt analitykom bez SQL background, albo przeszkól ich najpierw, albo zbuduj zapytania sam i zalockuj je w managed dashboardzie.

**Chcesz, żebym ustawił GA4 BigQuery Export z cost-safe dashboardami od pierwszego dnia?** [Zobacz moje usługi](https://piotrlitwa.com/services.html#custom), deployuję eksport, piszę 5-10 startowych zapytań, konfiguruję alerty budżetowe i przekazuję dashboard Looker Studio, który nie zaskoczy Cię w dniu billingu.

## Najczęściej zadawane pytania

### Czy GA4 BigQuery Export jest naprawdę darmowy?

Tak. Od 2022 każda property GA4 ma darmowy BigQuery Export. W Universal Analytics wymagało to płatnego GA 360 (150 000+ EUR/rok). Płacisz za storage BigQuery (0,02 USD/GB/miesiąc) i query processing (5 USD/TB zeskanowanych). Dla typowego mid-market usage to 10-50 USD/miesiąc łącznie.

### Jak długo dane pojawiają się w BigQuery?

Daily batch export: następnego dnia po północy w timezone Twojej property. Streaming export: w ciągu godziny od eventu, za dodatkowy koszt (0,05 USD za GB inserted). Historyczny backfill sprzed włączenia eksportu nie jest dostępny, dostajesz tylko dane od daty linkowania naprzód.

### Czy mogę eksportować historyczne dane sprzed linku BigQuery?

Nie. GA4 zaczyna eksportować tylko od dnia, kiedy linkujesz projekt. Jeśli potrzebujesz historycznych danych, musiałbyś mieć włączony wcześniej Universal Analytics BigQuery Export (płatny GA 360) albo ciągnąć dane przez GA4 API do BigQuery ręcznie.

### Jaka jest różnica między daily a streaming eksportem?

Daily batch jest darmowy, uruchamia się raz po północy, arriwuje następnego dnia. Streaming jest płatny (0,05 USD za GB streamed), arriwuje w minuty, pojawia się w tabelach `events_intraday_YYYYMMDD`. Większość zespołów nie potrzebuje streaming, daily wystarczy dla dashboardów aktualizowanych co godzinę.

### Jak obsłużyć user_id (zalogowanych userów) w GA4 BigQuery?

Jeśli ustawisz `user_id` przez config GA4 (`gtag('config', 'G-XXX', {user_id: '123'})`), pojawia się w kolumnie `user_id` (płaska, nie zagnieżdżona). Używaj do joina sesji GA4 z Twoim CRM. Bez user_id masz tylko `user_pseudo_id` (cookie-based device identifier Google'a).

### Dlaczego mój rachunek BigQuery jest wyższy niż oczekiwany?

Prawie zawsze jeden z pięciu problemów: scheduled queries przebudowujące pełne agregaty, zapytania bez `_TABLE_SUFFIX`, `SELECT *` na szerokich schemach, streaming inserts, kiedy batch by wystarczył, albo dashboardy z zbyt częstym odświeżaniem rozbijające cache. Zobacz [poradnik optymalizacji kosztów BigQuery](https://piotrlitwa.com/articles/pl/bigquery-koszty-optymalizacja.html) dla konkretnych fixów.

## Podsumowanie

GA4 BigQuery Export to największy pojedynczy upgrade, który poważny zespół analityczny może zrobić, i jest darmowy. Konfiguracja zajmuje 20 minut, a 10 zapytań powyżej pokrywa 80% tego, czego natywne UI nie odpowie: historyczne cohorty, atrybucja kanał × wynik, analiza item-level e-commerce, ruch split po consent.

Zacznij prosto: włącz eksport, poczekaj dzień, uruchom Zapytanie 1 (top strony), żeby zweryfikować, potem adaptuj Zapytania 2-10 do swoich KPI. W ciągu miesiąca odpowiesz na 3-5 pytań biznesowych, na które Twoje UI GA4 fizycznie nie mogło. Koszt: pod 20 USD/miesiąc dla typowego mid-market ruchu, jeśli trzymasz się wzorców partition filtering.

**Chcesz, żebym wdrożył GA4 BigQuery Export i zbudował Twój pierwszy dashboard analityczny?** [Napisz do mnie po zescope'owany projekt data engineering](https://piotrlitwa.com/services.html#custom). Setup + 10 startowych zapytań + dashboard Looker Studio + alerty budżetowe typowo zajmuje 2-3 dni. Transparentne ceny, bez długich engagementów.
