Advent of SQL
  • 2024
  • About

2024

Day 1

Day 1

WITH latest_list AS (
    SELECT
        child_id,
        MAX(submitted_date) AS latest
    FROM wish_lists
    GROUP BY child_id
),

fav_colors AS (
    SELECT
        wl.child_id,
        wl.wishes::json ->> 'colors' AS color_array
    FROM wish_lists AS wl
    INNER JOIN latest_list AS ll ON
        wl.child_id = ll.child_id
        AND wl.submitted_date = ll.latest
),

wishes AS (
    SELECT
        wl.child_id,
        wl.wishes::json ->> 'first_choice' AS primary_wish,
        wl.wishes::json ->> 'second_choice' AS backup_wish,
        (wl.wishes::json -> 'colors')::json ->> 0 AS favorite_color,
        JSON_ARRAY_LENGTH(fc.color_array::json) AS n_colors
    FROM wish_lists AS wl
    INNER JOIN fav_colors AS fc
        ON wl.child_id = fc.child_id
    INNER JOIN latest_list AS ll ON
        wl.child_id = ll.child_id
        AND wl.submitted_date = ll.latest
),

complexity AS (
    SELECT
        toy_name,
        CASE
            WHEN difficulty_to_make = 1 THEN 'Simple Gift'
            WHEN difficulty_to_make = 2 THEN 'Moderate Gift'
            ELSE 'Complex Gift'
        END AS gift_complexity,
        CASE
            WHEN category = 'educational' THEN 'Learning Workshop'
            WHEN category = 'outdoor' THEN 'Outdoor Workshop'
            ELSE 'General Workshop'
        END AS workshop_assignment
    FROM toy_catalogue
)

SELECT
    ch.name,
    wsh.primary_wish,
    wsh.backup_wish,
    wsh.favorite_color,
    wsh.n_colors,
    cmplx.gift_complexity,
    cmplx.workshop_assignment
FROM children AS ch
INNER JOIN wishes AS wsh ON ch.child_id = wsh.child_id
INNER JOIN complexity AS cmplx ON wsh.primary_wish = cmplx.toy_name
ORDER BY ch.name
LIMIT 5;
      name            primary_wish             backup_wish favorite_color
1    Abbie            Barbie dolls Toy musical instruments          Green
2 Abbigail              Jump ropes          Action figures         Yellow
3 Abdullah                  Yo-yos                  Yo-yos         Yellow
4 Abdullah Toy musical instruments            Barbie dolls           Pink
5 Abelardo              Toy trains             Board games         Yellow
  n_colors gift_complexity workshop_assignment
1        5   Moderate Gift    General Workshop
2        1     Simple Gift    Outdoor Workshop
3        1     Simple Gift    General Workshop
4        2    Complex Gift   Learning Workshop
5        4    Complex Gift    General Workshop
0.069 sec elapsed

Day 2

Day 2

WITH combined AS (
    SELECT * FROM letters_a
    UNION ALL
    SELECT * FROM letters_b
),

decoded AS (
    SELECT
        *,
        CHR(value) AS string
    FROM combined
    WHERE
        (value BETWEEN 65 AND 90)
        OR (value BETWEEN 97 AND 122)
        OR (value IN (32, 33, 44, 46))
)

SELECT STRING_AGG(string, '') FROM decoded;
                                                                                           string_agg
1 Dear Santa, I hope this letter finds you well in the North Pole! I want a SQL course for Christmas!
0.006 sec elapsed

Day 3

Day 3

-- noqa: disable=LT05
WITH parsed_menu AS (
    SELECT
        (XPATH('//food_item_id/text()', menu_data))::text [] AS array_food_item,
        CASE
            WHEN ARRAY_LENGTH(XPATH('//total_present/text()', menu_data), 1) > 0
                THEN (XPATH('//total_present/text()', menu_data))[1]::text::integer
            WHEN ARRAY_LENGTH(XPATH('//total_count/text()', menu_data), 1) > 0
                THEN (XPATH('//total_count/text()', menu_data))[1]::text::integer
            WHEN ARRAY_LENGTH(XPATH('//total_guests/text()', menu_data), 1) > 0
                THEN (XPATH('//total_guests/text()', menu_data))[1]::text::integer
        END AS n_guests
    FROM christmas_menus
)

SELECT
    UNNEST(array_food_item) AS food_item,
    COUNT(*) AS n
FROM parsed_menu
WHERE n_guests > 78
GROUP BY food_item
ORDER BY n DESC
LIMIT 1;
  food_item   n
