Retention and engagement
How this LookML project separates new and returning visitors
What share of sessions comes from returning visitors, and do weekly cohorts come back?
Short answer
Trust: InferredCount people, not sessions. This project calls a session new when its session index is 1, and its visitor share divides sessions by sessions, so a heavy user counts many times. Use distinct visitors for the share, and remember a visitor here is a cookie: cleared cookies and new devices look like new visitors.
Decision it supports: Whether acquisition is bringing people who return, or only first visits.
What to measure
New or returning
Trust: Source-definedA session is new when its session index is 1, returning when it is higher.
Defined as sessions.new_vs_returning_visitor
Show the SQL: New or returning
CASE WHEN sessions.domain_sessionidx = 1 THEN 'new' WHEN sessions.domain_sessionidx > 1 THEN 'returning' ELSE 'unknown'END
Translated to Redshift SQL from user_sessions.view.lookml L166-170. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L166-170
- dimension: bounce type: yesno sql: ${TABLE}.number_of_events = 1 - dimension: new_vs_returning_visitor sql_case: new: ${TABLE}.domain_sessionidx = 1 returning: ${TABLE}.domain_sessionidx > 1 else: unknown - dimension: distinct_pages_viewed sql: ${TABLE}.distinct_pages_viewed - dimension: distinct_pages_viewed_tiered
dating-platform, user_sessions.view.lookml, lines 166-170
Sessions from new visitors
Trust: Source-definedSessions with index 1.
Defined as sessions.sessions_from_new_visitors_count
COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)Trust: Translated
Show the SQL: Sessions from new visitors
COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)
Translated to Redshift SQL from user_sessions.view.lookml L102-106, L304-309. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L304-309
type: number decimals: 2 sql: ${bounced_sessions_count}/NULLIF(${count},0)::REAL - measure: sessions_from_new_visitors_count type: count_distinct sql: ${session_id} filters: session_index: 1 detail: individual_detail* - measure: sessions_from_returning_visitor_count type: number sql: ${count} - ${sessions_from_new_visitors_count}
dating-platform, user_sessions.view.lookml, lines 304-309
Sessions from returning visitors
Trust: Source-definedAll sessions minus new ones.
Defined as sessions.sessions_from_returning_visitor_count
COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx) - COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)Trust: Translated
Show the SQL: Sessions from returning visitors
COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx) - COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)
Translated to Redshift SQL from user_sessions.view.lookml L102-106, L285-288, L304-313. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L310-313
filters: session_index: 1 detail: individual_detail* - measure: sessions_from_returning_visitor_count type: number sql: ${count} - ${sessions_from_new_visitors_count} detail: individual_detail* - measure: new_visitors_count_over_total_visitors_count type: number decimals: 2
dating-platform, user_sessions.view.lookml, lines 310-313
New visitor share, as named
Trust: Source-definedNamed as a visitor share, computed as a share of sessions.
Defined as sessions.new_visitors_count_over_total_visitors_count
COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)/NULLIF(COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx),0)::REALTrust: Translated
Show the SQL: New visitor share, as named
COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)/NULLIF(COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx),0)::REAL
Translated to Redshift SQL from user_sessions.view.lookml L102-106, L285-288, L304-309, L314-317. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L314-317
type: number sql: ${count} - ${sessions_from_new_visitors_count} detail: individual_detail* - measure: new_visitors_count_over_total_visitors_count type: number decimals: 2 sql: ${sessions_from_new_visitors_count}/NULLIF(${count},0)::REAL - measure: returning_visitors_count_over_total_visitors_count type: number decimals: 2
dating-platform, user_sessions.view.lookml, lines 314-317
Visitors
Trust: Source-definedDistinct visitor cookies.
Defined as visitors.count
COUNT(DISTINCT visitors.domain_userid)Trust: Translated
Show the SQL: Visitors
COUNT(DISTINCT visitors.domain_userid)
Translated to Redshift SQL from guests.view.lookml L39-40, L141-144. Not run against a warehouse.
Show the original LookML: guests.view.lookml L141-144
sql: ${TABLE}.mkt_term - dimension: campaign_name sql: ${TABLE}.mkt_campaign - measure: count type: count_distinct sql: ${user_id} detail: individual_detail* - measure: bounced_visitor_count type: count_distinct sql: ${user_id}
dating-platform, guests.view.lookml, lines 141-144
Returning visitor rate
Trust: InferredA visitor-level rate needs distinct returning visitors over distinct visitors. The source only has session shares.
Cohort retention
Trust: SuggestedThe cohort tile shows visitor counts per first-touch week and session week. A retention percentage is not in the source.
The query
New and returning sessions by day
Trust: TranslatedThe dashboard's daily tile: sessions per day, split into new and returning. The pivot comes back as rows.
Show the SQL: new and returning sessions by day
SELECT CAST(sessions.session_start_ts AS DATE) AS start_date, CASE WHEN sessions.domain_sessionidx = 1 THEN 'new' WHEN sessions.domain_sessionidx > 1 THEN 'returning' ELSE 'unknown' END AS new_vs_returning_visitor, COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx) AS countFROM sessions -- derived table, defined in the sourceGROUP BY 1, 2LIMIT 500
- On the dashboard this tile also follows its filters (date, referer_medium, new_vs_returning, country, landing_page, referer_host, app_id). They are left out here.
Translated to Redshift SQL from traffic_overview.dashboard.lookml L156-190, event_tracker.model.lookml L13-16 and user_sessions.view.lookml L105-106, L122-129, L166-170, L285-288. Not run against a warehouse.
Show the original LookML: traffic_overview.dashboard.lookml L156-172
width: 6 height: 4 legend_align: null - name: new_vs_returning_visitors_by_day title: New vs returning visitors type: looker_area explore: sessions dimensions: - sessions.start_date pivots: - sessions.new_vs_returning_visitor measures: - sessions.count filters: null listen: date: sessions.start_date referer_medium: sessions.referer_medium new_vs_returning: sessions.new_vs_returning_visitor country: sessions.geography_country landing_page: sessions.landing_page_path referer_host: sessions.referer_url_host app_id: sessions.app_id limit: 500
dating-platform, traffic_overview.dashboard.lookml, lines 156-172
The data behind it
Trust: Inferred- Entities
- session, visitor (cookie)
- Grain
- One row per session. Visitors are cookies (
domain_userid). - Time
- Session start week and visitor first-touch week.
- Key fields
sessions.new_vs_returning_visitorsessions.session_indexsessions.session_index_tiervisitors.first_touch_week
What can change the answer 3
Caution: A visitor is a cookie
Trust: Inferreddomain_userid is a first-party cookie. Cleared cookies, new devices and private windows all start again at session 1.
What goes wrong: The new visitor share is overstated and the returning share understated.
Show the SQL: user id
sessions.domain_userid
Translated to Redshift SQL from user_sessions.view.lookml L100-101. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L100-101
- dimension: app_id type: string sql: ${TABLE}.app_id - dimension: user_id sql: ${TABLE}.domain_userid - dimension: session_index type: int sql: ${TABLE}.domain_sessionidx
dating-platform, user_sessions.view.lookml, lines 100-101
Caution: Session shares named as visitor shares
Trust: Source-definednew_visitors_count_over_total_visitors_count divides sessions by sessions. A visitor with five sessions counts five times.
Show the SQL: new visitors count over total visitors count
COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)/NULLIF(COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx),0)::REAL
Translated to Redshift SQL from user_sessions.view.lookml L102-106, L285-288, L304-309, L314-317. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L314-317
type: number sql: ${count} - ${sessions_from_new_visitors_count} detail: individual_detail* - measure: new_visitors_count_over_total_visitors_count type: number decimals: 2 sql: ${sessions_from_new_visitors_count}/NULLIF(${count},0)::REAL - measure: returning_visitors_count_over_total_visitors_count type: number decimals: 2
dating-platform, user_sessions.view.lookml, lines 314-317
Caution: Cohorts cut by a session date
Trust: InferredThe date filter applies to session start, so older cohorts only show visitors with sessions in the window.
Read the guide: Filters that change metric meaning
Source: traffic_overview.dashboard.lookml L263-283
y_axis_labels: null y_axis_min: null y_axis_max: null - name: cohort_analysis title: Cohort analysis type: table explore: sessions dimensions: - visitors.first_touch_week pivots: - sessions.start_week measures: - visitors.count listen: date: sessions.start_date referer_medium: sessions.referer_medium new_vs_returning: sessions.new_vs_returning_visitor country: sessions.geography_country landing_page: sessions.landing_page_path referer_host: sessions.referer_url_host app_id: sessions.app_id limit: 500 width: null height: null - name: session_count_and_engagement_level_by_referer_host title: Session count and events per sesion for top 10 referer hosts type: looker_column
dating-platform, traffic_overview.dashboard.lookml, lines 263-283
How the real implementation does it
sessions
Trust: Source-definedNew vs returning is decided by the session index alone.
Show the SQL: new or returning
CASE WHEN sessions.domain_sessionidx = 1 THEN 'new' WHEN sessions.domain_sessionidx > 1 THEN 'returning' ELSE 'unknown'END
Translated to Redshift SQL from user_sessions.view.lookml L166-170. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L166-170
- dimension: bounce type: yesno sql: ${TABLE}.number_of_events = 1 - dimension: new_vs_returning_visitor sql_case: new: ${TABLE}.domain_sessionidx = 1 returning: ${TABLE}.domain_sessionidx > 1 else: unknown - dimension: distinct_pages_viewed sql: ${TABLE}.distinct_pages_viewed - dimension: distinct_pages_viewed_tiered
dating-platform, user_sessions.view.lookml, lines 166-170
sessions
Trust: Source-definedReturning sessions are derived by subtraction, not counted directly.
Show the SQL: returning sessions
COUNT(DISTINCT sessions.domain_userid || '-' || sessions.domain_sessionidx) - COUNT(DISTINCT CASE WHEN sessions.domain_sessionidx = 1 THEN sessions.domain_userid || '-' || sessions.domain_sessionidx END)
Translated to Redshift SQL from user_sessions.view.lookml L102-106, L285-288, L304-313. Not run against a warehouse.
Show the original LookML: user_sessions.view.lookml L310-313
filters: session_index: 1 detail: individual_detail* - measure: sessions_from_returning_visitor_count type: number sql: ${count} - ${sessions_from_new_visitors_count} detail: individual_detail* - measure: new_visitors_count_over_total_visitors_count type: number decimals: 2
dating-platform, user_sessions.view.lookml, lines 310-313
traffic_pulse
Trust: Source-definedThe cohort table pivots session week against visitor first-touch week.
Show the SQL: weekly cohorts
SELECT DATE_TRUNC('week', visitors.first_touch) AS first_touch_week, DATE_TRUNC('week', sessions.session_start_ts) AS start_week, COUNT(DISTINCT visitors.domain_userid) AS countFROM sessions -- derived table, defined in the sourceLEFT JOIN visitors -- derived table, defined in the source ON sessions.domain_userid = visitors.domain_useridGROUP BY 1, 2LIMIT 500
- On the dashboard this tile also follows its filters (date, referer_medium, new_vs_returning, country, landing_page, referer_host, app_id). They are left out here.
Translated to Redshift SQL from traffic_overview.dashboard.lookml L263-283, event_tracker.model.lookml L13-16, guests.view.lookml L39-40, L43-50, L141-144 and user_sessions.view.lookml L122-129. Not run against a warehouse.
Show the original LookML: traffic_overview.dashboard.lookml L263-283
y_axis_labels: null y_axis_min: null y_axis_max: null - name: cohort_analysis title: Cohort analysis type: table explore: sessions dimensions: - visitors.first_touch_week pivots: - sessions.start_week measures: - visitors.count listen: date: sessions.start_date referer_medium: sessions.referer_medium new_vs_returning: sessions.new_vs_returning_visitor country: sessions.geography_country landing_page: sessions.landing_page_path referer_host: sessions.referer_url_host app_id: sessions.app_id limit: 500 width: null height: null - name: session_count_and_engagement_level_by_referer_host title: Session count and events per sesion for top 10 referer hosts type: looker_column
dating-platform, traffic_overview.dashboard.lookml, lines 263-283
Other ways to measure it
Count visitors, not sessions
Trust: SuggestedDistinct visitors with a session index above 1, over distinct visitors, gives a true returning share for the period.
Show the suggested SQL: returning visitor share
SELECT COUNT(DISTINCT CASE WHEN domain_sessionidx > 1 THEN domain_userid END) AS returning_visitors, COUNT(DISTINCT domain_userid) AS visitors, 1.0 * COUNT(DISTINCT CASE WHEN domain_sessionidx > 1 THEN domain_userid END) / NULLIF(COUNT(DISTINCT domain_userid), 0) AS returning_visitor_shareFROM sessions -- the derived table defined in the source
Use a member id after signup
Trust: SuggestedMap cookies to member ids once people sign up, so returning is measured per person rather than per browser.
