I have written a simple solution that follows your diagram completely and have annotated it to attempt to help you understand what we are doing.
-- Create a temp table to hold the date values CREATE TEMP TABLE dates ( YEAR INT64 ); -- insert dates in the desired range - have chosen 2015-2022 here as an example INSERT INTO dates SELECT EXTRACT(YEAR FROM MY_DATE) FROM ( -- this selects dates from 2015-2022, change 2015 to desired start date and 7 to the desired number of years SELECT DATE_ADD('2015-01-01',INTERVAL param YEAR) AS MY_DATE FROM unnest(GENERATE_ARRAY(0, 7, 1)) as param ) ; -- create data in the original table WITH original_table AS (SELECT 'a' as customer_id, '2020-11-01' as snapshot_date, 20 as age UNION ALL SELECT 'a', '2020-12-01', 21 UNION ALL SELECT 'b', '2020-09-01', 25 UNION ALL SELECT 'b', '2020-10-01', 25 UNION ALL SELECT 'c', '2020-01-01', 45), -- Select customer_id, min_snapshot_date, age for min table min_date_age AS (SELECT original_table.customer_id, DATE(MIN(original_table.snapshot_date)) AS min_snapshot_date, MIN(original_table.age) AS min_age FROM original_table GROUP BY customer_id) -- select customer id, and derived snapshot year and age SELECT customer_id, YEAR AS derived_snapshot_year, min_age - (EXTRACT(YEAR FROM min_snapshot_date) - YEAR) AS derived_age FROM min_date_age -- cross join to create duplicate rows (one for each desired date) CROSS JOIN dates ORDER BY min_date_age.customer_id, derived_snapshot_year
Output table:

This creates the exact behavior as you have described with your diagram - however as pointed out by other users there are some issues with this output as each year there are technically two ages a user can possibly be in a single year (with the rare exception of people born on XXXX/01/01).
EDIT:
Edit to response to the comment.
-- Create a temp table to hold the date values CREATE TEMP TABLE dates ( YEAR INT64 ); -- insert dates in the desired range - have chosen 2015-2022 here as an example INSERT INTO dates SELECT EXTRACT(YEAR FROM MY_DATE) FROM ( -- this selects dates from 2015-2022, change 2015 to desired start date and 7 to the desired number of years SELECT DATE_ADD('2015-01-01',INTERVAL param YEAR) AS MY_DATE FROM unnest(GENERATE_ARRAY(0, 7, 1)) as param ) ; -- create data in the original table WITH original_table AS (SELECT 'a' as customer_id, '2020-11-01' as snapshot_date, 20 as age UNION ALL SELECT 'a', '2020-12-01', 21 UNION ALL SELECT 'b', '2020-09-01', 25 UNION ALL SELECT 'b', '2020-10-01', 25 UNION ALL SELECT 'c', '2020-01-01', 45), -- Select customer_id, min_snapshot_date, age for min table min_date_age AS (SELECT original_table.customer_id, DATE(MIN(original_table.snapshot_date)) AS min_snapshot_date, MIN(original_table.age) AS min_age FROM original_table GROUP BY customer_id), -- select customer id, and derived snapshot year and age output AS (SELECT customer_id, YEAR AS derived_snapshot_year, min_age - (EXTRACT(YEAR FROM min_snapshot_date) - YEAR) AS derived_age FROM min_date_age -- cross join to create duplicate rows (one for each desired date) CROSS JOIN dates ORDER BY min_date_age.customer_id, derived_snapshot_year) SELECT ... FROM other_table LEFT JOIN output ON other_table.field = output.field
date_sub(cast(min_date as date), INTERVAL age YEAR)but this may be off by 1 year for some people. May need to play around with it.date_sub(cast(min_date as date), INTERVAL age YEAR) as Xthen calculate the minimum value per user and the difference:date_diff(max(X),min(X),day) as y. If y is greater than 365 days the user lied with the age. If it is zero only one day is given and thus the user can be one year older.365-ygives you the amount of days the user can be older.