Every post in the Production SQL series queries the same database: CellarDB, a mid-size California winery. Estate and purchased fruit come in, wine lots move through barrels and blends, and bulk wine goes out on contracts that get amended after signature. This page builds it. Run the script once and you're set for the whole series.
Why a winery instead of the usual orders-and-customers store? Because the hard problems this series teaches arise from this data on their own. A blend draws from many wine lots. A lot fills many barrels, and a barrel is filled many times across vintages. A contract's terms change months after the ink dries. Nothing has to be contrived; the structure produces the traps. I also spent a decade around data shaped like this, which means the examples can be honest about how it behaves.
What you need
The same two pieces as ever: an engine and a client.
- SQL Server 2022 Developer Edition, free and full-featured, from the SQL Server downloads page. Basic installation, default settings.
- SQL Server Management Studio (SSMS), from the SSMS download page. Connect with Windows Authentication.
On Mac or Linux, run the mcr.microsoft.com/mssql/server:2022-latest Docker image and connect with Azure Data Studio or any SQL client. Everything in the series is plain T-SQL; the client doesn't matter.
The tables, by grain
The single most useful thing to know about any table is what one row means. Here is the answer for all twelve, up front:
VineyardBlocks: one row per vineyard block (~40 rows)FruitLots: one row per fruit delivery: a block, a vintage, a pick (~330 rows)WineLots: one row per wine lot in the cellar (~335 rows)Blends: one row per finished blend (~60 rows)BlendComponents: one row per blend-and-wine-lot pairing (~280 rows)Barrels: one row per physical barrel (~900 rows)BarrelFills: one row per barrel-fill event, not per barrel and not per lot (~2,600 rows)WorkOrders: one row per cellar operation logged against a lot (~5,000 rows)Customers: one row per bulk-wine buyer (25 rows)BulkContracts: one row per contract, holding the original terms only (~45 rows)ContractAmendments: one row per amendment to a contract (~30 rows)Shipments: one row per shipment against a contract (~100 rows)
Row counts are approximate by design; the generators target those sizes.
A note on the data
The script is deterministic. There is no RAND() and no NEWID() anywhere in it; every "random" value is computed arithmetically from stable inputs. Run it twice, or on two machines, and you get the same database both times. When a later post says a query returns a particular result, yours will match.
The data is imperfect on purpose. There are missing lab numbers, a double-entered record, and a contract whose terms changed twice after signature. The comments in the script mark these as deliberate wrinkles. Later posts in the series depend on every one of them, so resist the urge to clean anything.
The setup script
Run the whole thing in one go. It drops and rebuilds, so it's safe to re-run.
-- CellarDB setup script
-- Companion to the Production SQL series. Target: SQL Server 2022.
-- Deterministic: no RAND(), no NEWID(). Re-running produces the same data.
-- GENERATE_SERIES requires database compatibility level 160, which is the
-- default for a database created on SQL Server 2022.
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE name = 'CellarDB')
CREATE DATABASE CellarDB;
GO
USE CellarDB;
GO
DROP TABLE IF EXISTS Shipments, ContractAmendments, BulkContracts, Customers,
WorkOrders, BarrelFills, Barrels, BlendComponents,
Blends, WineLots, FruitLots, VineyardBlocks;
GO
-- ---------------------------------------------------------------------------
-- Tables
-- ---------------------------------------------------------------------------
CREATE TABLE VineyardBlocks ( -- one row per vineyard block
BlockID INT IDENTITY(1,1) PRIMARY KEY,
BlockCode VARCHAR(12) NOT NULL UNIQUE,
VineyardName NVARCHAR(60) NOT NULL,
Region NVARCHAR(40) NOT NULL,
Variety NVARCHAR(30) NOT NULL,
Acres DECIMAL(5,2) NOT NULL,
PlantedYear SMALLINT NULL -- unknown for two leased blocks
);
CREATE TABLE FruitLots ( -- one row per fruit delivery (block x vintage x pick)
FruitLotID INT IDENTITY(1,1) PRIMARY KEY,
BlockID INT NOT NULL REFERENCES VineyardBlocks (BlockID),
Vintage SMALLINT NOT NULL,
PickDate DATE NOT NULL,
Tons DECIMAL(6,2) NOT NULL,
Brix DECIMAL(4,1) NULL, -- NULL = arrived unsampled
pH DECIMAL(3,2) NULL
);
CREATE TABLE WineLots ( -- one row per wine lot in the cellar
WineLotID INT IDENTITY(1,1) PRIMARY KEY,
LotCode VARCHAR(16) NOT NULL UNIQUE,
FruitLotID INT NULL REFERENCES FruitLots (FruitLotID), -- NULL = purchased bulk wine
Vintage SMALLINT NOT NULL,
Variety NVARCHAR(30) NOT NULL,
VolumeGal DECIMAL(8,1) NOT NULL,
Status VARCHAR(12) NOT NULL -- Aging | Blended | Sold | Dumped
);
CREATE TABLE Blends ( -- one row per finished blend
BlendID INT IDENTITY(1,1) PRIMARY KEY,
BlendCode VARCHAR(16) NOT NULL UNIQUE,
BlendName NVARCHAR(60) NOT NULL,
Vintage SMALLINT NOT NULL,
TargetVolumeGal DECIMAL(8,1) NOT NULL,
BottlingDate DATE NULL -- NULL = not yet bottled
);
CREATE TABLE BlendComponents ( -- one row per (blend, wine lot) component
BlendID INT NOT NULL REFERENCES Blends (BlendID),
WineLotID INT NOT NULL REFERENCES WineLots (WineLotID),
ComponentPct DECIMAL(5,2) NULL,
VolumeUsedGal DECIMAL(8,1) NOT NULL,
PRIMARY KEY (BlendID, WineLotID)
);
CREATE TABLE Barrels ( -- one row per physical barrel
BarrelID INT IDENTITY(1,1) PRIMARY KEY,
BarrelCode VARCHAR(12) NOT NULL UNIQUE,
Cooper NVARCHAR(40) NOT NULL,
Origin NVARCHAR(30) NULL, -- French | American | Hungarian
ToastLevel VARCHAR(8) NULL,
CapacityGal DECIMAL(6,1) NOT NULL, -- mostly 59.4 (225 L)
FirstFillYear SMALLINT NOT NULL
);
CREATE TABLE BarrelFills ( -- one row per barrel-fill EVENT
BarrelFillID INT IDENTITY(1,1) PRIMARY KEY,
BarrelID INT NOT NULL REFERENCES Barrels (BarrelID),
WineLotID INT NOT NULL REFERENCES WineLots (WineLotID),
FillDate DATE NOT NULL,
EmptyDate DATE NULL, -- NULL = still in barrel
FillVolumeGal DECIMAL(6,1) NOT NULL
);
CREATE TABLE WorkOrders ( -- one row per cellar operation logged
WorkOrderID INT IDENTITY(1,1) PRIMARY KEY,
WineLotID INT NOT NULL REFERENCES WineLots (WineLotID),
WorkType VARCHAR(20) NOT NULL, -- PumpOver | Racking | SO2Add | Topping | Filtration | Transfer
ScheduledDate DATE NOT NULL,
CompletedDate DATE NULL, -- NULL = open
CellarHand NVARCHAR(40) NULL,
LaborHours DECIMAL(4,1) NULL
);
CREATE TABLE Customers ( -- one row per bulk-wine buyer
CustomerID INT IDENTITY(1,1) PRIMARY KEY,
CustomerName NVARCHAR(60) NOT NULL,
State CHAR(2) NULL,
CustomerType VARCHAR(12) NOT NULL -- Winery | Negociant | Broker
);
CREATE TABLE BulkContracts ( -- one row per contract, ORIGINAL terms
ContractID INT IDENTITY(1,1) PRIMARY KEY,
ContractNumber VARCHAR(12) NOT NULL UNIQUE,
CustomerID INT NOT NULL REFERENCES Customers (CustomerID),
ContractDate DATE NOT NULL,
Variety NVARCHAR(30) NOT NULL,
Vintage SMALLINT NOT NULL,
VolumeGal DECIMAL(8,1) NOT NULL,
PricePerGal DECIMAL(8,2) NOT NULL,
Status VARCHAR(12) NOT NULL -- Open | Fulfilled | Cancelled
);
CREATE TABLE ContractAmendments ( -- one row per amendment
AmendmentID INT IDENTITY(1,1) PRIMARY KEY,
ContractID INT NOT NULL REFERENCES BulkContracts (ContractID),
AmendmentDate DATE NOT NULL,
NewVolumeGal DECIMAL(8,1) NULL, -- NULL = volume unchanged
NewPricePerGal DECIMAL(8,2) NULL, -- NULL = price unchanged
Reason NVARCHAR(100) NULL
);
CREATE TABLE Shipments ( -- one row per shipment against a contract
ShipmentID INT IDENTITY(1,1) PRIMARY KEY,
ContractID INT NOT NULL REFERENCES BulkContracts (ContractID),
ShipDate DATE NOT NULL,
VolumeGal DECIMAL(8,1) NOT NULL,
CarrierRef VARCHAR(20) NULL
);
GO
-- ---------------------------------------------------------------------------
-- VineyardBlocks: 40 blocks across seven vineyards.
-- ---------------------------------------------------------------------------
INSERT INTO VineyardBlocks (BlockCode, VineyardName, Region, Variety, Acres, PlantedYear)
VALUES
('HR-01', N'Home Ranch', N'Russian River Valley', N'Pinot Noir', 6.20, 1999),
('HR-02', N'Home Ranch', N'Russian River Valley', N'Pinot Noir', 4.80, 1999),
('HR-03', N'Home Ranch', N'Russian River Valley', N'Pinot Noir', 7.50, 2004),
('HR-04', N'Home Ranch', N'Russian River Valley', N'Pinot Noir', 5.10, 2010),
('HR-05', N'Home Ranch', N'Russian River Valley', N'Chardonnay', 8.40, 1997),
('HR-06', N'Home Ranch', N'Russian River Valley', N'Chardonnay', 6.60, 2002),
('HR-07', N'Home Ranch', N'Russian River Valley', N'Pinot Noir', 5.90, 2012),
('HR-08', N'Home Ranch', N'Russian River Valley', N'Chardonnay', 4.20, 2015),
('RB-01', N'River Bend', N'Russian River Valley', N'Chardonnay', 9.10, 2001),
('RB-02', N'River Bend', N'Russian River Valley', N'Chardonnay', 7.30, 2005),
('RB-03', N'River Bend', N'Russian River Valley', N'Pinot Noir', 6.80, 2008),
('RB-04', N'River Bend', N'Russian River Valley', N'Pinot Noir', 5.40, 2008),
('RB-05', N'River Bend', N'Russian River Valley', N'Syrah', 4.60, 2011),
('RB-06', N'River Bend', N'Russian River Valley', N'Chardonnay', 8.80, 2016),
('SC-01', N'Stone Corral', N'Alexander Valley', N'Cabernet Sauvignon', 10.20, 1996),
('SC-02', N'Stone Corral', N'Alexander Valley', N'Cabernet Sauvignon', 8.70, 2000),
('SC-03', N'Stone Corral', N'Alexander Valley', N'Cabernet Sauvignon', 7.90, 2003),
('SC-04', N'Stone Corral', N'Alexander Valley', N'Merlot', 6.30, 2003),
('SC-05', N'Stone Corral', N'Alexander Valley', N'Merlot', 5.80, 2009),
('SC-06', N'Stone Corral', N'Alexander Valley', N'Cabernet Sauvignon', 9.40, 2014),
('DB-01', N'Dry Creek Bench', N'Dry Creek Valley', N'Zinfandel', 7.10, 1989),
('DB-02', N'Dry Creek Bench', N'Dry Creek Valley', N'Zinfandel', 5.50, 1992),
('DB-03', N'Dry Creek Bench', N'Dry Creek Valley', N'Zinfandel', 6.00, 2001),
('DB-04', N'Dry Creek Bench', N'Dry Creek Valley', N'Petite Sirah', 4.40, 2006),
('DB-05', N'Dry Creek Bench', N'Dry Creek Valley', N'Petite Sirah', 3.90, 2013),
('ET-01', N'Eastside Terrace', N'Sonoma Coast', N'Pinot Noir', 5.20, 2007),
('ET-02', N'Eastside Terrace', N'Sonoma Coast', N'Pinot Noir', 6.70, 2007),
('ET-03', N'Eastside Terrace', N'Sonoma Coast', N'Chardonnay', 7.00, 2010),
('ET-04', N'Eastside Terrace', N'Sonoma Coast', N'Syrah', 4.10, 2012),
('ET-05', N'Eastside Terrace', N'Sonoma Coast', N'Pinot Noir', 5.60, 2017),
('OL-01', N'Olsen Lease', N'Clarksburg', N'Chardonnay', 11.30, 2003),
('OL-02', N'Olsen Lease', N'Clarksburg', N'Chardonnay', 9.60, NULL), -- leased; planting year unknown
('OL-03', N'Olsen Lease', N'Clarksburg', N'Sauvignon Blanc', 8.20, 2008),
('OL-04', N'Olsen Lease', N'Clarksburg', N'Sauvignon Blanc', 7.70, 2011),
('KL-01', N'Kettleman Lease', N'Lodi', N'Zinfandel', 10.80, 1998),
('KL-02', N'Kettleman Lease', N'Lodi', N'Zinfandel', 9.20, 2002),
('KL-03', N'Kettleman Lease', N'Lodi', N'Merlot', 8.50, 2005),
('KL-04', N'Kettleman Lease', N'Lodi', N'Petite Sirah', 6.90, NULL), -- leased; planting year unknown
('KL-05', N'Kettleman Lease', N'Lodi', N'Zinfandel', 7.40, 2009),
('KL-06', N'Kettleman Lease', N'Lodi', N'Merlot', 5.30, 2012);
GO
-- ---------------------------------------------------------------------------
-- FruitLots: every block delivers each vintage 2019-2024; about a third of
-- block-vintages get a second pick. ORDER BY keeps identity assignment stable.
-- ---------------------------------------------------------------------------
INSERT INTO FruitLots (BlockID, Vintage, PickDate, Tons, Brix, pH)
SELECT
b.BlockID,
v.value,
DATEADD(DAY,
ABS(CHECKSUM('pick', b.BlockCode, v.value, p.value)) % 45 + (p.value - 1) * 6,
DATEFROMPARTS(v.value, 8, 15)),
CAST(b.Acres * (2.6 + ABS(CHECKSUM('yield', b.BlockCode, v.value, p.value)) % 26 / 10.0)
/ p.value AS DECIMAL(6,2)),
-- Deliberate wrinkle: night machine picks arrive unsampled (NULL Brix/pH).
CASE WHEN ABS(CHECKSUM('night', b.BlockCode, v.value, p.value)) % 13 = 0 THEN NULL
ELSE CAST(21.8 + ABS(CHECKSUM('brix', b.BlockCode, v.value, p.value)) % 48 / 10.0 AS DECIMAL(4,1))
END,
CASE WHEN ABS(CHECKSUM('night', b.BlockCode, v.value, p.value)) % 13 = 0 THEN NULL
ELSE CAST(3.28 + ABS(CHECKSUM('ph', b.BlockCode, v.value, p.value)) % 58 / 100.0 AS DECIMAL(3,2))
END
FROM VineyardBlocks b
CROSS JOIN GENERATE_SERIES(2019, 2024) v
CROSS APPLY GENERATE_SERIES(1,
CASE WHEN ABS(CHECKSUM('twopick', b.BlockCode, v.value)) % 3 = 0 THEN 2 ELSE 1 END) p
ORDER BY b.BlockCode, v.value, p.value;
GO
-- ---------------------------------------------------------------------------
-- WineLots: every delivery ferments as its own lot, at 150-170 gallons per
-- ton (post 9's sanity band rides on that). Plus 15 purchased bulk lots with
-- no fruit lot behind them. The handful of deliveries that never became wine
-- are planted explicitly after this block.
-- ---------------------------------------------------------------------------
WITH RankedFruit AS (
SELECT f.FruitLotID, f.BlockID, f.Vintage, f.Tons,
ROW_NUMBER() OVER (PARTITION BY f.BlockID, f.Vintage ORDER BY f.PickDate) AS PickNo
FROM FruitLots f
)
INSERT INTO WineLots (LotCode, FruitLotID, Vintage, Variety, VolumeGal, Status)
SELECT
CONCAT(RIGHT(CAST(rf.Vintage AS VARCHAR(4)), 2), '-',
CASE b.Variety
WHEN N'Pinot Noir' THEN 'PN'
WHEN N'Chardonnay' THEN 'CH'
WHEN N'Cabernet Sauvignon' THEN 'CS'
WHEN N'Merlot' THEN 'ME'
WHEN N'Zinfandel' THEN 'ZN'
WHEN N'Petite Sirah' THEN 'PS'
WHEN N'Syrah' THEN 'SY'
ELSE 'SB'
END,
'-', REPLACE(b.BlockCode, '-', ''),
CASE WHEN rf.PickNo = 2 THEN 'B' ELSE '' END),
rf.FruitLotID,
rf.Vintage,
b.Variety,
CAST(rf.Tons * (150 + ABS(CHECKSUM('gpt', b.BlockCode, rf.Vintage, rf.PickNo)) % 21) AS DECIMAL(8,1)),
CASE
WHEN rf.Vintage >= 2023 THEN 'Aging'
WHEN ABS(CHECKSUM('status', b.BlockCode, rf.Vintage, rf.PickNo)) % 10 <= 5 THEN 'Blended'
WHEN ABS(CHECKSUM('status', b.BlockCode, rf.Vintage, rf.PickNo)) % 10 <= 8 THEN 'Sold'
ELSE 'Dumped'
END
FROM RankedFruit rf
JOIN VineyardBlocks b ON b.BlockID = rf.BlockID
ORDER BY rf.Vintage, b.BlockCode, rf.PickNo;
GO
-- Deliberate wrinkle: purchased bulk wine has no fruit lot. FruitLotID is NULL.
INSERT INTO WineLots (LotCode, FruitLotID, Vintage, Variety, VolumeGal, Status)
VALUES
('19-CH-BULK01', NULL, 2019, N'Chardonnay', 4200.0, 'Blended'),
('19-CS-BULK02', NULL, 2019, N'Cabernet Sauvignon', 6300.0, 'Blended'),
('20-PN-BULK01', NULL, 2020, N'Pinot Noir', 2800.0, 'Blended'),
('20-ZN-BULK02', NULL, 2020, N'Zinfandel', 5100.0, 'Sold'),
('21-CH-BULK01', NULL, 2021, N'Chardonnay', 3900.0, 'Blended'),
('21-CS-BULK02', NULL, 2021, N'Cabernet Sauvignon', 7200.0, 'Blended'),
('21-SY-BULK03', NULL, 2021, N'Syrah', 1900.0, 'Dumped'),
('22-PN-BULK01', NULL, 2022, N'Pinot Noir', 3400.0, 'Blended'),
('22-CH-BULK02', NULL, 2022, N'Chardonnay', 5600.0, 'Sold'),
('22-ME-BULK03', NULL, 2022, N'Merlot', 4100.0, 'Blended'),
('23-CH-BULK01', NULL, 2023, N'Chardonnay', 6800.0, 'Aging'),
('23-CS-BULK02', NULL, 2023, N'Cabernet Sauvignon', 5200.0, 'Aging'),
('23-PN-BULK03', NULL, 2023, N'Pinot Noir', 2600.0, 'Aging'),
('24-SB-BULK01', NULL, 2024, N'Sauvignon Blanc', 4400.0, 'Aging'),
('24-CH-BULK02', NULL, 2024, N'Chardonnay', 7100.0, 'Aging');
GO
-- Deliberate wrinkle: eight deliveries were sold as fruit and never became a
-- wine lot. Inserted after WineLots on purpose, so the corrected anti-join in
-- post 4 (NOT EXISTS) finds exactly these. The two 2023-09-21 rows also
-- guarantee a tied PickDate for post 7's RANGE-vs-ROWS frame demo.
INSERT INTO FruitLots (BlockID, Vintage, PickDate, Tons, Brix, pH)
SELECT b.BlockID, d.Vintage, d.PickDate, d.Tons, d.Brix, d.pH
FROM (VALUES
('KL-01', 2019, '2019-09-24', 3.40, 24.1, 3.61),
('OL-01', 2020, '2020-09-08', 4.10, 22.9, 3.42),
('KL-05', 2021, '2021-09-15', 2.80, 23.5, 3.55),
('DB-02', 2022, '2022-09-12', 3.10, 25.0, 3.70),
('KL-02', 2022, '2022-10-01', 2.20, 24.6, 3.66),
('SC-03', 2023, '2023-09-21', 3.70, 23.8, 3.58),
('KL-03', 2023, '2023-09-21', 4.60, 23.2, 3.51),
('OL-04', 2024, '2024-09-10', 3.00, 21.9, 3.31)
) d (BlockCode, Vintage, PickDate, Tons, Brix, pH)
JOIN VineyardBlocks b ON b.BlockCode = d.BlockCode;
GO
-- ---------------------------------------------------------------------------
-- Blends: ten programs per vintage. 2023+ not yet bottled.
-- ---------------------------------------------------------------------------
INSERT INTO Blends (BlendCode, BlendName, Vintage, TargetVolumeGal, BottlingDate)
SELECT
CONCAT('BL-', v.value, '-', RIGHT('0' + CAST(s.value AS VARCHAR(2)), 2)),
CASE s.value
WHEN 1 THEN N'Estate Pinot Noir'
WHEN 2 THEN N'Russian River Chardonnay'
WHEN 3 THEN N'Sonoma Coast Pinot Noir'
WHEN 4 THEN N'Alexander Valley Cabernet'
WHEN 5 THEN N'Old Vine Zinfandel'
WHEN 6 THEN N'Proprietor Red'
WHEN 7 THEN N'Dry Creek Petite Sirah'
WHEN 8 THEN N'Coastal Syrah'
WHEN 9 THEN N'Clarksburg Sauvignon Blanc'
ELSE N'Winemaker Reserve'
END,
v.value,
CAST(1800 + ABS(CHECKSUM('tgt', v.value, s.value)) % 9200 AS DECIMAL(8,1)),
CASE WHEN v.value >= 2023 THEN NULL
ELSE DATEADD(DAY, ABS(CHECKSUM('bottle', v.value, s.value)) % 120,
DATEFROMPARTS(v.value + 2, 2, 1))
END
FROM GENERATE_SERIES(2019, 2024) v
CROSS JOIN GENERATE_SERIES(1, 10) s
ORDER BY v.value, s.value;
GO
-- Deliberate wrinkle: a second 2023 blend targets exactly 1,200.0 gallons,
-- the same round number as the field blend planted further down, so
-- SUM(DISTINCT TargetVolumeGal) silently collapses the two (post 5). Pinned
-- before components are generated so component volumes stay consistent.
UPDATE Blends SET TargetVolumeGal = 1200.0 WHERE BlendCode = 'BL-2023-05';
GO
-- ---------------------------------------------------------------------------
-- BlendComponents: each blend draws four or five same-vintage lots (about
-- 4.7 on average, which is post 5's fan-out inflation factor).
-- ---------------------------------------------------------------------------
WITH Lots AS (
SELECT WineLotID, Vintage,
ROW_NUMBER() OVER (PARTITION BY Vintage ORDER BY WineLotID) AS LotSeq,
COUNT(*) OVER (PARTITION BY Vintage) AS LotsInVintage
FROM WineLots
),
B AS (
SELECT BlendID, BlendCode, Vintage, TargetVolumeGal,
ROW_NUMBER() OVER (PARTITION BY Vintage ORDER BY BlendID) AS BlendSeq,
CASE WHEN ABS(CHECKSUM('ncomp', BlendCode)) % 3 = 0 THEN 4 ELSE 5 END AS ComponentCount
FROM Blends
)
INSERT INTO BlendComponents (BlendID, WineLotID, ComponentPct, VolumeUsedGal)
SELECT b.BlendID, l.WineLotID,
CAST(100.0 / b.ComponentCount AS DECIMAL(5,2)),
CAST(b.TargetVolumeGal / b.ComponentCount AS DECIMAL(8,1))
FROM B b
CROSS APPLY GENERATE_SERIES(1, b.ComponentCount) s
JOIN Lots l
ON l.Vintage = b.Vintage
AND l.LotSeq = 1 + (b.BlendSeq * 4 + s.value - 1) % l.LotsInVintage;
GO
-- Deliberate wrinkle: two trial components were logged without a percentage.
UPDATE bc
SET ComponentPct = NULL
FROM BlendComponents bc
JOIN Blends b ON b.BlendID = bc.BlendID
WHERE b.BlendCode IN ('BL-2024-02', 'BL-2024-07')
AND bc.WineLotID = (SELECT MIN(x.WineLotID)
FROM BlendComponents x
WHERE x.BlendID = bc.BlendID);
GO
-- The other components of those two blends carry their true shares, which
-- total 92: the unassigned trial component is the missing 8 percent. So
-- SUM(ComponentPct) comes back around 92 and looks like a math error that
-- isn't (post 4).
UPDATE bc
SET ComponentPct = CAST(92.0 / n.NamedComponents AS DECIMAL(5,2))
FROM BlendComponents bc
JOIN Blends b ON b.BlendID = bc.BlendID
CROSS APPLY (SELECT COUNT(*) AS NamedComponents
FROM BlendComponents x
WHERE x.BlendID = bc.BlendID
AND x.ComponentPct IS NOT NULL) n
WHERE b.BlendCode IN ('BL-2024-02', 'BL-2024-07')
AND bc.ComponentPct IS NOT NULL;
GO
-- Plant: post 5's worked example. '23-GSM-01' targets 1,200.0 gallons and
-- draws exactly five lots from the library, so the Blends-to-components join
-- shows five copies of 1200.0 and a naive SUM reports 6,000 gallons of a
-- 1,200-gallon blend.
INSERT INTO Blends (BlendCode, BlendName, Vintage, TargetVolumeGal, BottlingDate)
VALUES ('23-GSM-01', N'GSM Field Blend', 2023, 1200.0, NULL);
INSERT INTO BlendComponents (BlendID, WineLotID, ComponentPct, VolumeUsedGal)
SELECT b.BlendID, w.WineLotID, 20.00, 240.0
FROM Blends b
CROSS JOIN (VALUES (118), (124), (131), (142), (156)) w (WineLotID)
WHERE b.BlendCode = '23-GSM-01';
GO
-- ---------------------------------------------------------------------------
-- Barrels: 900 barrels, seven coopers.
-- ---------------------------------------------------------------------------
INSERT INTO Barrels (BarrelCode, Cooper, Origin, ToastLevel, CapacityGal, FirstFillYear)
SELECT
CONCAT('BRL-', RIGHT('000' + CAST(n.value AS VARCHAR(4)), 4)),
CASE ABS(CHECKSUM('cooper', n.value)) % 7
WHEN 0 THEN N'Francois Freres' WHEN 1 THEN N'Taransaud'
WHEN 2 THEN N'Seguin Moreau' WHEN 3 THEN N'Demptos'
WHEN 4 THEN N'Canton' WHEN 5 THEN N'World Cooperage'
ELSE N'Kadar'
END,
CASE WHEN ABS(CHECKSUM('origin', n.value)) % 19 = 0 THEN NULL
WHEN ABS(CHECKSUM('cooper', n.value)) % 7 <= 3 THEN N'French'
WHEN ABS(CHECKSUM('cooper', n.value)) % 7 <= 5 THEN N'American'
ELSE N'Hungarian'
END,
CASE ABS(CHECKSUM('toast', n.value)) % 9
WHEN 0 THEN NULL
WHEN 1 THEN 'Light' WHEN 2 THEN 'Light'
WHEN 3 THEN 'Med' WHEN 4 THEN 'Med' WHEN 5 THEN 'Med'
WHEN 6 THEN 'Med+' WHEN 7 THEN 'Med+'
ELSE 'Heavy'
END,
CASE WHEN ABS(CHECKSUM('cap', n.value)) % 12 = 0 THEN 79.2 ELSE 59.4 END,
2016 + ABS(CHECKSUM('ffy', n.value)) % 9
FROM GENERATE_SERIES(1, 900) n;
GO
-- ---------------------------------------------------------------------------
-- BarrelFills, part 1: the historical program, vintages 2019-2022. Estate
-- lots only (purchased bulk stays in stainless; Sauvignon Blanc never leaves
-- the tank). The cellar samples each lot into up to seven barrels and the
-- remainder stays in tank. Every one of these fills is closed (EmptyDate
-- set), so post 9's open-fill tie-out never touches them.
-- ---------------------------------------------------------------------------
WITH LotsToBarrel AS (
SELECT wl.WineLotID, wl.Vintage,
ROW_NUMBER() OVER (ORDER BY wl.WineLotID) AS LotSeq,
CAST(LEAST(CEILING(wl.VolumeGal / 59.4), 7) AS INT) AS BarrelCount
FROM WineLots wl
WHERE wl.Vintage <= 2022
AND wl.Variety <> N'Sauvignon Blanc'
AND wl.FruitLotID IS NOT NULL
)
INSERT INTO BarrelFills (BarrelID, WineLotID, FillDate, EmptyDate, FillVolumeGal)
SELECT
1 + (l.LotSeq * 37 + s.value * 11) % 900,
l.WineLotID,
DATEADD(DAY, ABS(CHECKSUM('fill', l.WineLotID, s.value)) % 25,
DATEFROMPARTS(l.Vintage, 10, 20)),
DATEADD(MONTH, 9 + ABS(CHECKSUM('mo', l.WineLotID, s.value)) % 8,
DATEADD(DAY, ABS(CHECKSUM('fill', l.WineLotID, s.value)) % 25,
DATEFROMPARTS(l.Vintage, 10, 20))),
CAST(56.5 + ABS(CHECKSUM('fv', l.WineLotID, s.value)) % 30 / 10.0 AS DECIMAL(6,1))
FROM LotsToBarrel l
CROSS APPLY GENERATE_SERIES(1, l.BarrelCount) s;
GO
-- ---------------------------------------------------------------------------
-- BarrelFills, part 2: the 2023 reserve program. Eight lots are FULLY in
-- barrel: whole barrels at exactly 59.4 gallons each, the remainder in one
-- last barrel, so each lot's open fills sum exactly to WineLots.VolumeGal.
-- Post 9's volume tie-out depends on that. Each lot was barreled in November
-- 2023 and racked to fresh barrels in March 2024, so it carries both closed
-- fills (the November round) and open ones (the March round, EmptyDate NULL).
-- Every other 2023 and all 2024 lots are still in tank, with no fills at all.
-- ---------------------------------------------------------------------------
WITH ProgramLots AS (
SELECT wl.WineLotID, wl.VolumeGal,
ROW_NUMBER() OVER (ORDER BY wl.LotCode) AS LotSeq,
CAST(CEILING(wl.VolumeGal / 59.4) AS INT) AS BarrelCount
FROM WineLots wl
WHERE wl.LotCode IN ('23-PN-HR01', '23-PN-HR07', '23-PN-ET01', '23-PN-RB03',
'23-CH-HR05', '23-CH-RB01', '23-CS-SC01', '23-ZN-DB01')
),
Fills AS (
SELECT p.WineLotID, p.LotSeq, s.value AS BarrelSeq, r.value AS FillRound,
CASE WHEN s.value < p.BarrelCount THEN CAST(59.4 AS DECIMAL(6,1))
ELSE CAST(p.VolumeGal - 59.4 * (p.BarrelCount - 1) AS DECIMAL(6,1))
END AS FillVolumeGal
FROM ProgramLots p
CROSS APPLY GENERATE_SERIES(1, p.BarrelCount) s
CROSS JOIN GENERATE_SERIES(1, 2) r
)
INSERT INTO BarrelFills (BarrelID, WineLotID, FillDate, EmptyDate, FillVolumeGal)
SELECT
CASE WHEN f.FillRound = 1 THEN 1 + (f.LotSeq * 101 + f.BarrelSeq * 3) % 900
ELSE 1 + (f.LotSeq * 113 + f.BarrelSeq * 7) % 900
END,
f.WineLotID,
CASE WHEN f.FillRound = 1 THEN DATEFROMPARTS(2023, 11, 4 + f.LotSeq)
ELSE DATEFROMPARTS(2024, 3, 8 + f.LotSeq)
END,
CASE WHEN f.FillRound = 1 THEN DATEFROMPARTS(2024, 3, 8 + f.LotSeq)
ELSE NULL
END,
f.FillVolumeGal
FROM Fills f;
GO
-- Deliberate wrinkle: one fill event double-entered a day apart. Same barrel,
-- same lot, same fill date; different surrogate key. The primary key cannot
-- see it. The copied row is an open, exactly-59.4-gallon fill on an aging
-- reserve lot, so the grain check (post 6), the ROW_NUMBER dedupe (post 7),
-- and the volume tie-out (post 9) all surface the same 59.4 phantom gallons.
INSERT INTO BarrelFills (BarrelID, WineLotID, FillDate, EmptyDate, FillVolumeGal)
SELECT TOP (1) bf.BarrelID, bf.WineLotID, bf.FillDate, bf.EmptyDate, bf.FillVolumeGal
FROM BarrelFills bf
JOIN WineLots wl ON wl.WineLotID = bf.WineLotID
WHERE wl.LotCode = '23-PN-ET01'
AND bf.EmptyDate IS NULL
AND bf.FillVolumeGal = 59.4
ORDER BY bf.BarrelID;
GO
-- ---------------------------------------------------------------------------
-- WorkOrders: 10-27 operations per lot, trimmed to the present.
-- ---------------------------------------------------------------------------
WITH L AS (
SELECT WineLotID, LotCode, Vintage,
10 + ABS(CHECKSUM('nwo', LotCode)) % 18 AS OpCount
FROM WineLots
)
INSERT INTO WorkOrders (WineLotID, WorkType, ScheduledDate, CompletedDate, CellarHand, LaborHours)
SELECT
l.WineLotID,
CASE ABS(CHECKSUM('wt', l.LotCode, s.value)) % 10
WHEN 0 THEN 'Racking' WHEN 1 THEN 'Racking'
WHEN 2 THEN 'SO2Add' WHEN 3 THEN 'SO2Add'
WHEN 4 THEN 'Topping' WHEN 5 THEN 'Topping' WHEN 6 THEN 'Topping'
WHEN 7 THEN 'PumpOver'
WHEN 8 THEN 'Filtration'
ELSE 'Transfer'
END,
d.ScheduledDate,
CASE WHEN ABS(CHECKSUM('open', l.LotCode, s.value)) % 23 = 0 THEN NULL
ELSE DATEADD(DAY, ABS(CHECKSUM('lag', l.LotCode, s.value)) % 3, d.ScheduledDate)
END,
-- Deliberate wrinkle: some work orders were logged with no cellar hand.
CASE ABS(CHECKSUM('hand', l.LotCode, s.value)) % 8
WHEN 0 THEN N'Ramos' WHEN 1 THEN N'Ramos'
WHEN 2 THEN N'Delgado' WHEN 3 THEN N'Okafor'
WHEN 4 THEN N'Fitch' WHEN 5 THEN N'Nguyen'
WHEN 6 THEN N'Barnes'
ELSE NULL
END,
CASE WHEN ABS(CHECKSUM('hrs', l.LotCode, s.value)) % 19 = 0 THEN NULL
ELSE CAST(0.5 + ABS(CHECKSUM('hrs', l.LotCode, s.value)) % 70 / 10.0 AS DECIMAL(4,1))
END
FROM L l
CROSS APPLY GENERATE_SERIES(1, l.OpCount) s
CROSS APPLY (SELECT DATEADD(DAY,
(s.value - 1) * 17 + ABS(CHECKSUM('sched', l.LotCode, s.value)) % 11,
DATEFROMPARTS(l.Vintage, 9, 20)) AS ScheduledDate) d
WHERE d.ScheduledDate <= '2024-09-30';
GO
-- ---------------------------------------------------------------------------
-- Customers: 25 bulk-wine buyers.
-- ---------------------------------------------------------------------------
INSERT INTO Customers (CustomerName, State, CustomerType)
VALUES
(N'Meridian Cellars', 'CA', 'Winery'),
(N'Halcyon Wine Co.', 'CA', 'Winery'),
(N'Black Oak Vintners', 'CA', 'Winery'),
(N'Sierra Foothill Wineworks', 'CA', 'Winery'),
(N'Cascadia Cellars', 'OR', 'Winery'),
(N'Columbia Crest Partners', 'WA', 'Winery'),
(N'Bluestem Winery', 'TX', 'Winery'),
(N'Seneca Lake Cellars', 'NY', 'Winery'),
(N'Pacific Slope Wines', 'CA', 'Winery'),
(N'Dunlap Family Vintners', 'CA', 'Winery'),
(N'Castellan & Sons', 'CA', 'Negociant'),
(N'Vintner''s Exchange', 'CA', 'Negociant'),
(N'Goldfield Wine Trading', 'CA', 'Negociant'),
(N'Areton Brands', 'CA', 'Negociant'),
(N'Crush Coast Selections', 'CA', 'Negociant'),
(N'Hartwell & Mayer', 'NY', 'Negociant'),
(N'Verraison Group', NULL, 'Negociant'),
(N'Old Bridge Wine Co.', 'OR', 'Negociant'),
(N'Loomis Bulk Services', 'CA', 'Broker'),
(N'Central Valley Wine Brokerage', 'CA', 'Broker'),
(N'Pacific Bulk Partners', 'CA', 'Broker'),
(N'Quail Run Brokerage', 'CA', 'Broker'),
(N'Westgate Wine Brokers', NULL, 'Broker'),
(N'Foss Creek Trading', 'CA', 'Broker'),
(N'Amador Wine Exchange', 'CA', 'Broker');
GO
-- ---------------------------------------------------------------------------
-- BulkContracts: 45 contracts, 2019-2024, selling the prior vintage.
-- ---------------------------------------------------------------------------
INSERT INTO BulkContracts (ContractNumber, CustomerID, ContractDate, Variety, Vintage,
VolumeGal, PricePerGal, Status)
SELECT
CONCAT('BC-', 2019 + (n.value - 1) % 6, '-',
RIGHT('00' + CAST((n.value - 1) / 6 + 1 AS VARCHAR(2)), 3)),
1 + ABS(CHECKSUM('cust', n.value)) % 23, -- the last two customers never buy
-- (post 2's LEFT JOIN demo needs them)
DATEADD(DAY, ABS(CHECKSUM('cdate', n.value)) % 235,
DATEFROMPARTS(2019 + (n.value - 1) % 6, 1, 10)),
CASE ABS(CHECKSUM('cvar', n.value)) % 6
WHEN 0 THEN N'Chardonnay' WHEN 1 THEN N'Cabernet Sauvignon'
WHEN 2 THEN N'Pinot Noir' WHEN 3 THEN N'Zinfandel'
WHEN 4 THEN N'Merlot' ELSE N'Sauvignon Blanc'
END,
2019 + (n.value - 1) % 6 - 1,
CAST(2000 + ABS(CHECKSUM('cvol', n.value)) % 26 * 1000 AS DECIMAL(8,1)),
CAST(4.25 + ABS(CHECKSUM('cprice', n.value)) % 90 / 10.0 AS DECIMAL(8,2)),
CASE WHEN 2019 + (n.value - 1) % 6 = 2024 THEN 'Open'
WHEN ABS(CHECKSUM('cstat', n.value)) % 11 = 0 THEN 'Cancelled'
ELSE 'Fulfilled'
END
FROM GENERATE_SERIES(1, 45) n
ORDER BY n.value;
GO
-- Deliberate wrinkle: BC-2024-007 is the contract the series keeps returning
-- to. Pin its terms exactly.
UPDATE BulkContracts
SET CustomerID = (SELECT CustomerID FROM Customers WHERE CustomerName = N'Meridian Cellars'),
ContractDate = '2024-01-18',
Variety = N'Chardonnay',
Vintage = 2023,
VolumeGal = 12000.0,
PricePerGal = 8.50,
Status = 'Open'
WHERE ContractNumber = 'BC-2024-007';
GO
-- ---------------------------------------------------------------------------
-- ContractAmendments: roughly 40% of contracts get amended; a few twice.
-- ---------------------------------------------------------------------------
INSERT INTO ContractAmendments (ContractID, AmendmentDate, NewVolumeGal, NewPricePerGal, Reason)
SELECT
c.ContractID,
ad.AmendmentDate,
CASE WHEN ABS(CHECKSUM('which', c.ContractNumber, a.value)) % 2 = 0
THEN CAST(c.VolumeGal * (0.7 + ABS(CHECKSUM('vchg', c.ContractNumber, a.value)) % 7 / 10.0) AS DECIMAL(8,1))
ELSE NULL
END,
CASE WHEN ABS(CHECKSUM('which', c.ContractNumber, a.value)) % 2 = 1
THEN CAST(c.PricePerGal * (0.85 + ABS(CHECKSUM('pchg', c.ContractNumber, a.value)) % 4 / 10.0) AS DECIMAL(8,2))
ELSE NULL
END,
CASE ABS(CHECKSUM('why', c.ContractNumber, a.value)) % 4
WHEN 0 THEN N'Volume revised at buyer request'
WHEN 1 THEN N'Price renegotiated on market movement'
WHEN 2 THEN N'Schedule and volume rebalanced'
ELSE NULL
END
FROM BulkContracts c
CROSS APPLY GENERATE_SERIES(1,
CASE WHEN ABS(CHECKSUM('namd', c.ContractNumber)) % 5 = 0 THEN 2 ELSE 1 END) a
CROSS APPLY (SELECT DATEADD(DAY,
45 + ABS(CHECKSUM('amd', c.ContractNumber, a.value)) % 200,
c.ContractDate) AS AmendmentDate) ad
WHERE c.ContractNumber <> 'BC-2024-007'
AND ABS(CHECKSUM('namd', c.ContractNumber)) % 5 <= 1
AND ad.AmendmentDate <= '2024-09-30';
GO
-- Deliberate wrinkle: BC-2024-007 amended twice. Price reduced in March,
-- volume increased in June. The contract row above still holds the original
-- terms; current terms exist only by reading the amendments.
INSERT INTO ContractAmendments (ContractID, AmendmentDate, NewVolumeGal, NewPricePerGal, Reason)
SELECT ContractID, '2024-03-14', NULL, 7.90, N'Price adjusted to bulk market'
FROM BulkContracts
WHERE ContractNumber = 'BC-2024-007';
INSERT INTO ContractAmendments (ContractID, AmendmentDate, NewVolumeGal, NewPricePerGal, Reason)
SELECT ContractID, '2024-06-20', 15000.0, NULL, N'Volume increased at buyer request'
FROM BulkContracts
WHERE ContractNumber = 'BC-2024-007';
GO
-- ---------------------------------------------------------------------------
-- Shipments: 1-4 shipments per non-cancelled contract, trimmed to the present.
-- ---------------------------------------------------------------------------
INSERT INTO Shipments (ContractID, ShipDate, VolumeGal, CarrierRef)
SELECT
c.ContractID,
sd.ShipDate,
CAST(c.VolumeGal / (1 + ABS(CHECKSUM('nship', c.ContractNumber)) % 4) AS DECIMAL(8,1)),
CASE WHEN ABS(CHECKSUM('pro', c.ContractNumber, s.value)) % 7 = 0 THEN NULL
ELSE CONCAT('PRO-', RIGHT('00000' +
CAST(ABS(CHECKSUM('pro', c.ContractNumber, s.value)) % 100000 AS VARCHAR(6)), 6))
END
FROM BulkContracts c
CROSS APPLY GENERATE_SERIES(1, 1 + ABS(CHECKSUM('nship', c.ContractNumber)) % 4) s
CROSS APPLY (SELECT DATEADD(DAY,
s.value * 40 + ABS(CHECKSUM('sd', c.ContractNumber, s.value)) % 25,
c.ContractDate) AS ShipDate) sd
WHERE c.Status <> 'Cancelled'
AND c.ContractNumber <> 'BC-2024-007'
AND sd.ShipDate <= '2024-09-30';
GO
-- Deliberate wrinkle: BC-2024-007 ships against its amended terms. Three
-- shipments of 5,000 gallons each, 15,000 in total: that ties to the amended
-- volume, not the original 12,000 (posts 5, 6, and 8 all lean on this).
INSERT INTO Shipments (ContractID, ShipDate, VolumeGal, CarrierRef)
SELECT c.ContractID, d.ShipDate, d.VolumeGal, d.CarrierRef
FROM BulkContracts c
CROSS APPLY (VALUES
(CAST('2024-04-02' AS DATE), CAST(5000.0 AS DECIMAL(8,1)), 'PRO-118204'),
(CAST('2024-07-11' AS DATE), CAST(5000.0 AS DECIMAL(8,1)), 'PRO-121873'),
(CAST('2024-09-05' AS DATE), CAST(5000.0 AS DECIMAL(8,1)), 'PRO-124551')
) d (ShipDate, VolumeGal, CarrierRef)
WHERE c.ContractNumber = 'BC-2024-007';
GO
-- ---------------------------------------------------------------------------
-- Indexes the later posts lean on.
-- ---------------------------------------------------------------------------
CREATE INDEX IX_FruitLots_BlockID ON FruitLots (BlockID);
CREATE INDEX IX_WineLots_FruitLotID ON WineLots (FruitLotID);
CREATE INDEX IX_BlendComponents_WineLot ON BlendComponents (WineLotID);
CREATE INDEX IX_BarrelFills_WineLotID ON BarrelFills (WineLotID);
CREATE INDEX IX_BarrelFills_BarrelID ON BarrelFills (BarrelID);
CREATE INDEX IX_WorkOrders_WineLotID ON WorkOrders (WineLotID);
-- No index on WorkOrders.ScheduledDate on purpose: the performance post has
-- you create IX_WorkOrders_ScheduledDate yourself and watch the plan change.
CREATE INDEX IX_Shipments_ContractID ON Shipments (ContractID);
GO
PRINT 'CellarDB setup complete.';
GOCheck your install
Run this after the script finishes:
SELECT t.name AS TableName, SUM(p.rows) AS RowCnt
FROM sys.tables t
JOIN sys.partitions p
ON p.object_id = t.object_id AND p.index_id IN (0, 1)
GROUP BY t.name
ORDER BY t.name;Twelve tables, with BarrelFills and WorkOrders carrying most of the rows. If that's what you see, you have everything the series needs. Start with SQL in an Afternoon.
— Lo