1       493 117
1.568 sec elapsed

Day 4

Day 4

SELECT
    toy_id,
    (
        SELECT COUNT(*)
        FROM (
            SELECT UNNEST(new_tags)
            EXCEPT
            SELECT UNNEST(previous_tags)
        )
    ) AS added,
    (
        SELECT COUNT(*)
        FROM (
            SELECT UNNEST(previous_tags)
            INTERSECT
            SELECT UNNEST(new_tags)
        )
    ) AS unchanged,
    (
        SELECT COUNT(*)
        FROM (
            SELECT UNNEST(previous_tags)
            EXCEPT
            SELECT UNNEST(new_tags)
        )
    ) AS removed
FROM toy_production
ORDER BY added DESC
LIMIT 1;
  toy_id added unchanged removed
1   2726    98         2       0
0.171 sec elapsed

Day 5

Day 5

WITH previous AS (
    SELECT
        production_date,
        toys_produced,
        LAG(toys_produced, 1, NULL)
            OVER (ORDER BY production_date)
        AS previous_day_production
    FROM toy_production
)

SELECT
    tp.production_date,
    pr.toys_produced,
    pr.previous_day_production,
    tp.toys_produced - pr.previous_day_production AS production_change,
    ROUND(
        (tp.toys_produced - pr.previous_day_production)
        / (pr.previous_day_production * 1.0) * 100.0, 2
    ) AS production_change_percentage
FROM toy_production AS tp
INNER JOIN previous AS pr ON tp.production_date = pr.production_date
WHERE pr.previous_day_production IS NOT NULL
ORDER BY production_change_percentage DESC
LIMIT 1;
  production_date toys_produced previous_day_production production_change
1      2017-03-20          2327                       3              2324
  production_change_percentage
1                     77466.67
0.04 sec elapsed

Day 6

Day 6

SELECT
    ch.name AS child_name,
    gi.name AS gift_name,
    gi.price AS gift_price
FROM children AS ch
INNER JOIN gifts AS gi ON ch.child_id = gi.child_id
WHERE gi.price > (SELECT AVG(mp.price) FROM gifts AS mp)
ORDER BY gi.price ASC
LIMIT 5;
  child_name        gift_name gift_price
1     Hobart        art easel     497.44
2    Lindsey wizard chess set     497.52
3       Reba   bubble machine     497.89
4      Reina    talking globe     497.91
5    Brenden    talking globe     498.25
0.037 sec elapsed

Day 7

Day 7

WITH min_experience AS (
    SELECT DISTINCT ON (primary_skill)
        elf_id,
        years_experience,
        primary_skill
    FROM workshop_elves
    ORDER BY primary_skill ASC, years_experience ASC, elf_id ASC
),

max_experience AS (
    SELECT DISTINCT ON (primary_skill)
        elf_id,
        years_experience,
        primary_skill
    FROM workshop_elves
    ORDER BY primary_skill ASC, years_experience DESC, elf_id ASC
)

SELECT
    max_exp.elf_id AS elf_1_id,
    min_exp.elf_id AS elf_2_id,
    max_exp.primary_skill AS shared_skill
FROM min_experience AS min_exp
INNER JOIN max_experience AS max_exp
    ON min_exp.primary_skill = max_exp.primary_skill
ORDER BY min_exp.primary_skill
LIMIT 3;
  elf_1_id elf_2_id  shared_skill
1     4153     3611  Gift sorting
2    10497     1016 Gift wrapping
3       50    13551    Toy making
0.392 sec elapsed

Day 8

Day 8

WITH RECURSIVE managers AS (
    SELECT
        s1.staff_id,
        s1.staff_name,
        s1.manager_id
    FROM staff AS s1
    UNION ALL
    SELECT
        s2.staff_id,
        s2.staff_name,
        s2.manager_id
    FROM staff AS s2
    INNER JOIN managers AS mgmt
        ON s2.manager_id = mgmt.staff_id
)

SELECT COUNT(*) AS n
FROM managers
GROUP BY staff_id
ORDER BY n DESC
LIMIT 1;
   n
1 24
2.382 sec elapsed

Day 9

Day 9

WITH avg_exercise AS (
    SELECT
        reindeer_id,
        exercise_name,
        AVG(speed_record) AS avg_speed
    FROM training_sessions
    GROUP BY reindeer_id, exercise_name
),

