ExpertJoins50 points

Basket pairs: what sells together

The recommendation team wants a quick "Frequently bought together" list before any machine learning. They look at pairs of different products that appear in the same non-cancelled order. Each pair must be counted once, regardless of order, so list the product with the smaller id first. Self joining order_items on order_id with a.product_id < b.product_id does exactly that.

Task: return product_a and product_b (product names, product_a has the smaller product id) and orders_together (number of orders containing both). Keep the top 8 pairs: sort by orders_together from high to low, then by product_a A to Z, then by product_b A to Z.

The data you can query

Each table is shown with its column types and the first rows, so you can see exactly what the data looks like.

customers48 rows
id INTEGERname TEXTcountry TEXTcity TEXTsignup_date TEXTloyalty_tier TEXT
1Emily CarterUnited StatesAustin2025-01-11NULL
2Michael BrooksUnited StatesSeattle2025-01-19Silver
3Sofia RamirezUnited StatesMiami2025-08-21Gold
4Jake TurnerUnited StatesChicago2025-03-21Bronze
5Oliver HughesUnited KingdomManchester2025-03-22Bronze
6Amelia WrightUnited KingdomLondon2025-10-14Platinum

Showing the first 6 of 48 rows.

order_items719 rows
id INTEGERorder_id INTEGERproduct_id INTEGERquantity INTEGERunit_price REALdiscount_pct INTEGER
1115394.950
2110224.990
322112920
4315394.9515
5424112.9910
64211290

Showing the first 6 of 719 rows.

orders340 rows
id INTEGERcustomer_id INTEGERorder_date TEXTship_date TEXTdelivered_date TEXTstatus TEXTchannel TEXT
112025-01-162025-01-192025-01-21DeliveredMobile App
212025-01-232025-01-252025-01-30DeliveredWeb
312025-01-232025-01-252025-01-29DeliveredWeb
422025-01-312025-02-022025-02-04DeliveredWeb
512025-02-022025-02-032025-02-06DeliveredSocial Media
6312025-02-142025-02-152025-02-20DeliveredWeb

Showing the first 6 of 340 rows.

payments329 rows
id INTEGERorder_id INTEGERmethod TEXTamount REAL
11PayPal334.83
22PayPal23.2
33Card242.12
44Card40.69
55Card148.14
66Gift Card42.95

Showing the first 6 of 329 rows.

products36 rows
id INTEGERname TEXTcategory TEXTseller_id INTEGERprice REALcost REAL
1Noise-Cancelling HeadphonesElectronics8129.9971
2Smart Fitness WatchElectronics789.548
3Portable Power Bank 20000mAhElectronics834.9917
44K Action CameraElectronics115996
5USB-C Charging HubElectronics627.511
6Wireless Gaming MouseElectronics145.9922

Showing the first 6 of 36 rows.

returns52 rows
id INTEGERorder_item_id INTEGERreturn_date TEXTreason TEXTrefund_amount REAL
172025-02-10Wrong size18.99
2312025-04-12Not as described34.99
3392025-04-06Wrong size31.05
4502025-05-09Changed my mind169
5692025-05-27Arrived late34.99
6742025-06-02Arrived late23.92

Showing the first 6 of 52 rows.

reviews261 rows
id INTEGERorder_item_id INTEGERrating INTEGERcomment_length INTEGERreview_date TEXT
112NULL2025-01-22
2242322025-02-02
374182025-02-16
41252552025-03-14
51511612025-03-15
61742082025-03-19

Showing the first 6 of 261 rows.

sellers10 rows
id INTEGERname TEXTcountry TEXTjoined_date TEXT
1Northwind TradersUnited States2024-06-14
2Brightside GoodsUnited Kingdom2024-09-02
3Kaufhaus BerlinGermany2024-11-20
4Maison PetiteFrance2025-01-15
5Rio BazaarBrazil2024-08-08
6Bharat EmporiumIndia2024-07-19

Showing the first 6 of 10 rows.

Your query runs on SQLite. Write a single SELECT; use LIMIT instead of TOP.

Need a hint?

Join order_items to itself; the inequality on product_id removes mirror pairs and self pairs.