{"id":93829,"date":"2025-02-17T15:09:09","date_gmt":"2025-02-17T15:09:09","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/bigquerytips-query-guide-to-google-analytics-app-web\/"},"modified":"2025-02-17T15:09:09","modified_gmt":"2025-02-17T15:09:09","slug":"bigquerytips-query-guide-to-google-analytics-app-web","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=93829","title":{"rendered":"#BigQueryTips: Query Guide To Google Analytics: App + Web"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p>I\u2019ve thoroughly enjoyed writing short (and sometimes a bit longer) bite-sized tips for my <a href=\"https:\/\/www.simoahava.com\/categories\/gtm-tips\/\">#GTMTips<\/a> topic. With the advent of <a href=\"https:\/\/www.blog.google\/products\/marketingplatform\/analytics\/new-way-unify-app-and-website-measurement-google-analytics\/\">Google Analytics: App + Web<\/a> and particularly the opportunity to <a href=\"https:\/\/www.simoahava.com\/analytics\/enable-bigquery-export-google-analytics-app-web\/\">access raw data through BigQuery<\/a>, I thought it was a good time to get started on a new tip topic: <strong>#BigQueryTips<\/strong>.<\/p>\n<p>For Universal Analytics, getting access to the BigQuery export with Google Analytics 360 has been one of the major selling points for the expensive platform. The hybrid approach of getting access to <strong>raw<\/strong> data that has nevertheless been annotated with Google Analytics\u2019 sessionization schema and any integrations (e.g. Google Ads) the user might have enabled is a powerful thing indeed.<\/p>\n<p>With <strong>Google Analytics: App + Web<\/strong>, the platform is moving away from the data model that has existed since the days of Urchin, and is instead converging with <a href=\"https:\/\/firebase.google.com\/docs\/analytics\">Firebase Analytics<\/a>, to which we have already had access with native Android and iOS applications.<\/p>\n<div style=\"aspect-ratio: 2806 \/ 1862;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/bigquery-guide-app-web.jpg\" title=\"BigQuery Guide App+Web\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1862\" width=\"2806\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/bigquery-guide-app-web.jpg#ZgotmplZ\" alt=\"BigQuery Guide App+Web\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Fortunately, <a href=\"https:\/\/www.simoahava.com\/analytics\/enable-bigquery-export-google-analytics-app-web\/\">BigQuery export for App + Web properties<\/a> comes at no additional platform costs &#8211; you only pay for storage and queries just as you would if creating a BigQuery project by yourself in Google\u2019s cloud platform.<\/p>\n<p>So, with data flowing into BigQuery, I thought it time to start writing about how to build queries against this columnar data store. One of the reasons is because I want to challenge myself, but the other reason is that there just isn\u2019t that much information available online when it comes to using SQL with Firebase Analytics\u2019 BigQuery exports.<\/p>\n<div style=\"aspect-ratio: 256 \/ 256;\" class=\"figure nocaption right\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/pawel.jpg\" title=\"Pawel\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"256\" width=\"256\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/pawel.jpg#ZgotmplZ\" alt=\"Pawel\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>To get things started with this new topic, I\u2019ve enlisted the help of my favorite SQL wizard in <a href=\"https:\/\/www.measure.chat\/\">#measure<\/a>, <a href=\"https:\/\/twitter.com\/AlienDeg\"><strong>Pawel Kapuscinski<\/strong><\/a>. He\u2019s never short of a solution when tricky BigQuery questions pop up in Measure Slack, so I thought it would be great to get him to contribute with some of his favorite tips for how to approach querying BigQuery data with SQL.<\/p>\n<p>Hopefully, <strong>#BigQueryTips<\/strong> will expand to more articles in the future. There certainly is a lot of ground to cover!<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"getting-started\">Getting started<\/h2>\n<p>First of all, you\u2019ll naturally need a <strong>Google Analytics: App + Web<\/strong> property. Here are some guides for getting started:<\/p>\n<div style=\"aspect-ratio: 672 \/ 508;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/08\/link-toggle-on.jpg\" title=\"Link toggle on\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"508\" width=\"672\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/08\/link-toggle-on.jpg#ZgotmplZ\" alt=\"Link toggle on\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, you need to enable the BigQuery export for the new property, and for that you should follow <a href=\"https:\/\/www.simoahava.com\/analytics\/enable-bigquery-export-google-analytics-app-web\/\">this guide<\/a>.<\/p>\n<div style=\"aspect-ratio: 2290 \/ 856;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/mode-sql-tutorial.jpg\" title=\"Mode SQL Tutorial\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"856\" width=\"2290\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/mode-sql-tutorial.jpg#ZgotmplZ\" alt=\"Mode SQL Tutorial\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you have the export up-and-running, it\u2019s a good time to take a moment to learn and\/or get a refresher on how SQL works. For that, there is no other tutorial online that comes even close to the <a href=\"https:\/\/mode.com\/sql-tutorial\/\">free, interactive SQL tutorial at Mode Analytics<\/a>.<\/p>\n<div style=\"aspect-ratio: 1732 \/ 1128;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/firebase-bigquery-sample.jpg\" title=\"Bigquery sample\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1128\" width=\"1732\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/firebase-bigquery-sample.jpg#ZgotmplZ\" alt=\"Bigquery sample\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>And once you\u2019ve learned the difference between <code>LEFT JOIN<\/code> and <code>CROSS JOIN<\/code>, you can take a look at some of the sample data sets for <a href=\"https:\/\/bigquery.cloud.google.com\/dataset\/firebase-analytics-sample-data:ios_dataset?pli=1\">iOS Firebase Analytics<\/a> and <a href=\"https:\/\/bigquery.cloud.google.com\/dataset\/firebase-analytics-sample-data:android_dataset\">Android Firebase Analytics<\/a>. Play around with them, trying to figure out just how much querying a typical relational database differs from accessing data stored in columnar structure as BigQuery does.<\/p>\n<p>At this point, you should have your BigQuery table collecting daily data dumps from the <strong>App + Web<\/strong> tags firing on your site, so let\u2019s work with Pawel and introduce some pretty useful <strong>BigQuery SQL queries<\/strong> to get started on that data analysis path!<\/p>\n<h2 id=\"tip-1-case-and-group-by\">Tip #1: CASE and GROUP BY<\/h2>\n<p>Our first tip covers two extremely useful SQL statements: <code>CASE<\/code> and <code>GROUP BY<\/code>. Use these to aggregate and group your data!<\/p>\n<h3 id=\"case\">CASE<\/h3>\n<div style=\"aspect-ratio: 1367 \/ 616;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/case-when.jpg\" title=\"CASE WHEN\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"616\" width=\"1367\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/case-when.jpg#ZgotmplZ\" alt=\"CASE WHEN\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><code>CASE<\/code> statements are similar to the <code>if...else<\/code> statements used in other languages. The condition is introduced with the <code>WHEN<\/code> keyword, and the first <code>WHEN<\/code> condition that matches will have its <code>THEN<\/code> value returned as the value for that column.<\/p>\n<p>You can use the <code>ELSE<\/code> keyword at the end to specify a default value. If <code>ELSE<\/code> is not defined and no conditions are met, the column gets the value <code>null<\/code>.<\/p>\n<h4 id=\"source-table\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>age<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>15<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>54<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span> <span style=\"color:#00a\">user<\/span>, age,\n  <span style=\"color:#00a\">CASE<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> age &gt;= <span style=\"color:#099\">90<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"90+\"<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> age &gt;= <span style=\"color:#099\">50<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"50-89\"<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> age &gt;= <span style=\"color:#099\">20<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"20-49\"<\/span>\n    <span style=\"color:#00a\">ELSE<\/span> <span style=\"color:#a50\">\"0-19\"<\/span>\n  <span style=\"color:#00a\">END<\/span> <span style=\"color:#00a\">AS<\/span> age_bucket\n<span style=\"color:#00a\">FROM<\/span> some_table<\/code><\/pre>\n<\/div>\n<h4 id=\"query-result\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>age<\/th>\n<th>age_bucket<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>15<\/td>\n<td>0-19<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>54<\/td>\n<td>50-89<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The <code>CASE<\/code> statement is useful for quick transformations and for aggregating the data based on simple conditions.<\/p>\n<h3 id=\"group-by\">GROUP BY<\/h3>\n<div style=\"aspect-ratio: 1381 \/ 558;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/group-by.jpg\" title=\"GROUP BY\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"558\" width=\"1381\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/group-by.jpg#ZgotmplZ\" alt=\"GROUP BY\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><code>GROUP BY<\/code> is required every time you want to <strong>summarize<\/strong> your data. For example, when you do calculations with <code>COUNT<\/code> (to return the number of instances) or <code>SUM<\/code> (to return the sum of instances), you need to indicate a column to group these calculations by (unless you\u2019re <em>only<\/em> retrieving the calculated column) . <code>GROUP BY<\/code> is thus most often used with <a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/reference\/standard-sql\/functions-and-operators#aggregate-functions\">aggregate functions<\/a> such as <code>COUNT<\/code>, <code>MAX<\/code>, <code>ANY_VALUE<\/code>, <code>SUM<\/code>, and <code>AVG<\/code>. It\u2019s also used with some string functions such as <code>STRING_AGG<\/code> when aggregating multiple rows into a single string.<\/p>\n<h4 id=\"source-table-1\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>mobile_device_model<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>iPhone 5<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>Nokia 3310<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>iPhone 7<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-1\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span> \n  <span style=\"color:#00a\">user<\/span>, \n  <span style=\"color:#00a\">COUNT<\/span>(mobile_device_model) <span style=\"color:#00a\">AS<\/span> device_count\n<span style=\"color:#00a\">FROM<\/span> <span style=\"color:#00a\">table<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span> <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-1\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>device_count<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>1<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In the query above, we assume that a single user can have more than one device associated with them in the table that is being queried. As we do a <code>COUNT<\/code> of all the devices for a given user, we need to group the results by the <code>user<\/code> column for the query to work.<\/p>\n<h3 id=\"use-case-device-category-distribution-across-users\">USE CASE: Device category distribution across users<\/h3>\n<p>Let\u2019s keep the theme of users and devices alive for a moment.<\/p>\n<p><strong>Users<\/strong> and <strong>events<\/strong> are the key metrics for App + Web. This is fundamentally different to the <strong>session<\/strong>-centric approach of Google Analytics (even though there are reverberations of \u201csessions\u201d in App + Web, too). It\u2019s much closer to a true <em>hit stream<\/em> model than before.<\/p>\n<p>However, event counts alone tell as nothing without us drilling into <em>what kind of event happened<\/em>.<\/p>\n<p>In this first tip, we\u2019ll learn to <strong>calculate the number of users per device category<\/strong>. As the concept of \u201cUser\u201d is still bound to a unique browser client ID, if the same person visited the website on two different browser instances or devices, they would be counted as two users.<\/p>\n<p>This is what the query looks like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-SQL\" data-lang=\"SQL\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">CASE<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> device.category = <span style=\"color:#a50\">\"desktop\"<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"desktop\"<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> device.category = <span style=\"color:#a50\">\"tablet\"<\/span> <span style=\"color:#00a\">AND<\/span> app_info.id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NULL<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"tablet-web\"<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> device.category = <span style=\"color:#a50\">\"mobile\"<\/span> <span style=\"color:#00a\">AND<\/span> app_info.id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NULL<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"mobile-web\"<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> device.category = <span style=\"color:#a50\">\"tablet\"<\/span> <span style=\"color:#00a\">AND<\/span> app_info.id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NOT<\/span> <span style=\"color:#00a\">NULL<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"tablet-app\"<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> device.category = <span style=\"color:#a50\">\"mobile\"<\/span> <span style=\"color:#00a\">AND<\/span> app_info.id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NOT<\/span> <span style=\"color:#00a\">NULL<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"mobile-app\"<\/span>\n  <span style=\"color:#00a\">END<\/span> <span style=\"color:#00a\">AS<\/span> device,\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> user_pseudo_id) <span style=\"color:#00a\">AS<\/span> users\n<span style=\"color:#00a\">FROM<\/span>\n  `dataset.analytics_accountId.events_2*`\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 639 \/ 580;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/group-by-case-output.jpg\" title=\"Group by and case output\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"580\" width=\"639\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/group-by-case-output.jpg#ZgotmplZ\" alt=\"Group by and case output\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The query itself is simple, but it makes use of the two statements covered in this chapter effectively. <code>CASE<\/code> is used to segment \u201cmobile\u201d and \u201ctablet\u201d users further into web and app groups (something you\u2019ll find useful once you start collecting data from both websites and mobile apps), and <code>GROUP BY<\/code> displays the count of unique device IDs per device category.<\/p>\n<h2 id=\"tip-2-distinct-and-having\">Tip #2: DISTINCT and HAVING<\/h2>\n<p>The next two keywords we\u2019ll cover are <code>HAVING<\/code> and <code>DISTINCT<\/code>. The first is great for filtering results based on aggregated values. The latter is used to deduplicate results to avoid calculating the same result multiple times.<\/p>\n<h3 id=\"distinct\">DISTINCT<\/h3>\n<div style=\"aspect-ratio: 1472 \/ 566;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/distinct.jpg\" title=\"DISTINCT\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"566\" width=\"1472\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/distinct.jpg#ZgotmplZ\" alt=\"DISTINCT\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The <code>DISTINCT<\/code> keyword is used to deduplicate results.<\/p>\n<h4 id=\"source-table-2\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>device_category<\/th>\n<th>session_id<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>desktop<\/td>\n<td>abc123<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>desktop<\/td>\n<td>def234<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>tablet<\/td>\n<td>efg345<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>mobile<\/td>\n<td>fgh456<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-2\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">user<\/span>,\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> device_category) <span style=\"color:#00a\">AS<\/span> device_category_count\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-2\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>device_category_count<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>1<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For example, if the user had three sessions with device categories <code>desktop<\/code>, <code>desktop<\/code>, and <code>tablet<\/code>, then a query for <code>COUNT(DISTINCT device.category)<\/code> would return <code>2<\/code>, as there are just two instances of <em>distinct<\/em> device categories.<\/p>\n<h3 id=\"having\">HAVING<\/h3>\n<div style=\"aspect-ratio: 1427 \/ 745;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/having.jpg\" title=\"HAVING\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"745\" width=\"1427\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/having.jpg#ZgotmplZ\" alt=\"HAVING\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The <code>HAVING<\/code> clause can be used at the end of the query, after all calculations have been done, to filter the results. All the rows selected for the query are still processed, even if the <code>HAVING<\/code> statement strips out some of them from the result table.<\/p>\n<h4 id=\"source-table-3\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>device_category<\/th>\n<th>session_id<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>desktop<\/td>\n<td>abc123<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>desktop<\/td>\n<td>def234<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>tablet<\/td>\n<td>efg345<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>mobile<\/td>\n<td>fgh456<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-3\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">user<\/span>,\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> device_category) <span style=\"color:#00a\">AS<\/span> device_category_count\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span> \n  <span style=\"color:#099\">1<\/span>\n<span style=\"color:#00a\">HAVING<\/span>\n  device_category_count &gt; <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-3\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>device_category_count<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>It\u2019s similar to <code>WHERE<\/code> (see next chapter), but unlike <code>WHERE<\/code> which is used to filter the actual records that are processed by the query, <code>HAVING<\/code> is used to filter on aggregated values. In the query above, <code>device_category_count<\/code> is an aggregated count of all the distinct device categories found in the data set.<\/p>\n<h3 id=\"use-case-distinct-device-categories-per-user\">USE CASE: Distinct device categories per user<\/h3>\n<p>Since the very name, <strong>App + Web<\/strong>, implies cross-device measurement, exploring some ways of grouping and filtering data based on users with more than one device in use seems fruitful.<\/p>\n<p>The query is similar to the one in the previous chapter, but this time instead of grouping by device category, we\u2019re grouping by user and counting the number of unique device categories each user has visited the site with. We\u2019re filtering the data to only include users with more than one device category in the data set.<\/p>\n<p>This is an exploratory query. You can then extend it to actually detect different models instead of just using device category. Device category is a fickle dimension to use, as in the example dataset used for this article, many times a device labelled as \u201cApple iPhone\u201d was actually counted as a Desktop device.<\/p>\n<p>See <a href=\"https:\/\/medium.com\/@optimiseordie\/rapid-mining-for-ecommerce-conversion-53d1aab2e21\">this article by Craig Sullivan<\/a> to understand how messed up device attribution in web analytics actually is.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-SQL\" data-lang=\"SQL\"><span style=\"color:#00a\">SELECT<\/span>\n  user_pseudo_id,\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> device.category) <span style=\"color:#00a\">AS<\/span> used_devices_count,\n  STRING_AGG(<span style=\"color:#00a\">DISTINCT<\/span> device.category) <span style=\"color:#00a\">AS<\/span> distinct_devices,\n  STRING_AGG(device.category) <span style=\"color:#00a\">AS<\/span> devices\n<span style=\"color:#00a\">FROM<\/span>\n  `dataset.analytics_accountId.events_2*`\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span>\n<span style=\"color:#00a\">HAVING<\/span>\n  used_devices_count &gt; <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 971 \/ 676;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/cross-device-query.jpg\" title=\"cross-device query\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"676\" width=\"971\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/cross-device-query.jpg#ZgotmplZ\" alt=\"cross-device query\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As a bonus, you can see how <code>STRING_AGG<\/code> can be used to concatenate multiple values into a single column. This is useful for identifying patterns that emerge across multiple rows of data!<\/p>\n<h2 id=\"tip-3-where\">Tip #3: WHERE<\/h2>\n<h3 id=\"where\">WHERE<\/h3>\n<div style=\"aspect-ratio: 1391 \/ 733;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/where.jpg\" title=\"WHERE\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"733\" width=\"1391\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/where.jpg#ZgotmplZ\" alt=\"WHERE\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you want to filter the records against which you\u2019ll run your query, <code>WHERE<\/code> is your best friend. As it filters the records that are processed, it\u2019s also a great way to reduce the (performance and monetary) cost of your queries.<\/p>\n<h4 id=\"source-table-4\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>session_id<\/th>\n<th>landing_page_path<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>abc123<\/td>\n<td>\/home\/<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>bcd234<\/td>\n<td>\/purchase\/<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>cde345<\/td>\n<td>\/home\/<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>def456<\/td>\n<td>\/contact-us\/<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-4\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  *\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span>\n<span style=\"color:#00a\">WHERE<\/span>\n  <span style=\"color:#00a\">user<\/span> = <span style=\"color:#a50\">'12345'<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-4\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>session_id<\/th>\n<th>landing_page_path<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>abc123<\/td>\n<td>\/home\/<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>bcd234<\/td>\n<td>\/purchase\/<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The <code>WHERE<\/code> clause is used to filter the rows against which the rest of the query is made. It is introduced directly after the <code>FROM<\/code> statement, and it reads as \u201creturn all the rows in the <code>FROM<\/code> table that match the condition of the <code>WHERE<\/code> clause\u201d.<\/p>\n<p>Do note that <code>WHERE<\/code> <strong>can\u2019t be used with aggregate values<\/strong>. So if you\u2019ve done any calculations with the rows returned from the table, you need to use <code>HAVING<\/code> instead.<\/p>\n<p>Due to this, <code>WHERE<\/code> is less expensive in terms of query processing than <code>HAVING<\/code>.<\/p>\n<h3 id=\"use-case-engaged-users\">USE CASE: Engaged users<\/h3>\n<p>As mentioned before, <strong>App + Web<\/strong> is event-driven. With Firebase Analytics, Google introduced a number of <a href=\"https:\/\/support.google.com\/firebase\/answer\/6317485?hl=en\">automatically collected<\/a>, pre-defined events to help users gather useful data from their apps and websites without having to flex a single muscle.<\/p>\n<p>One such event is <strong>user_engagement<\/strong>. This is fired when the user has engaged with the website or app for a specified period of time.<\/p>\n<p>Since this is readily available as a custom event, we can create a simple query that uses the <code>WHERE<\/code> clause to return only those records where the user was engaged.<\/p>\n<p>The key to <code>WHERE<\/code> is that it\u2019s executed after the <code>FROM<\/code> clause (which specifies the table to be queried). If a row doesn\u2019t match the condition in <code>WHERE<\/code>, it won\u2019t be used to match the rest of the query against.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  event_date,\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> user_pseudo_id) <span style=\"color:#00a\">AS<\/span> engaged_users\n<span style=\"color:#00a\">FROM<\/span>\n  `dataset.analytics_accountId.events_20190922`\n<span style=\"color:#00a\">WHERE<\/span>\n  event_name = <span style=\"color:#a50\">\"user_engagement\"<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 645 \/ 533;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/engaged-users.jpg\" title=\"Engaged users\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"533\" width=\"645\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/engaged-users.jpg#ZgotmplZ\" alt=\"Engaged users\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>See how we\u2019re including data from just one date? We\u2019re also only including <code>DISTINCT<\/code> users, so if a user was engaged more than once during the day we\u2019re only counting them once.<\/p>\n<h2 id=\"tip-4-subqueries-and-analytic-functions\">Tip #4: SUBQUERIES and ANALYTIC functions<\/h2>\n<p>A <strong>subquery<\/strong> in SQL means a query within a query. They can emerge in multiple different places, such as within <code>SELECT<\/code> clauses, <code>FROM<\/code> clauses, and <code>WHERE<\/code> clauses.<\/p>\n<p><strong>Analytic<\/strong> functions let you do calculations that cover <em>other<\/em> rows than just the one that is being processed. It\u2019s similar to aggregations such as <code>SUM<\/code> and <code>AVG<\/code> except it doesn\u2019t result in rows getting grouped into a single result. That\u2019s why analytic functions are perfectly suited for things like <em>running totals\/averages<\/em> or, in analytics contexts, determining session boundaries by looking at first and last timestamps, for example.<\/p>\n<h3 id=\"subquery\">SUBQUERY<\/h3>\n<div style=\"aspect-ratio: 1427 \/ 777;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/subquery.jpg\" title=\"SUBQUERY\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"777\" width=\"1427\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/subquery.jpg#ZgotmplZ\" alt=\"SUBQUERY\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The point of a subquery is to run calculations on table data, and return the result of those calculations to the enclosing query. This lets you logically organize your queries, and it makes it possible to do calculations on calculations, which would not be possible in a single query.<\/p>\n<h4 id=\"source-table-5\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>event_name<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>session_start<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>user_engagement<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>user_engagement<\/td>\n<\/tr>\n<tr>\n<td>45678<\/td>\n<td>link_click<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-5\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> <span style=\"color:#00a\">user<\/span>) <span style=\"color:#00a\">AS<\/span> all_users,\n  (\n    <span style=\"color:#00a\">SELECT<\/span>\n      <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> <span style=\"color:#00a\">user<\/span>)\n    <span style=\"color:#00a\">FROM<\/span>\n      <span style=\"color:#00a\">table<\/span>\n    <span style=\"color:#00a\">WHERE<\/span>\n      event_name = <span style=\"color:#a50\">'user_engagement'<\/span>\n  ) <span style=\"color:#00a\">AS<\/span> engaged_users\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-5\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>all_users<\/th>\n<th>engaged_users<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>4<\/td>\n<td>2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In this example, the subquery is within the <code>SELECT<\/code> statement, meaning the subquery result is bundled into a single column of the main query.<\/p>\n<p>Here, the <code>engaged_users<\/code> column retrieves the count of all distinct user IDs from the table, where these users had an event named <code>user_engagement<\/code> collected at any time.<\/p>\n<p>The main query then combines this with a count of all distinct user IDs without any restrictions, and thus you get both counts in the same table.<\/p>\n<p>You couldn\u2019t have achieved with just the main query, since the <code>WHERE<\/code> statement applies to all <code>SELECT<\/code> columns in the table. That\u2019s why we needed the subquery &#8211; we had to use a <code>WHERE<\/code> statement that only applied to the <code>engaged_users<\/code> column.<\/p>\n<h3 id=\"analytic-functions\">ANALYTIC FUNCTIONS<\/h3>\n<div style=\"aspect-ratio: 1536 \/ 785;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/analytic-function.jpg\" title=\"Analytic function\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"785\" width=\"1536\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/analytic-function.jpg#ZgotmplZ\" alt=\"Analytic function\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Analytic functions can be really difficult to understand, since with SQL you\u2019re used to going over the table row-by-row, and comparing the query against each row one at a time.<\/p>\n<p>With an analytic function, you stretch this logic a bit. The query still goes over the source table row-by-row, but this time you can reference other rows when doing calculations.<\/p>\n<h4 id=\"source-table-6\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>event_name<\/th>\n<th>event_timestamp<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1001<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1002<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>session_start<\/td>\n<td>1012<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>user_engagement<\/td>\n<td>1009<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>click<\/td>\n<td>1000<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-6\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  event_name,\n  <span style=\"color:#00a\">user<\/span>,\n  event_timestamp\n<span style=\"color:#00a\">FROM<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    <span style=\"color:#00a\">user<\/span>,\n    event_name,\n    event_timestamp,\n    RANK() OVER (PARTITION <span style=\"color:#00a\">BY<\/span> event_name <span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span> event_timestamp) <span style=\"color:#00a\">AS<\/span> rank\n  <span style=\"color:#00a\">FROM<\/span>\n    <span style=\"color:#00a\">table<\/span>\n) \n<span style=\"color:#00a\">WHERE<\/span>\n  rank = <span style=\"color:#099\">1<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-6\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>event_name<\/th>\n<th>user<\/th>\n<th>event_timestamp<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>click<\/td>\n<td>34567<\/td>\n<td>1000<\/td>\n<\/tr>\n<tr>\n<td>user_engagement<\/td>\n<td>23456<\/td>\n<td>1009<\/td>\n<\/tr>\n<tr>\n<td>session_start<\/td>\n<td>23456<\/td>\n<td>1012<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In this query, we take each event and see which user sent the <em>first<\/em> such event in the table.<\/p>\n<p>For each row in the table, the <code>RANK() OVER (PARTITION BY event_name ORDER BY event_timestamp) AS rank<\/code> is run. The <em>partition<\/em> is basically a \u201creference\u201d table with all the event names matching the current row\u2019s event name, and their respective timestamps.<\/p>\n<p>These timestamps are then ordered in ascending order (within the partition). The <code>RANK() OVER<\/code> part of the function returns the current event name\u2019s <em>rank<\/em> in this table.<\/p>\n<p>To walk through an example, let\u2019s say the query engine encounters the first row of the table. The <code>RANK() OVER (PARTITION BY event_name ORDER BY event_timestamp)<\/code> creates the reference table that looks like this for the first row:<\/p>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>event_name<\/th>\n<th>event_timestamp<\/th>\n<th>rank<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1000<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1001<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1002<\/td>\n<td>3<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The query then checks how the current row matches against this partition, and returns <code>2<\/code> as the first row in the table was rank <code>2<\/code> of its partition.<\/p>\n<p>This partition is ephemeral &#8211; it\u2019s <strong>only<\/strong> used to calculate the result of the analytic function.<\/p>\n<p>For the purposes of this exercise, this analytic function is furthermore done in a subquery, so that the main query can filter the result using a <code>WHERE<\/code> for just those items that had rank <code>1<\/code> (first timestamp of any given event).<\/p>\n<p>I recommend checking out the first paragraphs in <a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/reference\/standard-sql\/analytic-function-concepts\">this<\/a> document &#8211; it explains how the partitioning works.<\/p>\n<h3 id=\"use-case-first-interaction-per-event\">USE CASE: First interaction per event<\/h3>\n<p>Let\u2019s extend the example from above into the App + Web dataset.<\/p>\n<p>We\u2019ll create a list of client IDs together with the event name, the timestamp, and the timestamp converted to a readable (date) format.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  user_pseudo_id,\n  event_name,\n  event_timestamp,\n  DATETIME(TIMESTAMP_MICROS(event_timestamp), <span style=\"color:#a50\">\"Europe\/Helsinki\"<\/span>) <span style=\"color:#00a\">AS<\/span> <span style=\"color:#0aa\">date<\/span>\n<span style=\"color:#00a\">FROM<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    user_pseudo_id,\n    event_name,\n    event_timestamp,\n    RANK() OVER (PARTITION <span style=\"color:#00a\">BY<\/span> event_name <span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span> event_timestamp) <span style=\"color:#00a\">AS<\/span> rank\n  <span style=\"color:#00a\">FROM<\/span>\n    `dataset.analytics_accountId.events_20190922` )\n<span style=\"color:#00a\">WHERE<\/span>\n  rank = <span style=\"color:#099\">1<\/span>\n<span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span>\n  event_timestamp<\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 654 \/ 749;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/first-interaction-timestamp.jpg\" title=\"First interaction timestamp\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"749\" width=\"654\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/first-interaction-timestamp.jpg#ZgotmplZ\" alt=\"First interaction timestamp\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The logic is exactly the same as in the introduction to analytic functions above. The only difference is how we use the <code>DATETIME<\/code> and <code>TIMESTAMP_MICROS<\/code> to turn the UNIX timestamp (stored in BigQuery) into a readable date format.<\/p>\n<p>Don\u2019t worry &#8211; analytic functions in particular are a tough concept to understand. Play around with the different analytic functions to get an idea of how they work with the source idea.<\/p>\n<p>There are quite a few articles and videos online that explain the concept further, so I recommend checking out the web for more information. We will also return to analytic functions many, many times in future #BigQueryTips posts.<\/p>\n<h2 id=\"tip-5-unnest-and-cross-join\">Tip #5: UNNEST and CROSS JOIN<\/h2>\n<p>The dataset exported by App + Web does not end up in a relational database where we could use the <code>JOIN<\/code> key to quickly pull in extra information about pages, sessions, and users.<\/p>\n<p>Instead, BigQuery arranges data in nested and repeated fields &#8211; that\u2019s how it can have a single row represent all the hits of a session (as in the Google Analytics dataset).<\/p>\n<p>The problem with this approach is that it\u2019s not too intuitive to access these nested values.<\/p>\n<p>Enter <code>UNNEST<\/code>, particularly when coupled with <code>CROSS JOIN<\/code>.<\/p>\n<p><code>UNNEST<\/code> means that the nested structure is actually expanded so that each item within can be joined with the rest of the columns in the row. This results in the single row becoming multiple rows, where each row corresponds to one value in the nested structure. This column-to-rows is achieved with a <code>CROSS JOIN<\/code>, where every item in the unnested structure is joined with each column in the rest of the table.<\/p>\n<h3 id=\"unnest-and-cross-join\">UNNEST and CROSS JOIN<\/h3>\n<div style=\"aspect-ratio: 1380 \/ 450;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/unnest-cross-join.jpg\" title=\"UNNEST CROSS JOIN\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"450\" width=\"1380\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/unnest-cross-join.jpg#ZgotmplZ\" alt=\"UNNEST CROSS JOIN\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The two keywords go intrinsically together, so we\u2019ll treat them as such.<\/p>\n<h4 id=\"source-table-7\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>timestamp<\/th>\n<th>event<\/th>\n<th>params.key<\/th>\n<th>params.value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1000<\/td>\n<td>click<\/td>\n<td>time<br \/>type<br \/>device<\/td>\n<td>10<br \/>right-click<br \/>iPhone<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-7\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">timestamp<\/span>,\n  event,\n  event_params.<span style=\"color:#00a\">key<\/span> <span style=\"color:#00a\">AS<\/span> event_params_key\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span> <span style=\"color:#00a\">AS<\/span> t\n  <span style=\"color:#00a\">CROSS<\/span> <span style=\"color:#00a\">JOIN<\/span> <span style=\"color:#00a\">UNNEST<\/span> (t.params) <span style=\"color:#00a\">AS<\/span> event_params<\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-7\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>timestamp<\/th>\n<th>event<\/th>\n<th>event_params_key<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1000<\/td>\n<td>click<\/td>\n<td>time<\/td>\n<\/tr>\n<tr>\n<td>1000<\/td>\n<td>click<\/td>\n<td>type<\/td>\n<\/tr>\n<tr>\n<td>1000<\/td>\n<td>click<\/td>\n<td>device<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>See what happened? The nested structure within <code>params<\/code> was <em>unnested<\/em> so that each item was treated as a separate row in its own column. Then, this unnested structure was <strong>cross-joined<\/strong> with the table. A <code>CROSS JOIN<\/code> combines every row from <code>table<\/code> with every row from the unnested structure.<\/p>\n<p>This is how you end up with a structure that you can then use for calculations in your main query.<\/p>\n<p>It\u2019s a bit complicated since you always need to do the <code>UNNEST<\/code> &#8211; <code>CROSS JOIN<\/code> exercise, but once you\u2019ve done it a few times it should become second nature.<\/p>\n<p>In fact, there\u2019s a shorthand for writing the <code>CROSS JOIN<\/code> that might make things easier to read (or not). You can replace the <code>CROSS JOIN<\/code> statement with a comma. This is what the example query would look like when thus modified.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">timestamp<\/span>,\n  event,\n  event_params.<span style=\"color:#00a\">key<\/span> <span style=\"color:#00a\">AS<\/span> event_params_key\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span> <span style=\"color:#00a\">AS<\/span> t,\n  <span style=\"color:#00a\">UNNEST<\/span> (t.params) <span style=\"color:#00a\">AS<\/span> event_params<\/code><\/pre>\n<\/div>\n<h3 id=\"use-case-pageviews-and-unique-pageviews-per-user\">USE CASE: Pageviews and Unique Pageviews per user<\/h3>\n<p>Let\u2019s look at a query for getting the count of pageviews and unique pageviews per user.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  event_date,\n  event_params.value.string_value <span style=\"color:#00a\">as<\/span> page_url,\n  <span style=\"color:#00a\">COUNT<\/span>(*) <span style=\"color:#00a\">AS<\/span> pageviews,\n  <span style=\"color:#00a\">COUNT<\/span>(<span style=\"color:#00a\">DISTINCT<\/span> user_pseudo_id) <span style=\"color:#00a\">AS<\/span> unique_pageviews\n<span style=\"color:#00a\">FROM<\/span>\n  `dataset.analytics_accountId.events_20190922` <span style=\"color:#00a\">AS<\/span> t,\n  <span style=\"color:#00a\">UNNEST<\/span>(t.event_params) <span style=\"color:#00a\">AS<\/span> event_params\n<span style=\"color:#00a\">WHERE<\/span>\n  event_params.<span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"page_location\"<\/span>\n  <span style=\"color:#00a\">AND<\/span> event_name = <span style=\"color:#a50\">\"page_view\"<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span>,\n  <span style=\"color:#099\">2<\/span>\n<span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">3<\/span> <span style=\"color:#00a\">DESC<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 932 \/ 622;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/pageviews.jpg\" title=\"Page views\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"622\" width=\"932\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/pageviews.jpg#ZgotmplZ\" alt=\"Page views\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In the <code>FROM<\/code> statement we define the table as usual, and we give it an alias <code>t<\/code> (good practice, as it helps keep the queries lean).<\/p>\n<p>The next step is to <code>UNNEST<\/code> one of those nested fields, and then <code>CROSS JOIN<\/code> it with the rest of our table. The <code>WHERE<\/code> clause is used to make sure only rows that have the <code>page_location<\/code> key and the <code>page_view<\/code> event type are included in the final table.<\/p>\n<p>Finally, we <code>GROUP BY<\/code>  the event date and page URL, and then we sort everything by the count of pageviews in descending order.<\/p>\n<p>Conceptually, the <code>UNNEST<\/code> and <code>CROSS JOIN<\/code> create a new table that has a crazy number of rows, as every row in the original table is multiplied by the number of rows in the <code>event_params<\/code> nested structure. The <code>WHERE<\/code> clause is thus your friend in making sure you only analyze the data that answers the question you have.<\/p>\n<h2 id=\"tip-6-withas-and-left-join\">Tip #6: WITH\u2026AS and LEFT JOIN<\/h2>\n<p>The final keywords we\u2019ll go through are <code>WITH...AS<\/code> and <code>LEFT JOIN<\/code>. The first lets you create a type of subquery that you can reference in your other queries as a separate table. The second lets you join two datasets together while preserving rows that do not match against the join condition.<\/p>\n<h3 id=\"withas\">WITH\u2026AS<\/h3>\n<div style=\"aspect-ratio: 1415 \/ 269;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/with-as.jpg\" title=\"WITH...AS\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"269\" width=\"1415\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/with-as.jpg#ZgotmplZ\" alt=\"WITH...AS\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><code>WITH...AS<\/code> is very similar conceptually to a subquery. It allows you to build a table expression that can then be used by your queries. The main difference to a subquery is that the table gets an alias, and you can then use this alias wherever you want to refer to the table\u2019s data. A subquery would need to be repeated in all places where you want to reference it, so <code>WITH<\/code> makes custom queries more accessible.<\/p>\n<h4 id=\"source-table-8\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>event_name<\/th>\n<th>event_timestamp<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1000<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>user_engagement<\/td>\n<td>1005<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1006<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>session_start<\/td>\n<td>1002<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>session_start<\/td>\n<td>1000<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>click<\/td>\n<td>1002<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-8\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">WITH<\/span> users_who_clicked <span style=\"color:#00a\">AS<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    <span style=\"color:#00a\">DISTINCT<\/span> <span style=\"color:#00a\">user<\/span>\n  <span style=\"color:#00a\">FROM<\/span>\n    <span style=\"color:#00a\">table<\/span>\n  <span style=\"color:#00a\">WHERE<\/span> event_name = <span style=\"color:#a50\">'click'<\/span>\n)\n<span style=\"color:#00a\">SELECT<\/span>\n  *\n<span style=\"color:#00a\">FROM<\/span>\n  users_who_clicked<\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-8\">QUERY RESULT<\/h4>\n<p>It\u2019s not the most mind-blowing of examples, but it should illustrate how to use table aliases. After the <code>WITH...AS<\/code> clause, you can then reference the table in e.g. <code>FROM<\/code> statements and in joins.<\/p>\n<p>See the next example where we make this more interesting.<\/p>\n<h3 id=\"left-join\">LEFT JOIN<\/h3>\n<div style=\"aspect-ratio: 1497 \/ 792;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/10\/left-join.jpg\" title=\"LEFT JOIN\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"792\" width=\"1497\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/10\/left-join.jpg#ZgotmplZ\" alt=\"LEFT JOIN\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>A <code>LEFT JOIN<\/code> is one of the most useful data joins because it allows you to account for unmatched rows as well.<\/p>\n<p>A <code>LEFT JOIN<\/code> takes <strong>all<\/strong> rows in the first table (\u201cleft\u201d table), and the rows that <strong>match<\/strong> a specific join criterion in the second table (\u201cright\u201d table). For all the rows that did not have a match, the first table is populated with a <em>null<\/em> value. Rows in the <em>second<\/em> table that do not have a match in the first table are discarded.<\/p>\n<h4 id=\"source-table-9\">SOURCE TABLE<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>event_name<\/th>\n<th>event_timestamp<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1000<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>user_engagement<\/td>\n<td>1005<\/td>\n<\/tr>\n<tr>\n<td>12345<\/td>\n<td>click<\/td>\n<td>1006<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>session_start<\/td>\n<td>1002<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>session_start<\/td>\n<td>1000<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>click<\/td>\n<td>1002<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4 id=\"sql-9\">SQL<\/h4>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">WITH<\/span> users_who_clicked <span style=\"color:#00a\">AS<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    <span style=\"color:#00a\">DISTINCT<\/span> <span style=\"color:#00a\">user<\/span>\n  <span style=\"color:#00a\">FROM<\/span>\n    <span style=\"color:#00a\">table<\/span>\n  <span style=\"color:#00a\">WHERE<\/span> event_name = <span style=\"color:#a50\">'click'<\/span>\n)\n<span style=\"color:#00a\">SELECT<\/span>\n  t1.<span style=\"color:#00a\">user<\/span>,\n  <span style=\"color:#00a\">CASE<\/span>\n    <span style=\"color:#00a\">WHEN<\/span> t2.<span style=\"color:#00a\">user<\/span> <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NOT<\/span> <span style=\"color:#00a\">NULL<\/span> <span style=\"color:#00a\">THEN<\/span> <span style=\"color:#a50\">\"true\"<\/span>\n    <span style=\"color:#00a\">ELSE<\/span> <span style=\"color:#a50\">\"false\"<\/span>\n  <span style=\"color:#00a\">END<\/span> <span style=\"color:#00a\">AS<\/span> user_did_click\n<span style=\"color:#00a\">FROM<\/span>\n  <span style=\"color:#00a\">table<\/span> <span style=\"color:#00a\">as<\/span> t1\n<span style=\"color:#00a\">LEFT<\/span> <span style=\"color:#00a\">JOIN<\/span>\n  users_who_clicked <span style=\"color:#00a\">as<\/span> t2\n<span style=\"color:#00a\">ON<\/span> \n  t1.<span style=\"color:#00a\">user<\/span> = t2.<span style=\"color:#00a\">user<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span>, <span style=\"color:#099\">2<\/span><\/code><\/pre>\n<\/div>\n<h4 id=\"query-result-9\">QUERY RESULT<\/h4>\n<table>\n<thead>\n<tr>\n<th>user<\/th>\n<th>user_did_click<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>12345<\/td>\n<td>true<\/td>\n<\/tr>\n<tr>\n<td>23456<\/td>\n<td>false<\/td>\n<\/tr>\n<tr>\n<td>34567<\/td>\n<td>true<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>With the <code>LEFT JOIN<\/code>, we take all the user IDs in the main table, and then we take all the rows in the <code>users_who_clicked<\/code> table (that we created in the <code>WITH...AS<\/code> tutorial above). For all the rows where the user ID from the main table has a match in the <code>users_who_clicked<\/code> table, we populate a new column named <code>user_did_click<\/code> with the value <strong>true<\/strong>.<\/p>\n<p>For all the user IDs that do not have a match in the <code>users_who_clicked<\/code> table, the value <strong>false<\/strong> is used instead.<\/p>\n<h4 id=\"use-case-simple-segmenting\">USE CASE: Simple segmenting<\/h4>\n<p>Let\u2019s put a bunch of things we\u2019ve now learned together, and replicate some simple segmenting functionality from Google Analytics in BigQuery.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">WITH<\/span>\n  engaged_users_table <span style=\"color:#00a\">AS<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    <span style=\"color:#00a\">DISTINCT<\/span> event_date,\n    user_pseudo_id\n  <span style=\"color:#00a\">FROM<\/span>\n    `dataset.analytics_accountId.events_20190922`\n  <span style=\"color:#00a\">WHERE<\/span>\n    EVENT_NAME = <span style=\"color:#a50\">\"user_engagement\"<\/span>),\n  pageviews_table <span style=\"color:#00a\">AS<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    <span style=\"color:#00a\">DISTINCT<\/span> event_date,\n    event_params.value.string_value <span style=\"color:#00a\">AS<\/span> page_url,\n    user_pseudo_id\n  <span style=\"color:#00a\">FROM<\/span>\n    `simoahava-com.analytics_206575074.events_20190922` <span style=\"color:#00a\">AS<\/span> t,\n    <span style=\"color:#00a\">UNNEST<\/span>(t.event_params) <span style=\"color:#00a\">AS<\/span> event_params\n  <span style=\"color:#00a\">WHERE<\/span>\n    event_params.<span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"page_location\"<\/span>\n    <span style=\"color:#00a\">AND<\/span> event_name = <span style=\"color:#a50\">\"page_view\"<\/span>)\n<span style=\"color:#00a\">SELECT<\/span>\n  t1.event_date,\n  page_url,\n  COUNTIF(t2.user_pseudo_id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NOT<\/span> <span style=\"color:#00a\">NULL<\/span>) <span style=\"color:#00a\">AS<\/span> engaged_users_visited,\n  COUNTIF(t2.user_pseudo_id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NULL<\/span>) <span style=\"color:#00a\">AS<\/span> not_engaged_users_visited\n<span style=\"color:#00a\">FROM<\/span>\n  pageviews_table <span style=\"color:#00a\">AS<\/span> t1\n<span style=\"color:#00a\">LEFT<\/span> <span style=\"color:#00a\">JOIN<\/span>\n  engaged_users_table <span style=\"color:#00a\">AS<\/span> t2\n<span style=\"color:#00a\">ON<\/span>\n  t1.user_pseudo_id = t2.user_pseudo_id\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span>,\n  <span style=\"color:#099\">2<\/span>\n<span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">3<\/span> <span style=\"color:#00a\">DESC<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 981 \/ 678;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/with-as-table.jpg\" title=\"With AS table\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"678\" width=\"981\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/with-as-table.jpg#ZgotmplZ\" alt=\"With AS table\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Let\u2019s chop this down to pieces again.<\/p>\n<p>In the query above, we have <strong>two<\/strong> table aliases created. The first one named <code>engaged_users_table<\/code> should be familiar &#8211; it\u2019s the query we built in <a href=\"#use-case-engaged-users\">tip #3<\/a>.<\/p>\n<p>The second one named <code>pageviews_table<\/code> should ring a bell as well. We built it in <a href=\"#use-case-pageviews-and-unique-pageviews-per-user\">tip #5<\/a>.<\/p>\n<p>We create these as table aliases so that we can use them in the subsequent join.<\/p>\n<p>Now, let\u2019s look at the rest of the query:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  t1.event_date,\n  page_url,\n  COUNTIF(t2.user_pseudo_id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NOT<\/span> <span style=\"color:#00a\">NULL<\/span>) <span style=\"color:#00a\">AS<\/span> engaged_users_visited,\n  COUNTIF(t2.user_pseudo_id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NULL<\/span>) <span style=\"color:#00a\">AS<\/span> not_engaged_users_visited\n<span style=\"color:#00a\">FROM<\/span>\n  pageviews_table <span style=\"color:#00a\">AS<\/span> t1\n<span style=\"color:#00a\">LEFT<\/span> <span style=\"color:#00a\">JOIN<\/span>\n  engaged_users_table <span style=\"color:#00a\">AS<\/span> t2\n<span style=\"color:#00a\">ON<\/span>\n  t1.user_pseudo_id = t2.user_pseudo_id\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span>,\n  <span style=\"color:#099\">2<\/span>\n<span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">3<\/span> <span style=\"color:#00a\">DESC<\/span><\/code><\/pre>\n<\/div>\n<p>Focus on the part after the <code>FROM<\/code> clause. Here, we take the two tables, and we <code>LEFT JOIN<\/code> them. The <code>pageviews_table<\/code> is the \u201cleft\u201d table, and <code>engaged_users<\/code> is the \u201cright\u201d table.<\/p>\n<p><code>LEFT JOIN<\/code> takes <strong>all the rows<\/strong> in the \u201cleft\u201d table and the <strong>matching rows<\/strong> from the \u201cright\u201d table. The matching criterion is established in the <code>ON<\/code> clause. Thus, a match is made if the <code>user_pseudo_id<\/code> is the same between the two tables.<\/p>\n<p>To illustrate, this is what a simple <code>LEFT JOIN<\/code> without any calculations or aggregations would look like:<\/p>\n<div style=\"aspect-ratio: 774 \/ 347;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/09\/left-join-example.jpg\" title=\"LEFT JOIN example\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"347\" width=\"774\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/09\/left-join-example.jpg#ZgotmplZ\" alt=\"LEFT JOIN example\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Here you can see that on rows <strong>1<\/strong>, <strong>9<\/strong>, and <strong>10<\/strong> there was no match for the <code>user_pseudo_id<\/code> in the <code>engaged_users<\/code> table. This means that the user who dispatched this pageview was not considered \u201cengaged\u201d in the date range selected for analysis.<\/p>\n<p>If you\u2019re wondering what <code>COUNTIF<\/code> does, then check out these two statements detail:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\">COUNTIF(\n  t2.user_pseudo_id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NOT<\/span> <span style=\"color:#00a\">NULL<\/span>\n) <span style=\"color:#00a\">AS<\/span> engaged_users_visited,\n\nCOUNTIF(\n  t2.user_pseudo_id <span style=\"color:#00a\">IS<\/span> <span style=\"color:#00a\">NULL<\/span>\n) <span style=\"color:#00a\">AS<\/span> not_engaged_users_visited<\/code><\/pre>\n<\/div>\n<p>The first one increments the <code>engaged_users_visited<\/code> counter by one for all the rows where the user visited the page AND had a row in the <code>engaged_users<\/code> table.<\/p>\n<p>The second one increments the <code>not_engaged_users_visited<\/code> counter by one for all the rows where the user visited the page and DID NOT have a row in the <code>engaged_users<\/code> table.<\/p>\n<p>We can do this because the <code>LEFT JOIN<\/code> leaves the <code>t2.*<\/code> columns <code>null<\/code> in case the user ID from the <code>pageviews_table<\/code> was not found in the <code>engaged_users_table<\/code>.<\/p>\n<h2 id=\"summary\">SUMMARY<\/h2>\n<p>Phew!<\/p>\n<p>That wasn\u2019t a quick foray into BigQuery after all.<\/p>\n<p>However, if you already possess a rudimentary understanding of SQL (and if you don\u2019t, just take a look at the <a href=\"https:\/\/mode.com\/sql-tutorial\/\">Mode Analytics<\/a> tutorial), then the most complex things you need to wrap your head around are the ways in which BigQuery leverages its unique columnar format.<\/p>\n<p>It\u2019s quite different to a relational database, where joins are the most powerful tool in your kit for running analyses against different data structure.<\/p>\n<p>In BigQuery, you need to understand the <a href=\"#tip-5-unnest-and-cross-join\">nested structures and how to <code>UNNEST<\/code> them<\/a>. This, for many, is a difficult concept to grasp, but once you consider how <code>UNNEST<\/code> and <code>CROSS JOIN<\/code> simply populate the table as if it had been a flat structure all along, it should help you build the queries you want.<\/p>\n<p><a href=\"#analytic-functions\">Analytic functions<\/a> are arguably the coolest thing since Arnold Schwarzenegger played Mr. Freeze. They let you do aggregations and calculations without having to build a complicated mess of subqueries across all the rows you want to process. We\u2019ve only explored a very simple use case here, but in subsequent articles we\u2019ll definitely cover these in more detail.<\/p>\n<p>Finally, huge thanks to Pawel for providing the SQL examples and walkthroughs. Simo contributed some enhancements to the queries, but his contribution is mainly editorial. Thus, direct all criticism and suggestions for improvement to him, and him alone.<\/p>\n<p>If you\u2019re an analyst working with Google Analytics and thinking about what you should do to improve your skill set, we recommend learning SQL and then going to town on either any one of the copious BigQuery sample datasets out there, or on your very own <a href=\"https:\/\/www.simoahava.com\/analytics\/enable-bigquery-export-google-analytics-app-web\/\">App + Web export<\/a>.<\/p>\n<p>Let us know in the comments if questions arose from this guide!<\/p>\n<\/p><\/div>\n<p><script async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><br \/>\n<br \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I\u2019ve thoroughly enjoyed writing short (and sometimes a bit longer) bite-sized tips for my #GTMTips topic. With the advent of Google Analytics: App + Web and particularly the opportunity to access raw data through BigQuery, I thought it was a good time to get started on a new tip topic: #BigQueryTips. For Universal Analytics, getting [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":93830,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[12765,4364,44761,1531,2059,44762,5393],"dealstore":[],"offerexpiration":[],"class_list":["post-93829","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-analytics","tag-app","tag-bigquerytips","tag-google","tag-guide","tag-query","tag-web"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>#BigQueryTips: Query Guide To Google Analytics: App + Web - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=93829\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"#BigQueryTips: Query Guide To Google Analytics: App + Web - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"I\u2019ve thoroughly enjoyed writing short (and sometimes a bit longer) bite-sized tips for my #GTMTips topic. With the advent of Google Analytics: App + Web and particularly the opportunity to access raw data through BigQuery, I thought it was a good time to get started on a new tip topic: #BigQueryTips. For Universal Analytics, getting [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=93829\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-17T15:09:09+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1699\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"24 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=93829#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=93829\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"#BigQueryTips: Query Guide To Google Analytics: App + Web\",\"datePublished\":\"2025-02-17T15:09:09+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=93829\"},\"wordCount\":3961,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=93829#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg\",\"keywords\":[\"Analytics\",\"APP\",\"BigQueryTips\",\"Google\",\"Guide\",\"Query\",\"Web\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=93829#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=93829\",\"url\":\"https:\/\/fivemor.com\/?p=93829\",\"name\":\"#BigQueryTips: Query Guide To Google Analytics: App + Web - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=93829#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=93829#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg\",\"datePublished\":\"2025-02-17T15:09:09+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=93829#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=93829\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=93829#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg\",\"width\":2560,\"height\":1699},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=93829#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"#BigQueryTips: Query Guide To Google Analytics: App + Web\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"#BigQueryTips: Query Guide To Google Analytics: App + Web - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=93829","og_locale":"en_US","og_type":"article","og_title":"#BigQueryTips: Query Guide To Google Analytics: App + Web - Som2ny Network","og_description":"I\u2019ve thoroughly enjoyed writing short (and sometimes a bit longer) bite-sized tips for my #GTMTips topic. With the advent of Google Analytics: App + Web and particularly the opportunity to access raw data through BigQuery, I thought it was a good time to get started on a new tip topic: #BigQueryTips. For Universal Analytics, getting [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=93829","og_site_name":"Som2ny Network","article_published_time":"2025-02-17T15:09:09+00:00","og_image":[{"width":2560,"height":1699,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"24 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=93829#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=93829"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"#BigQueryTips: Query Guide To Google Analytics: App + Web","datePublished":"2025-02-17T15:09:09+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=93829"},"wordCount":3961,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=93829#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg","keywords":["Analytics","APP","BigQueryTips","Google","Guide","Query","Web"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=93829#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=93829","url":"https:\/\/fivemor.com\/?p=93829","name":"#BigQueryTips: Query Guide To Google Analytics: App + Web - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=93829#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=93829#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg","datePublished":"2025-02-17T15:09:09+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=93829#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=93829"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=93829#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/bigquery-guide-app-web-scaled.jpg","width":2560,"height":1699},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=93829#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"#BigQueryTips: Query Guide To Google Analytics: App + Web"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/93829","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=93829"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/93829\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/93830"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=93829"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=93829"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=93829"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=93829"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=93829"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}