max_avg_speed AS (
    SELECT
        reindeer_id,
        MAX(avg_speed) AS max_avg_speed
    FROM avg_exercise
    GROUP BY reindeer_id
)

SELECT
    re.reindeer_name,
    ROUND(mas.max_avg_speed, 2) AS top_speed
FROM max_avg_speed AS mas
INNER JOIN reindeers AS re ON mas.reindeer_id = re.reindeer_id
WHERE re.reindeer_name != 'Rudolph'
ORDER BY top_speed DESC
LIMIT 3;
  reindeer_name top_speed
1         Cupid     88.64
2       Blitzen     88.38
3         Vixen     88.01
0.01 sec elapsed

Day 10

Day 10

WITH drinks AS (
    SELECT
        date,
        SUM(
            CASE WHEN drink_name = 'Eggnog' THEN quantity ELSE 0 END
        ) AS eggnog,
        SUM(
            CASE WHEN drink_name = 'Hot Cocoa' THEN quantity ELSE 0 END
        ) AS hot_cocoa,
        SUM(
            CASE
                WHEN drink_name = 'Peppermint Schnapps' THEN quantity ELSE 0
            END
        ) AS peppermint_schnapps
    FROM drinks
    GROUP BY date
)

SELECT date FROM drinks
WHERE
    1 = 1
    AND eggnog = 198
    AND hot_cocoa = 38
    AND peppermint_schnapps = 298;
        date
1 2024-03-14
0.009 sec elapsed

Day 11

Day 11

SELECT
    field_name,
    harvest_year,
    season,
    ROUND(AVG(trees_harvested) OVER (
        PARTITION BY field_name
        ORDER BY
            harvest_year,
            CASE
                WHEN season = 'Spring' THEN 1
                WHEN season = 'Summer' THEN 2
                WHEN season = 'Fall' THEN 3
                WHEN season = 'Winter' THEN 4
            END
        ROWS BETWEEN 2 PRECEDING
        AND CURRENT ROW
    ), 2) AS moving_ave
FROM
    treeharvests
ORDER BY
    moving_ave DESC
LIMIT 1;
           field_name harvest_year season moving_ave
1 Northern Gardens 62         2024 Winter     327.67
0.009 sec elapsed

Day 12

Day 12

WITH gift_counts AS (
    SELECT
        gi.gift_name,
        COUNT(*) AS n_req
    FROM gift_requests AS req
    LEFT JOIN gifts AS gi ON req.gift_id = gi.gift_id
    GROUP BY gi.gift_name
)

SELECT
    gift_name,
    ROUND(PERCENT_RANK() OVER (
        ORDER BY n_req
    )::numeric, 2) AS percentile
FROM
    gift_counts
ORDER BY percentile DESC, gift_name ASC
LIMIT 20;
                   gift_name percentile
1              costume trunk       0.92
2           finger paint set       0.92
3                 hoverboard       0.92
4                  jump rope       0.92
5                  magic kit       0.92
6           musical keyboard       0.92
7              rocking horse       0.92
8              talking globe       0.92
9              toy submarine       0.92
10             chemistry set       0.86
11 friendship bracelet maker       0.86
12           learning tablet       0.86
13              magic 8 ball       0.86
14                magic wand       0.86
15                toy rocket       0.86
16               bowling set       0.72
17           building blocks       0.72
18       crystal growing kit       0.72
19              flying drone       0.72
20           magic treehouse       0.72
0.035 sec elapsed

Day 13

Day 13

WITH email_addresses AS (
    SELECT
        name,
        email,
        SPLIT_PART(email, '@', 2) AS domain_name
    FROM contact_list, UNNEST(email_addresses) AS email
)

SELECT
    domain_name,
    COUNT(*) AS n_users
FROM
    email_addresses
GROUP BY
    domain_name
ORDER BY n_users DESC
LIMIT 5;
    domain_name n_users
1     bells.org     155
2 stockings.net     154
3     gifts.biz     149
4     bells.com     146
5     holly.biz     145
0.012 sec elapsed

Day 14

Day 14

WITH receipts AS (
    SELECT JSONB_ARRAY_ELEMENTS(cleaning_receipts) AS receipt
    FROM santarecords
),

unnested_receipt_items AS (
    SELECT
        receipt ->> 'drop_off' AS drop_off_date,
        receipt ->> 'garment' AS garment,
        receipt ->> 'color' AS color
    FROM receipts
)

SELECT * FROM unnested_receipt_items
WHERE
    1 = 1
    AND garment = 'suit'
    AND color = 'green';
  drop_off_date garment color
