cswork / libraryOpen practice workspace

Coding questions · DoorDash

Write SQL for cuisine median delivery times

This question evaluates SQL proficiency in computing cuisine-level delivery performance metrics, including time-window filtering, joins, window/percentile functions for median aggregation, null handling, and percent-change calculations.

Use SQL to answer the following. Assume ANSI SQL with window functions and percentile functions available. Treat “today” as 2025-09-01 (inclusive). Compute cuisine-level performance.

Schema and tiny sample data (for illustration only):

restaurants(restaurant_id INT, name TEXT, cuisine TEXT, is_ghost_kitchen BOOLEAN)
+---------------+------------+---------+-------------------+
| restaurant_id | name | cuisine | is_ghost_kitchen |
+---------------+------------+---------+-------------------+
| 1 | Sushi A | Sushi | false |
| 2 | Pizza B | Pizza | false |
| 3 | Wings C | American| true |
| 4 | Curry D | Indian | false |
+---------------+------------+---------+-------------------+
orders(order_id INT, restaurant_id INT, created_at TIMESTAMP, status TEXT, subtotal NUMERIC, city TEXT)
+----------+---------------+---------------------+-----------+----------+----------+
| order_id | restaurant_id | created_at | status | subtotal | city |
+----------+---------------+---------------------+-----------+----------+----------+
| 101 | 1 | 2025-08-25 18:02:00 | completed | 42.00 | SF |
| 102 | 2 | 2025-08-26 19:10:00 | completed | 28.50 | SF |
| 103 | 3 | 2025-08-27 12:05:00 | completed | 19.99 | SJ |
| 104 | 2 | 2025-08-28 20:14:00 | canceled | 31.00 | SF |
| 105 | 4 | 2025-08-31 11:40:00 | completed | 22.00 | OAK |
| 106 | 1 | 2025-09-01 13:22:00 | completed | 35.00 | SF |
+----------+---------------+---------------------+-----------+----------+----------+
deliveries(delivery_id INT, order_id INT, pickup_time TIMESTAMP, dropoff_time TIMESTAMP, tip NUMERIC, distance_miles NUMERIC)
+-------------+----------+---------------------+---------------------+------+----------------+
| delivery_id | order_id | pickup_time | dropoff_time | tip | distance_miles |
+-------------+----------+---------------------+---------------------+------+----------------+
| 9001 | 101 | 2025-08-25 18:18:00 | 2025-08-25 18:36:00 | 6.00 | 3.2 |
| 9002 | 102 | 2025-08-26 19:22:00 | 2025-08-26 19:52:00 | 5.00 | 7.5 |
| 9003 | 103 | 2025-08-27 12:20:00 | 2025-08-27 12:33:00 | 3.00 | 2.1 |
| 9004 | 104 | NULL | NULL | NULL | NULL |
| 9005 | 105 | 2025-08-31 11:55:00 | 2025-08-31 12:28:00 | 4.00 | 12.0 |
| 9006 | 106 | 2025-09-01 13:35:00 | 2025-09-01 13:55:00 | 2.00 | 4.0 |
+-------------+----------+---------------------+---------------------+------+----------------+

Task: Write a single SQL query that returns, for each cuisine: (a) median delivery_time_minutes = TIMESTAMP_DIFF(dropoff_time, pickup_time, MINUTE) for completed orders with distance_miles < 10 in the last 7 full days including 2025-09-01 (window A: 2025-08-25 to 2025-09-01), excluding ghost kitchens; (b) the same median for the preceding 7-day window (window B: 2025-08-18 to 2025-08-24); (c) pct_change = (A − B)/NULLIF(B,0); and (d) include only cuisines with at least 30 qualifying deliveries in window A. Output columns: cuisine, recent_median_min, prev_median_min, pct_change, n_recent. Assume UTC timestamps and that canceled orders have no delivery. Do not use temporary tables; use CTEs or subqueries only.

Compare Cuisine Delivery-Time Medians Across Two Windows

Using PostgreSQL, compute delivery-time performance by cuisine for the two explicit UTC windows in this problem: the recent window is 2025-08-25 00:00:00 inclusive through 2025-09-02 00:00:00 exclusive (so it includes all of 2025-09-01), and the preceding window is 2025-08-18 00:00:00 inclusive through 2025-08-25 00:00:00 exclusive. A qualifying delivery belongs to a completed order, has non-NULL pickup and dropoff timestamps, has distance_miles < 10, and comes from a restaurant that is not a ghost kitchen. Define delivery_time_minutes as the elapsed seconds from pickup_time to dropoff_time divided by 60. Return one row per cuisine with: cuisine, recent_median_min, prev_median_min, pct_change = (recent_median_min - prev_median_min) / NULLIF(prev_median_min, 0), and n_recent. Include only cuisines with at least 30 qualifying deliveries in the recent window. If a qualifying cuisine has no preceding-window deliveries, its preceding median and percentage change should be NULL. Order by cuisine ascending. Use one read-only query with CTEs or subqueries and no temporary tables.

