<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/">
	<channel>
		<title>Author at Infinum</title>
		<atom:link href="https://infinum.com/blog/author/josip-sadek/feed/" rel="self" type="application/rss+xml" />
		<link></link>
		<description>Building digital products</description>
		<lastBuildDate>Fri, 03 Apr 2026 12:58:20 +0000</lastBuildDate>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>

					<item>
				<image>
					<url>41656https://infinum.com/uploads/2023/08/User-retention-blogpost-img-min.webp</url>
				</image>
				<title>Uncover User Engagement with a Customer Retention Dashboard</title>
				<link>https://infinum.com/blog/customer-retention-dashboard/</link>
				<pubDate>Wed, 16 Aug 2023 12:03:34 +0000</pubDate>
				<dc:creator>Josip Šadek</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=41656</guid>
				<description>
					<![CDATA[<p>Help your clients make data-driven decisions by crafting an effective customer retention dashboard, a crucial tool for the success of any digital product. </p>
<p>The post <a href="https://infinum.com/blog/customer-retention-dashboard/">Uncover User Engagement with a Customer Retention Dashboard</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-123"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-92">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-95"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-93">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-94'
	>
	<strong>Knowing whether users keep coming back is crucial to the long-term success of any digital product. Help your clients make data-driven decisions by crafting an effective customer retention dashboard. </strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-98"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-96">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-97'
	>
	We often hear about companies collecting our data while we use their products. However, we rarely hear about what can be done with that data. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-101"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-99">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-100'
	>
	First of all, user data can inform us about the number of users our product has. But more importantly, it will tell us how well we retain those users on our site or using our service. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-104"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-102">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-103'
	>
	Knowing whether users keep coming back is crucial to the long-term success of any digital product. This article will show you one way to retrieve and visualize your customer retention data with the help of a customer retention dashboard.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-107"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-105">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-106'
	>
	What is a customer retention dashboard?</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-110"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-108">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-109'
	>
	A customer retention dashboard is a dashboard that shows how successfully new users are retained on a site or service. Customer retention is an important <a href="https://infinum.com/blog/product-success-metrics/" target="_blank" rel="noreferrer noopener">product success metric</a> that shows the staying power of a product. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-113"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-111">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-112'
	>
	Let&#8217;s say in January we gained 100 new users. A customer retention dashboard will tell us how many users out of those 100 are still active in February, March, and the following months (see the image below). <br />
<br />
This can be very useful when we want to know if we are actually meeting users’ needs and how active they are.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-115"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-114'
	>
	If we manage to attract new users but lose many of them within a month, then the question is: are we actually meeting their needs, and are they really satisfied with our product?</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-118"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-116">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-117'
	>
	It’s important to note that the customer retention dashboard doesn’t show us where issues affecting customer retention might lie, but it does show us how engaged users are. If we have a low retention rate, then we need to conduct various analyses to <a href="https://infinum.com/blog/product-success-metrics/" target="_blank" rel="noreferrer noopener">find out why users are not engaged with our product</a>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-121"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-119">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-120'
	>
	In the image below, you can see an example of the dashboard we are going to build in this blog.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-126"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-124"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-125">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2023/08/User-retention-dashboard-tutorial-in-article-visual-2-min-1400x903.webp				media='(max-width: 699px)'
				type=image/webp								height="903"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2023/08/User-retention-dashboard-tutorial-in-article-visual-2-min-2400x1548.webp				media='(max-width: 1199px)'
				type=image/webp								height="1548"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2023/08/User-retention-dashboard-tutorial-in-article-visual-2-min.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1754"
															width="2720"
										loading="lazy"
					 />
					</picture>

			<figcaption class="image__figcaption block-media__image-figcaption">
			AN EXAMPLE OF THE DASHBOARD WE ARE GOING TO BUILD		</figcaption>
	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-284"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-127">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-130"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-128">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-129'
	>
	Getting the definitions out of the way</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-133"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-131">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-132'
	>
	Before we dive into the code, let’s define a few things so that we are all on the same page.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-136"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-134">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-135'
	>
	These are some of the terms we’ll be using:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-139"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-137">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-138'
	>
	<li><strong>Cohort</strong> – in this context, a group of users that share the same characteristics. In our case, the date of first usage is the characteristic with which we will group users.</li><li><strong>CTE or </strong><a href="https://www.geeksforgeeks.org/cte-in-sql/" target="_blank" rel="noreferrer noopener"><strong>Common Table Expression</strong></a> – a temporary table that is created by SELECT clause.</li><li><a href="https://www.freecodecamp.org/news/window-functions-in-sql/" target="_blank" rel="noreferrer noopener"><strong>Window function</strong></a> – an aggregate and ranking function over a particular window (set of rows).</li><li><a href="https://www.geeksforgeeks.org/upsert-operation-in-sql-server/" target="_blank" rel="noreferrer noopener"><strong>Upsert</strong></a> – a SQL clause that uses a combination of insert and update.</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-142"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-140">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-141'
	>
	For this blog post, we will use mock data that represents the usage of 10 users using a non-existent application. If you want to code along, you can download the data from our repository at <a href="https://github.com/infinum/data-engineering-public-content/tree/main/Blog_HowToDevelopUserRetentionDashboard" target="_blank" rel="noreferrer noopener">GitHub</a>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-145"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-143">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-144'
	>
	The source table we’ll use is based on the standard Firebase table schema, more on which you can learn in the official <a href="https://support.google.com/firebase/answer/7029846?hl=en&amp;ref_topic=7029512&amp;sjid=6826812808947906301-EU" target="_blank" rel="noreferrer noopener">Firebase docs</a>. The main differences between a real table and the table we are going to use are that:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-148"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-146">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-147'
	>
	<li>Our source table will only have the column that we actually need</li><li>This data is something of a &#8220;perfect&#8221; case. It doesn’t have various edge cases that you would have with real data. The purpose here is to create an illustrative starting point for creating the customer retention dashboard</li><li>In this non-existent application, we will only have default events. We won&#8217;t add more complexity with custom events</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-151"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-149">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-150'
	>
	Viewing user data</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-154"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-152">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-153'
	>
	Let&#8217;s imagine a situation where a stakeholder asks us to create a customer retention dashboard. However, the stakeholder has three requirements: </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-157"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-155">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-156'
	>
	<li>They want to be able to filter data by user location and analytics consent</li><li>They would like to see a graph with actual numbers and another one with percentages</li><li>They want to be able to observe the trend on a monthly basis</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-160"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-158">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-159'
	>
	Before every data-field project, we should ask ourselves whether we have the data that can support the client’s requirements. Based on these requirements, we need to collect the user activity data (scrolling, clicks, likes, screen view, etc.) and the source for the attributes by which stakeholders will filter. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-163"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-161">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-162'
	>
	Every project is different and can be set up in a different way, so you need to look at your infrastructure, see where your data is stored, and know how you can access it. We’ve based this tutorial on the Firebase Analytics service since it is well-known and widely used.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-166"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-164">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-165'
	>
	If you want to use Firebase you first need to do two important things. First, you have to integrate Firebase SDK with your application so that it can collect all the relevant events (the user data). Then, you will need to integrate Firebase with your warehouse so that the data can be stored in permanent storage. Firebase offers very seamless integration with Google Big Query, and that is the warehouse that we are going to use in this blog.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-169"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-167">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-168'
	>
	Using Firebase</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-172"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-170">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-171'
	>
	When we integrate Firebase with BigQuery, we will see a table that has a lot of columns. Let’s go through the columns that we need in order to successfully create a customer retention dashboard.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-175"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-173">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-174'
	>
	<li><strong>Event date column</strong> – a date in the format yyyymmdd</li><li><strong>Event timestamp column</strong> – number of microseconds (UNIX time)</li><li><strong>User pseudo ID column</strong> – application instance ID (this is not user ID, which is stored in a separate column that should be empty if the user didn’t give analytics consent)</li><li><strong>Geo column</strong> – contains all geo data (continent, country, etc). This is extracted from the IP address. From here, we will extract the user&#8217;s location</li><li><strong>User properties column</strong> – an array that contains all user attributes. From here, we will extract analytics consent attributes</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-178"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-176">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-177'
	>
	You can learn more about Firebase with their <a href="https://support.google.com/firebase/answer/7029846?hl=en&amp;ref_topic=7029512&amp;sjid=6826812808947906301-EU" target="_blank" rel="noreferrer noopener">official documentation</a>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-181"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-179">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-180'
	>
	Lastly, let&#8217;s define two more things. We will define our cohort by the first event, and we will define a user as active if they have at least one user engagement event. User_engenemant event is the default event that Firebase collects regardless of analytics consent.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-184"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-182">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-183'
	>
	Getting down to the code</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-187"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-185">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-186'
	>
	First, we will do a simple version and then an optimized version of our customer retention dashboard. Let’s split the process into two steps: processing user activity, and calculating customer retention. The main reasons for this are separation of concerns and keeping the query simple so that when other people read the code, or if you return to it after a couple of months, it is much easier to understand.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-190"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-188">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-189'
	>
	Step #1: Processing user activities</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-193"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-191">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-192'
	>
	Firebase collects a lot of events per user, and the more complex the app is, the more events there will be. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-196"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-194">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-195'
	>
	Remember, a user is active when there is at least one user engagement event. Since the stakeholders want to see monthly trends, in this step, we will look for all active users for each month. In other words, for each month, we need to create one row per user that has at least one event. It isn’t really important what time or date in the month an event happened; all that matters is whether or not the user performed an activity.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-199"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-197">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-198'
	>
	Once we have found all active users, we do a cross-join with all possible months and exclude invalid combinations. A combination is invalid if the year and month that we paired don&#8217;t match the event date year and month, e.g., if the event happened on 2023-06-23, we want to pair it with the date 2023-06-01, so that events are grouped according to month.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-202"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-200">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-201'
	>
	Furthermore, we need to find the cohort date for each user. Previously, we defined cohort as the date of the first event ever recorded for a user. Along with cohort, we also extract user attributes. Finally, we connect each user’s activity with cohort and attribute data. So, to summarize:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-205"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-203">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-204'
	>
	<li>Get one row for every active month</li><li>Generate all months since the application release</li><li>Cross-join generated dates with users’ activities, and exclude rows where year and month are different for generated date and user activity</li><li>For each user, we find the cohort date, and we extract user attributes</li><li>Aggregate with attributes</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-207"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">WITH</span><span class="token"> </span><span class="token" style="color: #005cc5;">events</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #6f42c1;">PARSE_DATE</span><span class="token">(</span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">%Y%m%d</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">event_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">event_date</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">event_timestamp</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">e</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">country</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token">(</span><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">value</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">string_value</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #6f42c1;">UNNEST</span><span class="token">(</span><span class="token" style="color: #005cc5;">e</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">user_properties</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #005cc5;">WHERE</span><span class="token"> </span><span class="token" style="color: #005cc5;">key</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">analytic_type</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #032f62;">`</span><span class="token" style="color: #032f62;">analytics.events_*</span><span class="token" style="color: #032f62;">`</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">e</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">WHERE</span><span class="token"> </span><span class="token" style="color: #005cc5;">event_name</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">user_engagement</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">GROUP</span><span class="token"> </span><span class="token" style="color: #005cc5;">BY</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">2</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">3</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">4</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">5</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-210"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-208">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-209'
	>
	In the <code>events</code> CTE, we are extracting all relevant data that we will need in the later CTEs. The main reason for creating this CTE is to have a cleaner query overall so that you can change the source table much more easily.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-213"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-211">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-212'
	>
	The main action points here are:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-216"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-214">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-215'
	>
	<li>Unnesting the <code>user_properties</code> field in order to get the <code>analytics_consent</code> attribute</li><li>Parsing the date value from the string (in <code>yyyymmdd</code> format) to <code>DATE</code> object, so that we can work with dates much more easily</li><li>Selecting all the columns that we need</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-218"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">generated_dates</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_date</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #6f42c1;">UNNEST</span><span class="token">(</span><span class="token" style="color: #6f42c1;">GENERATE_DATE_ARRAY</span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">2023-01-01</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #6f42c1;">CURRENT_DATE</span><span class="token">(</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">INTERVAL</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token"> </span><span class="token" style="color: #005cc5;">MONTH</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_date</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-221"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-219">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-220'
	>
	In the <code>generated_dates</code> column, we generate all the months since the app was released. Look at the following CTE to see why we need this.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-223"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">user_activities</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">generated_date</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">first_date_of_active_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">MAX</span><span class="token">(</span><span class="token" style="color: #005cc5;">event_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">date_of_last_activity</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">events</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_dates</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">WHERE</span><span class="token"> </span><span class="token" style="color: #005cc5;">EXTRACT</span><span class="token">(</span><span class="token" style="color: #005cc5;">YEAR</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">event_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">EXTRACT</span><span class="token">(</span><span class="token" style="color: #005cc5;">YEAR</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_date</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">EXTRACT</span><span class="token">(</span><span class="token" style="color: #005cc5;">MONTH</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">event_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">EXTRACT</span><span class="token">(</span><span class="token" style="color: #005cc5;">MONTH</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_date</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">GROUP</span><span class="token"> </span><span class="token" style="color: #005cc5;">BY</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">2</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-226"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-224">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-225'
	>
	Here, we are cross-joining data from <code>events</code> and <code>generated_dates</code> CTEs. Each row in the <code>events</code> CTE will have all the months since the app’s release.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-229"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-227">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-228'
	>
	After we cross-join this data, we filter out all invalid rows. A row is valid only when the year and month are the same for <code>event_date</code> and <code>generated_date</code> rows.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-232"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-230">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-231'
	>
	Lastly, we aggregate <code>event_date</code> by grouping by <code>user_pseudo_id</code> and <code>generated_date</code> (which we rename to <code>first_date_of_active_month</code>).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-235"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-233">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-234'
	>
	The final result for this CTE is that for each month when a user activity was recorded, there will be a corresponding row, and we will see the latest activity date in that active month (we don’t need the latest activity date column, but it can help when you are checking if a query works properly).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-237"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">new_users_cohort</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">MIN</span><span class="token">(</span><span class="token" style="color: #005cc5;">event_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_date</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #d73a49;">--</span><span class="token"> </span><span class="token" style="color: #005cc5;">date</span><span class="token"> </span><span class="token" style="color: #005cc5;">of</span><span class="token"> </span><span class="token" style="color: #005cc5;">first</span><span class="token"> </span><span class="token" style="color: #005cc5;">event</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">MIN</span><span class="token">(</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">MIN</span><span class="token">(</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">events</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">GROUP</span><span class="token"> </span><span class="token" style="color: #005cc5;">BY</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-240"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-238">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-239'
	>
	In the <code>new_users_cohort</code> CTE, we find a cohort for each user. If you remember, in the previous section, we defined cohort as a user&#8217;s first event. So here, we find the date of the first event for each user, along with all the attributes we need.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-242"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">new_users_cohort_with_activities</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">cohort</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_date</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">activities</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">first_date_of_active_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">activities</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">date_of_last_activity</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">cohort</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">cohort</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_users_cohort</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">LEFT</span><span class="token"> </span><span class="token" style="color: #005cc5;">JOIN</span><span class="token"> </span><span class="token" style="color: #005cc5;">user_activities</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">activities</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">ON</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">activities</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-245"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-243">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-244'
	>
	In the last CTE, we combined all the data together. Here, we connect user cohorts with user activities.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-247"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">EXTRACT</span><span class="token">(</span><span class="token" style="color: #005cc5;">YEAR</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #005cc5;">EXTRACT</span><span class="token">(</span><span class="token" style="color: #005cc5;">MONTH</span><span class="token"> </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_date</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #005cc5;">first_date_of_active_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #005cc5;">date_of_last_activity</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_users_cohort_with_activities</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-250"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-248">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-249'
	>
	In the final query, we select all the data together. In this step, we also extract all the necessary data (in this case, <code>cohort_year</code> and <code>cohort_month</code>).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-253"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-251">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-252'
	>
	Step #2: Calculating customer retention</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-256"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-254">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-255'
	>
	We can calculate customer retention now that we have collected user activity data. This step is much easier and simpler. We follow this logic:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-259"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-257">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-258'
	>
	<li>calculate the number of users for each <code>cohort_year</code>, <code>cohort_month</code>, <code>first_date_of_active_month</code>, <code>analytics_consent</code>, and <code>geo_location</code></li><li>calculate the difference in months between <code>first_date_of_active_month</code> and cohort date</li><li>find <code>max_number_of_users</code></li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-261"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">WITH</span><span class="token"> </span><span class="token" style="color: #005cc5;">calculated_number_of_users</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">first_date_of_active_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">COUNT</span><span class="token">(</span><span class="token" style="color: #005cc5;">DISTINCT</span><span class="token"> </span><span class="token" style="color: #005cc5;">user_pseudo_id</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #032f62;">`</span><span class="token" style="color: #032f62;">dashboards.UserRetention_UsersActivities</span><span class="token" style="color: #032f62;">`</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">GROUP</span><span class="token"> </span><span class="token" style="color: #005cc5;">BY</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">2</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">3</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">5</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">6</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-264"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-262">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-263'
	>
	In the <code>calculated_number_of_users</code> CTE, we calculate the number of users for cohort and attributes. The source in this CTE is the result of a query that processes user activity.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-266"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">calculated_month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">  </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #005cc5;">DATE_DIFF</span><span class="token">(</span><span class="token" style="color: #005cc5;">first_date_of_active_month</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">DATE</span><span class="token">(</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">)</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">MONTH</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_diff</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span><span class="line"><span class="token">  </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">calculated_number_of_users</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">d</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-269"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-267">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-268'
	>
	In the <code>calculated_month_diff</code> CTE, we calculate the difference in months between <code>first_date_of_active_month</code> and cohort date <code>DATE(cohort_year, cohort_month, 1)</code>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-271"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">max_number_of_users_for_cohort</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #d73a49;">*</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">calculated_month_diff</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">WHERE</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">0</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-274"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-272">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-273'
	>
	In this CTE, we find the maximum number of users. This is the number of users from the first cohort, or where <code>month_diff</code> is zero.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-276"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">SELECT</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_diff</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">m</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">max_number_of_users</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">calculated_month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">d</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">LEFT</span><span class="token"> </span><span class="token" style="color: #005cc5;">JOIN</span><span class="token"> </span><span class="token" style="color: #005cc5;">max_number_of_users_for_cohort</span><span class="token"> </span><span class="token" style="color: #005cc5;">m</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">ON</span><span class="token"> </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">m</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">m</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">m</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">d</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">m</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-279"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-277">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-278'
	>
	Lastly, we combine the <code>calculated_month_diff</code> and <code>max_number_of_users_for_cohort</code> CTEs in order to get the final data.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-282"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-280">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-281'
	>
	We finally come to the point where we have transformed the data into a form that we can use in the customer retention dashboard. Looker Studio offers a pivot table with a heatmap, which is the graph we will use to visualize our data. Once we set up a pivot table, we will see a dashboard that looks something like this:</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-287"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-285"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-286">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2023/08/User-retention-dashboard-tutorial-in-article-visual-1-min-1400x903.webp				media='(max-width: 699px)'
				type=image/webp								height="903"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2023/08/User-retention-dashboard-tutorial-in-article-visual-1-min-2400x1548.webp				media='(max-width: 1199px)'
				type=image/webp								height="1548"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2023/08/User-retention-dashboard-tutorial-in-article-visual-1-min.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1754"
															width="2720"
										loading="lazy"
					 />
					</picture>

			<figcaption class="image__figcaption block-media__image-figcaption">
			DASHBOARD AFTER SETTING UP A PIVOT TABLE		</figcaption>
	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-366"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-288">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-291"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-289">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-290'
	>
	If you look closely you will see one big issue – the percentage table does not show the correct values. However, when you dig deeper and look at the data you will see that our query does work properly; it just doesn’t cover one case that may occur. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-294"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-292">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-293'
	>
	The formula that we are using for calculating percentage in Looker Studio looks like this: <code>SUM(number_of_users) / SUM(max_number_of_users)</code>. So, if we want a correct percentage, we need the sum of all users for each <code>month_diff</code> and each cohort and the sum of all users for each cohort.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-297"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-295">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-296'
	>
	This potential case is very specific. The query for calculating retention will calculate the number of active users and find the maximum number of users for each <code>cohort_year</code>, <code>cohort_month</code>, <code>month_diff</code>, and all the attributes, and then create a row for each combination that exists. The problem occurs when there are no active users for a specific <code>cohort_year</code>, <code>cohort_month</code>, <code>month_diff</code>, or attributes in the source data (data collected by Firebase). </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-300"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-298">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-299'
	>
	If we don’t have user data in the data collected (in this case, because no users were active), no row will be created. Therefore, for <code>sum(max_number_of_users)</code>, we are only taking combinations of <code>cohort_year</code>, <code>cohort_month</code>, <code>month_diff</code>, and all the attributes that recorded at least one active user. What we need to do is generate rows for combinations that did not have any active users for that month.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-303"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-301">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-302'
	>
	Here is an additional query to make sure that we have all the data:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-305"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">MERGE</span><span class="token"> </span><span class="token" style="color: #032f62;">`</span><span class="token" style="color: #032f62;">dashboards.UserRetention</span><span class="token" style="color: #032f62;">`</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #6f42c1;">USING </span><span class="token">(</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-308"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-306">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-307'
	>
	This query will append the missing data to the table. We achieve this by using an upsert query:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-310"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">WITH</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_zero_data</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #d73a49;">*</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #032f62;">`</span><span class="token" style="color: #032f62;">dashboards.UserRetention</span><span class="token" style="color: #032f62;">`</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #005cc5;">WHERE</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">0</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">,</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-313"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-311">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-312'
	>
	Firstly, we must select all the data that has <code>month_diff zero</code>, because those are the cohorts for all combinations of existing attributes.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-315"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">generated_month_diff_options</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_month_diff</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">    FROM</span><span class="token"> </span><span class="token" style="color: #6f42c1;">UNNEST</span><span class="token">(
</span></span><span class="line"><span class="token" style="color: #005cc5;">      GENERATE_ARRAY</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #6f42c1;">      </span><span class="token">(
</span></span><span class="line"><span class="token" style="color: #005cc5;">        0</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #6f42c1;">        </span><span class="token">(</span><span class="token" style="color: #6f42c1;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">DATE_DIFF</span><span class="token">(
</span></span><span class="line"><span class="token" style="color: #6f42c1;">          CURRENT_DATE</span><span class="token">(),</span><span class="token" style="color: #6f42c1;"> </span><span class="token">
</span></span><span class="line"><span class="token" style="color: #6f42c1;">          </span><span class="token">(
</span></span><span class="line"><span class="token" style="color: #005cc5;">            SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">MIN</span><span class="token">(</span><span class="token" style="color: #6f42c1;">cohort_date</span><span class="token">)</span><span class="token" style="color: #005cc5;"> </span><span class="token">
</span></span><span class="line"><span class="token" style="color: #6f42c1;">            FROM</span><span class="token"> (
</span></span><span class="line"><span class="token" style="color: #005cc5;">              SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">DATE</span><span class="token">(</span><span class="token" style="color: #6f42c1;">cohort_year</span><span class="token">, </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_date</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">              FROM</span><span class="token"> </span><span class="token" style="color: #032f62;">`</span><span class="token" style="color: #032f62;">dashboards.UserRetention</span><span class="token" style="color: #032f62;">`</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">            </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">          </span><span class="token">)</span><span class="token">,</span><span class="token" style="color: #005cc5;"> </span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">          MONTH</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">        </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">      </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">    </span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_month_diff</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-318"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-316">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-317'
	>
	We also need to generate all possible instances of <code>month_diff</code>. So, in the <code>generated_month_diff_options</code> CTE, we generate integer values from zero to the maximum <code>month_diff</code> value that exists. We calculate the maximum <code>month_diff</code> possible by calculating the difference between the first cohort date <code>DATE(cohort_year, cohort_month, 1)</code> and today’s date.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-320"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">SELECT</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">generated_month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_diff</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">0</span><span class="token"> </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">max_number_of_users</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">FROM</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_zero_data</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_month_diff_options</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">WHERE</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">!=</span><span class="token"> </span><span class="token" style="color: #005cc5;">0</span><span class="token"> </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">generated_month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">&lt;=</span><span class="token"> </span><span class="token" style="color: #005cc5;">DATE_DIFF</span><span class="token">(</span><span class="token" style="color: #6f42c1;">CURRENT_DATE</span><span class="token">(</span><span class="token">)</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">DATE</span><span class="token">(</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">1</span><span class="token">)</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">MONTH</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">) </span><span class="token" style="color: #d73a49;">AS</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-323"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-321">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-322'
	>
	In the main query, we cross-join <code>month_zero_data</code> and <code>generated_month_diff_options</code>, and we exclude months that haven’t happened yet.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-325"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #005cc5;">ON</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">month_diff</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">month_diff</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">max_number_of_users</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">max_number_of_users</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">AND</span><span class="token"> </span><span class="token" style="color: #005cc5;">existing_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">new_data</span><span class="token" style="color: #d73a49;">.</span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">WHEN</span><span class="token"> </span><span class="token" style="color: #005cc5;">NOT</span><span class="token"> </span><span class="token" style="color: #005cc5;">MATCHED</span><span class="token"> </span><span class="token" style="color: #005cc5;">THEN</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #005cc5;">INSERT</span><span class="token"> </span><span class="token" style="color: #6f42c1;">VALUES</span><span class="token">(</span><span class="token" style="color: #005cc5;">cohort_year</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">cohort_month</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">month_diff</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">number_of_users</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">max_number_of_users</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">analytics_consent</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #005cc5;">geo_location</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-328"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-326">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-327'
	>
	Lastly, we do an upsert condition. So, when a row does not exist, we simply insert that row. If a row exists, it means that for that cohort, with those attributes, there is at least one user that was active.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-331"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-329">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-330'
	>
	Query efficiency</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-334"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-332">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-333'
	>
	This solution is perfectly fine for an app that has a small number of users (from thousands to tens of thousands). However, as applications scale up (hundred of thousands to millions of users) so does data. This query can very easily get to the point of processing hundreds of gigabytes of data, which can be very expensive when run on a daily basis.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-337"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-335">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-336'
	>
	In order to avoid that happening in the future, we need to optimize our queries. Those that we’ve created so far are non-iterative, meaning that every time they are run, we are using all the data from the source, which in reality, we don&#8217;t need. Recent source data (such as from the last week or month) is more than enough for refreshing destination tables that are used in the dashboard. How much older data we actually need depends on how often we are refreshing the data.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-340"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-338">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-339'
	>
	Investing our time in converting queries for processing user activity from non-iterative to iterative will bring the biggest benefit here. So, in the following part, we will focus on this conversion. If you want to look at the code for converting a query from non-iterative to iterative, you’ll find it in the <a href="https://github.com/infinum/data-engineering-public-content/tree/main/Blog_HowToDevelopUserRetentionDashboard" target="_blank" rel="noreferrer noopener">repository</a>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-343"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-341">
	<h4	class='typography typography--size-30-text js-typography block-heading__heading'
	data-id='es-342'
	>
	Converting query from non-iterative to iterative</h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-346"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-344">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-345'
	>
	All the queries are available in the <a href="https://github.com/infinum/data-engineering-public-content/tree/main/Blog_HowToDevelopUserRetentionDashboard" target="_blank" rel="noreferrer noopener">repository</a>, so there’s no need to get into them here. However, we will go through the logic used. It is very similar to the previous logic.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-349"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-347">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-348'
	>
	<li>Get one row for every active month (but using only recent data)</li><li>We generate all dates that have happened between the last query run and today</li><li>Cross-join generated dates with user activities, and exclude rows where year and month are different for generated date and user activity</li><li>Do a full join with the destination table</li><li>If a user exists, take cohort data along with attributes from the destination table</li><li>If they do not exist, this means this is a new user, find a cohort for this user and extract attributes</li><li>Aggregate new user activities with attributes</li><li>Combine new users with existing users</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-352"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-350">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-351'
	>
	Data quality is a prerequisite</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-355"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-353">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-354'
	>
	So far, we have talked about how to handle data so that it tells us how engaged our users are, but there is one very crucial component in this whole story – data quality. It can take a lot of time and resources to set up infrastructure, integrate our application with Firebase, cover all edge cases, etc. However, if we don’t ensure the quality of our data, all that time and resources will go to waste. Low-quality data is not accurate nor reliable, and accuracy and reliability are crucial in a data-driven project.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-358"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-356">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-357'
	>
	Make your customer retention dashboard yours</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-361"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-359">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-360'
	>
	A customer retention dashboard is a very useful tool to know how engaged our users are. As we have seen, the process itself is not very complex. However, bear in mind that the queries shown are not production-ready. They are written for &#8220;perfect&#8221; data, which does not exist in the real world. Accurate data brings forth many edge cases that we did not cover here for the sake of brevity. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-364"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-362">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-363'
	>
	Ultimately, each organization has different data and requirements. This article should provide a good starting point for establishing a customer retention dashboard, which is crucial for diagnosing and acting upon your customer retention data.</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/customer-retention-dashboard/">Uncover User Engagement with a Customer Retention Dashboard</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
		
	</channel>
</rss>