1    2024-12-22    suit green
2    2024-12-22    suit green
3    2024-11-27    suit green
4    2024-11-08    suit green
5    2024-11-28    suit green
6    2024-11-07    suit green
7    2024-11-15    suit green
8    2024-11-06    suit green
0.008 sec elapsed

Day 15

Day 15

WITH last_location AS (
    SELECT coordinate
    FROM sleigh_locations
    WHERE timestamp = (
        SELECT MAX(sl.timestamp) FROM sleigh_locations AS sl
    )
)

SELECT areas.place_name
FROM areas, last_location AS ll
WHERE ST_INTERSECTS(areas.polygon, ll.coordinate);
  place_name
1      Paris
0.337 sec elapsed

Day 16

Day 16

WITH timepoints AS (
    SELECT
        coordinate,
        timestamp AS timestamp_current,
        LAG(timestamp, 1) OVER (ORDER BY timestamp DESC) AS timestamp_next
    FROM sleigh_locations
)

SELECT
    areas.place_name,
    SUM(tp.timestamp_next - tp.timestamp_current) AS duration
FROM timepoints AS tp, areas
WHERE ST_INTERSECTS(areas.polygon, tp.coordinate)
GROUP BY areas.place_name
ORDER BY duration DESC;
   place_name duration
1       Paris 01:30:00
2    New_York 01:15:00
3      London 01:00:00
4 Los_Angeles 01:00:00
5       Tokyo 00:45:00
0.007 sec elapsed

Day 17

Timezones are hard enough, let alone to do them in SQL 😅

Day 18

Couldn’t figure this one out 😕 It asks for a recursive CTE as we used before in Day 8, but this one had a trick to I can’t seem to get

Day 19

Day 19

WITH avg_last_score AS (
    SELECT
        AVG(
            year_end_performance_scores[
                ARRAY_UPPER(year_end_performance_scores, 1)
            ]
        ) AS avg_score
    FROM employees
),

salaries_w_bonus AS (
    SELECT
        CASE
            WHEN
                em.year_end_performance_scores[
                    ARRAY_UPPER(em.year_end_performance_scores, 1)
                ]
                > (SELECT als.avg_score FROM avg_last_score AS als)
                THEN em.salary * 1.15
            ELSE em.salary
        END AS salary_w_bonus
    FROM employees AS em
)

SELECT ROUND(SUM(salary_w_bonus), 2) AS total_salary_paid
FROM salaries_w_bonus;
  total_salary_paid
1        5491552488
0.012 sec elapsed

Day 20

Day 20

SELECT
    url,
    STRING_TO_ARRAY(SPLIT_PART(url, '?', 2), '&') AS query_params,
    CARDINALITY(STRING_TO_ARRAY(SPLIT_PART(url, '?', 2), '&')) AS n_params
FROM web_requests
WHERE
    SPLIT_PART(SUBSTRING(url FROM 'utm_source=[\w-]+'), '=', 2)
    = 'advent-of-sql'
ORDER BY query_params DESC, url ASC
LIMIT 5;
                                                                                                                                                                                                                              url
1                                                   https://prohaska.net?voluptatum_aut_qui=sit_delectus_facere_perferendis&quia_doloremque=deleniti_natus_dolores&ab-quia-pariatur-quisquam=aut_unde_et&utm_source=advent-of-sql
2 https://lebsack.com?voluptatum-sapiente-eaque-et-et=temporibus-assumenda&utm_source=advent-of-sql&sequi-voluptatibus-autem-sequi=cupiditate-consequatur-saepe-nemo-est&placeat-voluptatem-voluptatem=dolorum_laboriosam_aliquid
3                                                   https://heller.com?voluptatum-quam-natus=sunt-dolores-ut-debitis-ab&utm_source=advent-of-sql&utm_source=nisi-soluta&sit_soluta_dolor_voluptatem=qui_ullam_voluptatem_ea_nihil
4                               https://williamson.biz?voluptatum-nihil=perferendis_doloremque&utm_source=advent-of-sql&enim-et=repudiandae-fuga-tempore-commodi-autem&aut-et-est-provident-quae=earum-sit-qui-exercitationem-sed
5             http://koepp.name?voluptatum-et-illo-fuga-suscipit=distinctio-voluptatem-et-consequatur-perferendis&ut_deserunt=aliquid-voluptatum&utm_source=advent-of-sql&expedita-ut-nihil=magni_expedita_distinctio_consequatur
                                                                                                                                                                                                   query_params
