Sample dataset · CC0

Orders whose keys resolve.

Ten columns of order header, built to be joined. Every customer_id is a row in the customers file, every sku is a row in the products file, unit_price is the catalog price for that SKU, and subtotal plus shipping equals total on every line.

10 columns · 100 or 1,000 rows as a file · up to 1,000,000 rows generated here · CSV, JSON, JSONL, Excel, Parquet

Take a file, or take three

Take this with customers-1000.csv and products-100.csv and you have a three-table join that reconciles exactly. That combination is the reason this dataset exists.

Bigger sizes, built here

At larger sizes the keys stay inside the same space: customer_id 1001 to 2000 and SKU-1001 to SKU-1060. A million orders still join cleanly against the 1,000-row customers file, which is what makes this a fair test of a join at scale.

The first eight rows

The first eight rows of the 1,000-row file. Compare unit_price against the products dataset for the same SKU.

order_idcustomer_idskuorder_datestatusquantityunit_pricesubtotalshippingtotal
5000011653SKU-10092024-07-01delivered4226.78907.120.00907.12
5000021371SKU-10262024-07-02delivered4121.26485.040.00485.04
5000031290SKU-10242024-07-02returned3423.381270.140.001270.14
5000041230SKU-10552024-07-03shipped366.95200.850.00200.85
5000051479SKU-10112024-07-04delivered2298.51597.020.00597.02
5000061227SKU-10272024-07-05delivered6128.01768.060.00768.06
5000071327SKU-10482024-07-05shipped3122.57367.710.00367.71
5000081309SKU-10142024-07-06cancelled3332.56997.680.00997.68

Ten columns, and what each one holds

Column Type What it holds Example
order_id integer Primary key, 500001 upward. 500001
customer_id integer Foreign key into customers, 1001 to 2000. 1653
sku text Foreign key into products, SKU-1001 to SKU-1060. SKU-1009
order_date date Date the order was placed, ascending through the file. 2024-07-01
status text delivered, shipped, processing, returned or cancelled. delivered
quantity integer Units on the order, 1 to 6. 4
unit_price decimal Price paid per unit. Matches products.price for the same sku. 226.78
subtotal decimal quantity times unit_price. 907.12
shipping decimal Flat rate by weight band, 0.00 on orders over 75.00. 0.00
total decimal subtotal plus shipping. Adds up on every row. 907.12

What it models

An order header table from a small e-commerce system: who ordered, what they ordered, when, how it went, and what it cost. One row is one order for one SKU, which is a simplification real systems avoid, and it is the right simplification here because it keeps the join two-dimensional and the arithmetic visible on the row.

Statuses are weighted the way a real order table is weighted: delivered dominates, shipped and processing are the live tail, and returned and cancelled are the small unhappy fraction that every revenue query has to decide what to do about. That decision, whether to filter them out before summing, is exactly the question a sample dataset should be forcing you to ask.

Why the join is the point

Most sample order files have a customer_id column full of random integers that point at nothing. You can write the JOIN, but you cannot check the answer, because there is no other side. Here there is: customer_id is always between 1001 and 2000, which is precisely the range in the 1,000-row customers file, and sku is always between SKU-1001 and SKU-1060, which is inside the 100-row products file.

So an inner join loses no rows, a left join produces no nulls, and a count before and after the join is the same number. When you are learning joins, or testing a tool's join, that property is what tells you whether the result is right. If your join drops rows against these files, the join is wrong, not the data.

There is a second, sharper check available. unit_price on an order is the catalog price for that SKU, so joining orders to products and comparing orders.unit_price against products.price must return zero mismatches across all 1,000 rows. A test in the repository asserts exactly that.

The money columns

subtotal is quantity multiplied by unit_price. shipping is a flat band that drops to 0.00 once subtotal passes 75.00, which puts a real conditional into the data instead of a smooth number. total is subtotal plus shipping. All three are computed and rounded once, so no floating point residue leaks into the file and no row is off by a cent.

That free shipping threshold is more useful than it looks. It gives you a genuine discontinuity to find: bucket orders by subtotal, chart mean shipping, and the cliff at 75.00 shows up immediately. It is a good first exercise in "the data has a rule in it, find the rule".

What people use it for

  • Teaching or testing joins, where the correct row count is known in advance.
  • Revenue analysis that has to decide what to do with returned and cancelled orders.
  • Finding a rule hidden in the data: the free shipping threshold at 75.00.
  • Checking that a tool preserves a text primary key like SKU-1042 without mangling it.
  • Order funnel and status breakdown charts with a realistic status mix.

The three files that fit together

customer_id is 1001-2000 and sku is SKU-1001 to SKU-1060; unit_price is the catalogue price for that SKU, so a three-way join reconciles to the cent.

Download all three and the exercise writes itself: join orders to customers on customer_id, join the result to products on sku, then group by plan and category. Because the price on the order is the catalog price, the totals reconcile exactly, so you can tell the difference between a wrong answer and an approximate one.

License, and the people in it

This dataset is dedicated to the public domain under CC0 1.0. Put it in a course, a paid product, a test suite, a bug report, a screenshot, a conference talk or a book. There is nothing to ask for, nothing to sign and no attribution required. A link back is welcome and is not a condition.

There are no people in this file at all, only the integer keys that point at them. The customers file it joins to holds synthetic names and addresses on the reserved example.com domain, so even a full join produces nothing that describes a real person.

Questions people ask about this file

Do all the foreign keys really resolve?

Yes, and it is enforced by a test. Every customer_id is between 1001 and 2000, which is exactly the id range of the 1,000-row customers file, and every sku is between SKU-1001 and SKU-1060, which is inside the 100-row products file. An inner join against either loses no rows, at any size of orders file, including a million rows.

Does unit_price match the products catalog?

On every row. The price of a SKU is a pure function of the SKU, and both files call it, so joining orders to products and comparing orders.unit_price against products.price returns zero mismatches. That is the second check the repository's test suite runs on this dataset, after the key check.

Why is shipping sometimes exactly 0.00?

Because there is a free shipping threshold at a subtotal of 75.00, and it is in the data on purpose. It gives you a real discontinuity to discover rather than a smooth random column: bucket by subtotal, average the shipping, and the cliff appears. Sample data with no rules in it teaches people that data has no rules in it.

Should I filter out cancelled and returned orders before summing revenue?

That is the question the file is asking you. About one row in eight is returned or cancelled, which is roughly realistic, and whether they belong in a revenue total depends on what the total is for. Both answers are defensible; the failure mode is not noticing the column exists, which is why the status mix is weighted rather than uniform.