{"id":23525396,"url":"https://github.com/ajaxbarcelonacruyff/ga4_bigquery_session_source","last_synced_at":"2025-05-14T08:11:39.074Z","repository":{"id":266172040,"uuid":"897606644","full_name":"ajaxbarcelonacruyff/ga4_bigquery_session_source","owner":"ajaxbarcelonacruyff","description":"Creating GA4 session references in BigQuery.","archived":false,"fork":false,"pushed_at":"2024-12-23T19:50:42.000Z","size":17,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-17T01:44:25.988Z","etag":null,"topics":["bigquery","ga4","googleanalytics"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ajaxbarcelonacruyff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-12-02T23:18:06.000Z","updated_at":"2024-12-23T19:51:17.000Z","dependencies_parsed_at":"2024-12-03T00:26:15.958Z","dependency_job_id":"2bf3f50b-306e-4947-b217-d9604ac2e450","html_url":"https://github.com/ajaxbarcelonacruyff/ga4_bigquery_session_source","commit_stats":null,"previous_names":["ajaxbarcelonacruyff/ga4_bigquery_session_source"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajaxbarcelonacruyff%2Fga4_bigquery_session_source","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajaxbarcelonacruyff%2Fga4_bigquery_session_source/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajaxbarcelonacruyff%2Fga4_bigquery_session_source/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajaxbarcelonacruyff%2Fga4_bigquery_session_source/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ajaxbarcelonacruyff","download_url":"https://codeload.github.com/ajaxbarcelonacruyff/ga4_bigquery_session_source/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254101559,"owners_count":22014908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bigquery","ga4","googleanalytics"],"created_at":"2024-12-25T19:09:12.947Z","updated_at":"2025-05-14T08:11:34.066Z","avatar_url":"https://github.com/ajaxbarcelonacruyff.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e [!NOTE]\n\u003eThis article is based on the content as of August 2024. Please check official sources for the latest information.\n\n\n# Creating Session Source Information in GA4 with BigQuery\nSince GA4 records are event-based, session sources need to be aggregated at the session level before retrieval.\nRecently, the `session_traffic_source_last_click` column was added, making it usable for this purpose. However, the following query prioritizes `event_params` or `collected_traffic_source` columns.\n\n# Processing Steps\n1. Add session source information, such as the source of `session_start`. Priority order: `collected_traffic_source` column \u003e `event_params.source` column \u003e `session_traffic_source_last_click` column.\n2. Retrieve the earliest event containing session source information.\n3. Use the source from `session_start` if available; otherwise, retrieve it from events.\n   - For example, in `session_traffic_medium`, using `IF(s.event_traffic_source IS NOT NULL)` ensures that `source` and `medium` are both sourced from `session_start` and prevents mismatches.\n4. Adopt session information if it exists in historical data (you need to create a separate query to store `user_pseudo_id`, `ga_session_id`, and `session_traffic_source` in the `sessions` table).\n\n```\nwith ga AS(\n-- GA4 table\n  SELECT *,\n  (SELECT value.int_value FROM UNNEST(event_params) WHERE key ='ga_session_id') AS ga_session_id,\n  (SELECT value.string_value FROM UNNEST(event_params) WHERE key='source') AS event_traffic_source,\n  (SELECT value.string_value FROM UNNEST(event_params) WHERE key='medium') AS event_traffic_medium,\n  (SELECT value.string_value FROM UNNEST(event_params) WHERE key='campaign') AS event_traffic_campaign,\n  -- omitting the following\n  FROM `project_id.analytics_123456789.events_YYYYMMDD`  \n),\n-- Process of adding referents, e.g. to get session_start referents. Priority order is collected_traffic_source column \u003e event_params.source column \u003e session_traffic_source_last_click column.\nsession_start AS(\n    SELECT *\n    FROM(\n        SELECT \n            user_pseudo_id,\n            ga_session_id,\n            ARRAY_AGG(STRUCT(\n                COALESCE(g.collected_traffic_source.manual_source, g.event_traffic_source, g.session_traffic_source_last_click.cross_channel_campaign.source) AS event_traffic_source,\n                COALESCE(g.collected_traffic_source.manual_medium, g.event_traffic_medium, g.session_traffic_source_last_click.cross_channel_campaign.medium) AS event_traffic_medium,\n                COALESCE(g.collected_traffic_source.manual_campaign_name, g.event_traffic_campaign, g.session_traffic_source_last_click.cross_channel_campaign.campaign) AS event_traffic_campaign,\n                COALESCE(g.collected_traffic_source.manual_content, g.event_traffic_content, g.session_traffic_source_last_click.cross_channel_campaign.content) AS event_traffic_content,\n                COALESCE(g.collected_traffic_source.manual_term, g.event_traffic_term, g.session_traffic_source_last_click.cross_channel_campaign.term) AS event_traffic_term,\n                COALESCE(g.collected_traffic_source.manual_source_platform, g.event_traffic_source_platform, g.session_traffic_source_last_click.cross_channel_campaign.source_platform) AS event_traffic_source_platform,\n                COALESCE(g.collected_traffic_source.manual_creative_format, g.event_traffic_creative_format, g.session_traffic_source_last_click.cross_channel_campaign.creative_format) AS event_traffic_creative_format,\n                COALESCE(g.collected_traffic_source.manual_marketing_tactic, g.event_traffic_marketing_tactic, g.session_traffic_source_last_click.cross_channel_campaign.marketing_tactic) AS event_traffic_marketing_tactic,\n                COALESCE(g.collected_traffic_source.manual_campaign_id, g.event_traffic_campaign_id, g.session_traffic_source_last_click.cross_channel_campaign.campaign_id) AS event_traffic_campaign_id\n            ) ORDER BY event_timestamp ASC LIMIT 1)[OFFSET(0)].*\n        FROM ga g\n        WHERE event_name =\"session_start\"\n        GROUP BY ALL\n    ) \n    WHERE event_traffic_source IS NOT NULL AND event_traffic_source NOT IN(\"(not set)\",\"(direct)\")  -- 対象となったsession_startイベントのevent_traffic_sourceがNULLや (not set), (direct)の場合は値を返さない ※(not set)や(direct)はないはずですが念のため\n),\n-- Retrieve the oldest event with references, etc.\nfirst_campaign AS(\n    SELECT \n        user_pseudo_id,\n        ga_session_id,\n        ARRAY_AGG(STRUCT(\n                COALESCE(g.collected_traffic_source.manual_source, g.event_traffic_source, g.session_traffic_source_last_click.cross_channel_campaign.source) AS event_traffic_source,\n                COALESCE(g.collected_traffic_source.manual_medium, g.event_traffic_medium, g.session_traffic_source_last_click.cross_channel_campaign.medium) AS event_traffic_medium,\n                COALESCE(g.collected_traffic_source.manual_campaign_name, g.event_traffic_campaign, g.session_traffic_source_last_click.cross_channel_campaign.campaign) AS event_traffic_campaign,\n                COALESCE(g.collected_traffic_source.manual_content, g.event_traffic_content, g.session_traffic_source_last_click.cross_channel_campaign.content) AS event_traffic_content,\n                COALESCE(g.collected_traffic_source.manual_term, g.event_traffic_term, g.session_traffic_source_last_click.cross_channel_campaign.term) AS event_traffic_term,\n                COALESCE(g.collected_traffic_source.manual_source_platform, g.event_traffic_source_platform, g.session_traffic_source_last_click.cross_channel_campaign.source_platform) AS event_traffic_source_platform,\n                COALESCE(g.collected_traffic_source.manual_creative_format, g.event_traffic_creative_format, g.session_traffic_source_last_click.cross_channel_campaign.creative_format) AS event_traffic_creative_format,\n                COALESCE(g.collected_traffic_source.manual_marketing_tactic, g.event_traffic_marketing_tactic, g.session_traffic_source_last_click.cross_channel_campaign.marketing_tactic) AS event_traffic_marketing_tactic,\n                COALESCE(g.collected_traffic_source.manual_campaign_id, g.event_traffic_campaign_id, g.session_traffic_source_last_click.cross_channel_campaign.campaign_id) AS event_traffic_campaign_id\n        ) ORDER BY event_timestamp ASC LIMIT 1)[OFFSET(0)].*\n    FROM ga g\n    WHERE (\n        g.event_traffic_source IS NOT NULL AND g.event_traffic_source NOT IN(\"(not set)\",\"(direct)\",\"(none)\") \n    )OR (\n        g.event_traffic_medium IS NOT NULL AND g.event_traffic_medium NOT IN(\"(not set)\",\"(direct)\",\"(none)\") \n    )OR (\n        g.event_traffic_campaign IS NOT NULL AND g.event_traffic_campaign NOT IN(\"(not set)\",\"(direct)\",\"(none)\") \n    )\n    GROUP BY ALL\n),\n-- If session_start contains a reference source, it is used. If not, it is taken from the event. session_traffic_medium etc. also IF (s.event_traffic_source IS NOT NULL because if event_traffic_medium is used, the source is taken from session_start and medium is taken from the first event.\n  adopt_source AS(\n    SELECT \n        user_pseudo_id,\n        ga_session_id,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_source, a.event_traffic_source) AS session_traffic_source,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_medium, a.event_traffic_medium) AS session_traffic_medium,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_campaign, a.event_traffic_campaign) AS session_traffic_campaign,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_content, a.event_traffic_content) AS session_traffic_content,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_term, a.event_traffic_term) AS session_traffic_term,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_source_platform, a.event_traffic_source_platform) AS session_traffic_source_platform,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_creative_format , a.event_traffic_creative_format ) AS session_traffic_creative_format,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_marketing_tactic, a.event_traffic_marketing_tactic) AS session_traffic_marketing_tactic,\n        IF(s.event_traffic_source IS NOT NULL, s.event_traffic_campaign_id, a.event_traffic_campaign_id) AS session_traffic_campaign_id\n    FROM first_campaign a FULL JOIN session_start s USING(user_pseudo_id, ga_session_id)\n),\n-- If session data exists in the past, it is used.\nmart_session AS(\n    SELECT \n        user_pseudo_id,\n        ga_session_id,\n        ARRAY_AGG(STRUCT(\n            session_traffic_source,\n            session_traffic_medium,\n            session_traffic_campaign,\n            session_traffic_content,\n            session_traffic_term,\n            session_traffic_source_platform,\n            session_traffic_creative_format,\n            session_traffic_marketing_tactic,\n            session_traffic_campaign_id\n        ) ORDER BY event_date, entrance_timestamp,exit_timestamp ASC LIMIT 1)[OFFSET(0)].*\n    FROM `project_id.mart.sessions`  -- Another query is required to store user_pseudo_id, ga_session_id, session_traffic_source, etc. in the sessions table.\n    GROUP BY ALL\n),\nsession_source AS(\n    SELECT \n        user_pseudo_id,\n        ga_session_id,\n        ARRAY_AGG(STRUCT(\n            COALESCE(m.session_traffic_source, a.session_traffic_source) AS session_traffic_source,\n            COALESCE(m.session_traffic_medium, a.session_traffic_medium) AS session_traffic_medium,\n            COALESCE(m.session_traffic_campaign, a.session_traffic_campaign) AS session_traffic_campaign,\n            COALESCE(m.session_traffic_content, a.session_traffic_content) AS session_traffic_content,\n            COALESCE(m.session_traffic_term, a.session_traffic_term) AS session_traffic_term,\n            COALESCE(m.session_traffic_source_platform, a.session_traffic_source_platform) AS session_traffic_source_platform,\n            COALESCE(m.session_traffic_creative_format, a.session_traffic_creative_format) AS session_traffic_creative_format,\n            COALESCE(m.session_traffic_marketing_tactic, a.session_traffic_marketing_tactic) AS session_traffic_marketing_tactic,\n            COALESCE(m.session_traffic_campaign_id, a.session_traffic_campaign_id) AS session_traffic_campaign_id\n        ) LIMIT 1)[OFFSET(0)].*\n    FROM adopt_source a LEFT JOIN mart_session m \n    USING(user_pseudo_id, ga_session_id)\n    GROUP BY ALL\n)\nSELECT g.*,\na.session_traffic_source,\na.session_traffic_medium,\na.session_traffic_campaign\nFROM ga AS g\nLEFT JOIN session_source AS a USING (user_pseudo_id, ga_session_id)\n```\n\n\u003e [!NOTE]\n\u003eUpdate: As of October 2024, the `session_traffic_source_last_click.cross_channel_campaign` column has been added. To obtain data identical to GA4, using this column is recommended.\n\u003eHowever, for tables prior to this update, you will need to create queries like the one provided here.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajaxbarcelonacruyff%2Fga4_bigquery_session_source","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fajaxbarcelonacruyff%2Fga4_bigquery_session_source","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajaxbarcelonacruyff%2Fga4_bigquery_session_source/lists"}