1                                                    {voluptatum_aut_qui=sit_delectus_facere_perferendis,quia_doloremque=deleniti_natus_dolores,ab-quia-pariatur-quisquam=aut_unde_et,utm_source=advent-of-sql}
2 {voluptatum-sapiente-eaque-et-et=temporibus-assumenda,utm_source=advent-of-sql,sequi-voluptatibus-autem-sequi=cupiditate-consequatur-saepe-nemo-est,placeat-voluptatem-voluptatem=dolorum_laboriosam_aliquid}
3                                                  {voluptatum-quam-natus=sunt-dolores-ut-debitis-ab,utm_source=advent-of-sql,utm_source=nisi-soluta,sit_soluta_dolor_voluptatem=qui_ullam_voluptatem_ea_nihil}
4                                  {voluptatum-nihil=perferendis_doloremque,utm_source=advent-of-sql,enim-et=repudiandae-fuga-tempore-commodi-autem,aut-et-est-provident-quae=earum-sit-qui-exercitationem-sed}
5           {voluptatum-et-illo-fuga-suscipit=distinctio-voluptatem-et-consequatur-perferendis,ut_deserunt=aliquid-voluptatum,utm_source=advent-of-sql,expedita-ut-nihil=magni_expedita_distinctio_consequatur}
  n_params
1        4
2        4
3        4
4        4
5        4
0.07 sec elapsed

Day 21

Day 21

WITH total_sales AS (
    SELECT
        DATE_PART('YEAR', sale_date) AS sale_year,
        DATE_PART('QUARTER', sale_date) AS sale_quarter,
        SUM(amount) AS sale_amount
    FROM sales
    GROUP BY sale_year, sale_quarter
)

SELECT
    sale_year,
    sale_quarter,
    sale_amount,
    ROUND(
        sale_amount
        / LAG(sale_amount) OVER (ORDER BY sale_year, sale_quarter), 2
    ) AS growth_rate
FROM total_sales
ORDER BY growth_rate DESC NULLS LAST
LIMIT 5;
  sale_year sale_quarter sale_amount growth_rate
1      1997            4      481323        1.40
2      2022            3      460742        1.21
3      2007            2      488715        1.18
4      2000            2      520574        1.18
5      2004            2      501878        1.17
0.015 sec elapsed

Day 22

Day 22

SELECT COUNT(elf_name) AS numofelveswithsql
FROM elves
WHERE 'SQL' = ANY(STRING_TO_ARRAY(skills, ','));
  numofelveswithsql
1              2488
0.023 sec elapsed

Day 23

Day 23

WITH lag_values AS (
    SELECT
        LAG(id, 1) OVER (ORDER BY id) + 1 AS gap_start,
        id - 1 AS gap_end,
        id - LAG(id, 1) OVER (ORDER BY id) AS gap
    FROM sequence_table
),

long_gaps AS (
    SELECT
        gap_start,
        gap_end,
        GENERATE_SERIES(gap_start, gap_end) AS gap_numbers
    FROM lag_values
    WHERE gap > 1
)

SELECT
    gap_start,
    gap_end,
    ARRAY_AGG(gap_numbers ORDER BY gap_start) AS missing_numbers
FROM long_gaps
GROUP BY gap_start, gap_end;
  gap_start gap_end            missing_numbers
1       997    1001    {997,998,999,1000,1001}
2      3761    3765 {3761,3762,3763,3764,3765}
3      6525    6527           {6525,6526,6527}
4      6529    6529                     {6529}
5      9289    9292      {9289,9290,9291,9292}
0.012 sec elapsed

Day 24

Day 24

SELECT
    songs.song_title,
    COUNT(songs.song_title) AS total_plays,
    SUM(
        CASE WHEN up.duration < songs.song_duration THEN 1 ELSE 0 END
    ) AS skipped
FROM songs
INNER JOIN user_plays AS up ON songs.song_id = up.song_id
GROUP BY songs.song_title
ORDER BY total_plays DESC, skipped ASC
LIMIT 10;
                               song_title total_plays skipped
1         All I Want For Christmas Is You         562     412
2                          The First Noel         541     416
3                            Jingle Bells         524     399
4                          Deck the Halls         521     395
5                            Silent Night         513     389
6                       Winter Wonderland         511     386
7                            Silver Bells         508     386
8  Let It Snow! Let It Snow! Let It Snow!         506     381
9                         White Christmas         504     382
10                         Last Christmas         503     385
0.011 sec elapsed