Table restaurants

columntypeconstraints
restaurant_idINTEGERPRIMARY KEY
nameTEXTNOT NULL
cuisineTEXTNOT NULL
is_ghost_kitchenBOOLEANNOT NULL

Sample data:

0123
1Sushi ASushifalse
2Pizza BPizzafalse
3Wings CAmericantrue
4Curry DIndianfalse
5Sushi GhostSushitrue

Table orders

columntypeconstraints
order_idINTEGERPRIMARY KEY
restaurant_idINTEGERNOT NULL
created_atTIMESTAMPNOT NULL
statusTEXTNOT NULL
subtotalNUMERIC(10,2)NOT NULL
cityTEXTNOT NULL

Sample data:

012345
100112025-08-25 00:00:00completed31SF
100212025-08-26 09:00:00completed32SF
100312025-08-26 09:01:00completed33SF
100412025-08-26 09:02:00completed34SF
100512025-08-26 09:03:00completed35SF
100612025-08-27 09:00:00completed36SF
100712025-08-27 09:01:00completed37SF
100812025-08-27 09:02:00completed38SF
100912025-08-27 09:03:00completed39SF
101012025-08-28 09:00:00completed40SF
101112025-08-28 09:01:00completed41SF
101212025-08-28 09:02:00completed42SF
101312025-08-28 09:03:00completed43SF
101412025-08-29 09:00:00completed44SF
101512025-08-29 09:01:00completed45SF
101612025-08-29 09:02:00completed46SF
101712025-08-29 09:03:00completed47SF
101812025-08-30 09:00:00completed48SF
101912025-08-30 09:01:00completed49SF
102012025-08-30 09:02:00completed50SF
102112025-08-30 09:03:00completed51SF
102212025-08-31 09:00:00completed52SF
102312025-08-31 09:01:00completed53SF
102412025-08-31 09:02:00completed54SF
102512025-08-31 09:03:00completed55SF
102612025-09-01 08:00:00completed56SF
102712025-09-01 08:01:00completed57SF
102812025-09-01 08:02:00completed58SF
102912025-09-01 08:03:00completed59SF
103012025-09-01 23:00:00completed60SF
110112025-08-18 00:00:00completed41SF
110212025-08-20 11:00:00completed42SF
110312025-08-22 12:00:00completed43SF
110412025-08-24 23:00:00completed44SF
200122025-08-30 09:00:00completed21OAK
200222025-08-30 09:01:00completed22OAK
200322025-08-30 09:02:00completed23OAK
200422025-08-30 09:03:00completed24OAK
200522025-08-30 09:04:00completed25OAK
200622025-08-30 09:05:00completed26OAK
200722025-08-30 09:06:00completed27OAK
200822025-08-30 09:07:00completed28OAK
200922025-08-30 09:08:00completed29OAK
201022025-08-30 09:09:00completed30OAK
201122025-08-30 09:10:00completed31OAK
201222025-08-30 09:11:00completed32OAK
201322025-08-30 09:12:00completed33OAK
201422025-08-30 09:13:00completed34OAK
201522025-08-30 09:14:00completed35OAK
201622025-08-30 09:15:00completed36OAK
201722025-08-30 09:16:00completed37OAK
201822025-08-30 09:17:00completed38OAK
201922025-08-30 09:18:00completed39OAK
202022025-08-30 09:19:00completed40OAK
202122025-08-30 09:20:00completed41OAK
202222025-08-30 09:21:00completed42OAK
202322025-08-30 09:22:00completed43OAK
202422025-08-30 09:23:00completed44OAK
202522025-08-30 09:24:00completed45OAK
202622025-08-30 09:25:00completed46OAK
202722025-08-30 09:26:00completed47OAK
202822025-08-30 09:27:00completed48OAK
202922025-08-30 09:28:00completed49OAK
300152025-08-29 12:00:00completed22SF
300212025-08-29 12:01:00canceled23SF
300312025-08-29 12:02:00completed24SF
300412025-09-02 00:00:00completed25SF
300512025-08-17 23:59:59completed26SF
300612025-08-29 12:03:00completed27SF

Table deliveries

columntypeconstraints
delivery_idINTEGERPRIMARY KEY
order_idINTEGERNOT NULL
pickup_timeTIMESTAMP
dropoff_timeTIMESTAMP
tipNUMERIC(10,2)
distance_milesNUMERIC(6,2)

Sample data:

012345
900110012025-08-25 00:10:002025-08-25 00:21:0042
900210022025-08-26 10:00:002025-08-26 10:12:0042
900310032025-08-26 10:00:002025-08-26 10:13:0042
900410042025-08-26 10:00:002025-08-26 10:14:0042
900510052025-08-26 10:00:002025-08-26 10:15:0042
900610062025-08-27 10:00:002025-08-27 10:16:0042
900710072025-08-27 10:00:002025-08-27 10:17:0042
900810082025-08-27 10:00:002025-08-27 10:18:0042
900910092025-08-27 10:00:002025-08-27 10:19:0042
901010102025-08-28 10:00:002025-08-28 10:20:0042
901110112025-08-28 10:00:002025-08-28 10:21:0042
901210122025-08-28 10:00:002025-08-28 10:22:0042
901310132025-08-28 10:00:002025-08-28 10:23:0042
901410142025-08-29 10:00:002025-08-29 10:24:0042
901510152025-08-29 10:00:002025-08-29 10:25:0042
901610162025-08-29 10:00:002025-08-29 10:26:0042
901710172025-08-29 10:00:002025-08-29 10:27:0042
901810182025-08-30 10:00:002025-08-30 10:28:0042
901910192025-08-30 10:00:002025-08-30 10:29:0042
902010202025-08-30 10:00:002025-08-30 10:30:0042
902110212025-08-30 10:00:002025-08-30 10:31:0042
902210222025-08-31 10:00:002025-08-31 10:32:0042
902310232025-08-31 10:00:002025-08-31 10:33:0042
902410242025-08-31 10:00:002025-08-31 10:34:0042
902510252025-08-31 10:00:002025-08-31 10:35:0042
902610262025-09-01 10:00:002025-09-01 10:36:0042
902710272025-09-01 10:00:002025-09-01 10:37:0042
902810282025-09-01 10:00:002025-09-01 10:38:0042
902910292025-09-01 10:00:002025-09-01 10:39:0042
903010302025-09-01 23:10:002025-09-01 23:50:0042
910111012025-08-18 00:10:002025-08-18 00:20:0042
910211022025-08-20 11:10:002025-08-20 11:30:0042
910311032025-08-22 12:10:002025-08-22 12:40:0042
910411042025-08-24 23:10:002025-08-24 23:50:0042
920120012025-08-30 10:00:002025-08-30 10:18:0033
920220022025-08-30 10:01:002025-08-30 10:19:0033
920320032025-08-30 10:02:002025-08-30 10:20:0033
920420042025-08-30 10:03:002025-08-30 10:21:0033
920520052025-08-30 10:04:002025-08-30 10:22:0033
920620062025-08-30 10:05:002025-08-30 10:23:0033
920720072025-08-30 10:06:002025-08-30 10:24:0033
920820082025-08-30 10:07:002025-08-30 10:25:0033
920920092025-08-30 10:08:002025-08-30 10:26:0033
921020102025-08-30 10:09:002025-08-30 10:27:0033
921120112025-08-30 10:10:002025-08-30 10:28:0033
921220122025-08-30 10:11:002025-08-30 10:29:0033
921320132025-08-30 10:12:002025-08-30 10:30:0033
921420142025-08-30 10:13:002025-08-30 10:31:0033
921520152025-08-30 10:14:002025-08-30 10:32:0033
921620162025-08-30 10:15:002025-08-30 10:33:0033
921720172025-08-30 10:16:002025-08-30 10:34:0033
921820182025-08-30 10:17:002025-08-30 10:35:0033
921920192025-08-30 10:18:002025-08-30 10:36:0033
922020202025-08-30 10:19:002025-08-30 10:37:0033
922120212025-08-30 10:20:002025-08-30 10:38:0033
922220222025-08-30 10:21:002025-08-30 10:39:0033
922320232025-08-30 10:22:002025-08-30 10:40:0033
922420242025-08-30 10:23:002025-08-30 10:41:0033
922520252025-08-30 10:24:002025-08-30 10:42:0033
922620262025-08-30 10:25:002025-08-30 10:43:0033
922720272025-08-30 10:26:002025-08-30 10:44:0033
922820282025-08-30 10:27:002025-08-30 10:45:0033
922920292025-08-30 10:28:002025-08-30 10:46:0033
930130012025-08-29 13:00:002025-08-29 13:14:0022
930230022025-08-29 13:01:002025-08-29 13:16:00NULL2
930330032025-08-29 13:02:002025-08-29 13:18:00210
930430042025-09-02 00:10:002025-09-02 00:27:0022
930530052025-08-18 00:10:002025-08-18 00:28:0022
93063006NULLNULLNULL2

Expected output

cuisinerecent_median_minprev_median_minpct_changen_recent
Sushi25.5250.0230

Hints

  1. Filter completed, non-ghost, under-10-mile deliveries with usable timestamps before aggregating.
  2. PostgreSQL's PERCENTILE_CONT(0.5) WITHIN GROUP computes a continuous median; FILTER can apply each date window to the same grouped cuisine.
  3. Apply the recent-delivery threshold with a filtered COUNT in HAVING, then calculate pct_change from the two medians.