Subscription company, billing migration

A made-up subscription company moves customers, invoices and payments to a new billing database. Every difference the move made is found by SQL, with its cause.

112 differences in 3 tables: 7 rows lost at batch boundaries, 40 amounts truncated, 25 times shifted by the time zone, 10 spaces turned into NULL, 30 codes missing from the map

Bridge: source to target, by table and month

customers

MonthStepRowsControl total
All rowsSource1,200
All rowsTarget1,200

invoices

MonthStepRowsControl total
July 2026Source1,200129,762.6459
July 2026Amount truncated0−0.0759
July 2026Target1,200129,762.5700
August 2026Source1,200127,065.6409
August 2026Amount truncated0−0.0509
August 2026Target1,200127,065.5900
September 2026Source1,200126,119.3898
September 2026Amount truncated0−0.0798
September 2026Target1,200126,119.3100

payments

MonthStepRowsControl total
July 2026Source998108,665.4100
July 2026Lost at a batch boundary−1−49.0000
July 2026Target997108,616.4100
August 2026Source1,135118,177.5700
August 2026Lost at a batch boundary−4−266.0000
August 2026Target1,131117,911.5700
September 2026Source1,267136,030.9000
September 2026Lost at a batch boundary−2−148.0000
September 2026Moved to another month−2−148.0000
September 2026Target1,263135,734.9000
October 2026 (outside the period)Moved in from another month+2+148.0000
October 2026 (outside the period)Target2148.0000

Task graph of the DAG

Task graph of the DAG

Differences

TableCauseKeyColumnSourceTargetDifferenceExplanation
customersspaces became NULLC10355address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC10369address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC10443address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC10501address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC10552address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC10656address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC10923address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC11078address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC11118address_line2' 'NULLA value of spaces became NULL.
customersspaces became NULLC11190address_line2' 'NULLA value of spaces became NULL.
invoicescode missing from the mapINV-2026-00045statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00303statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00320statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00357statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00391statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00543statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00606statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-00814statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01064statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01085statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01412statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01500statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01567statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01613statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01688statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01717statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01883statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-01906statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02103statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02275statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02468statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02564statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02709statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02730statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-02836statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-03022statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-03040statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-03049statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-03169statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicescode missing from the mapINV-2026-03579statuson_holdNULLCode missing from the migration map; the target has NULL.
invoicesamount truncatedINV-2026-00012amount845.0914845.09−0.0014Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00304amount850.3652850.36−0.0052Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00360amount292.0176292.01−0.0076Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00407amount929.2268929.22−0.0068Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00428amount726.4054726.40−0.0054Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00522amount967.0581967.05−0.0081Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00672amount39.114739.11−0.0047Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00838amount451.0984451.09−0.0084Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00843amount265.9825265.98−0.0025Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00873amount148.1135148.11−0.0035Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-00911amount822.1361822.13−0.0061Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01011amount307.8965307.89−0.0065Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01060amount870.9806870.98−0.0006Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01165amount223.1591223.15−0.0091Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01554amount308.1369308.13−0.0069Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01656amount656.5999656.59−0.0099Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01756amount160.1429160.14−0.0029Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01860amount981.6116981.61−0.0016Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01939amount247.0291247.02−0.0091Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01947amount366.8938366.89−0.0038Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-01969amount243.1163243.11−0.0063Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02058amount161.2214161.22−0.0014Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02197amount858.3344858.33−0.0044Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02394amount922.5546922.55−0.0046Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02413amount393.1891393.18−0.0091Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02602amount212.5571212.55−0.0071Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02649amount981.9316981.93−0.0016Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02673amount96.321296.32−0.0012Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02695amount421.2070421.20−0.0070Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02844amount744.8051744.80−0.0051Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02846amount50.498550.49−0.0085Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02881amount286.3431286.34−0.0031Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02927amount425.1551425.15−0.0051Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-02951amount107.7133107.71−0.0033Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-03033amount874.1507874.15−0.0007Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-03353amount33.610933.61−0.0009Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-03405amount14.686614.68−0.0066Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-03460amount215.1664215.16−0.0064Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-03470amount90.356890.35−0.0068Amount cut to the target's decimals.
invoicesamount truncatedINV-2026-03597amount235.6973235.69−0.0073Amount cut to the target's decimals.
paymentslost at a batch boundaryP100501−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentslost at a batch boundaryP101002−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentslost at a batch boundaryP101503−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentslost at a batch boundaryP101504−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentslost at a batch boundaryP102005−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentslost at a batch boundaryP102506−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentslost at a batch boundaryP103007−1.0000Skipped at a batch boundary: the next batch started after this row's time.
paymentstime zone shiftP100210paid_at2026-07-13 10:20:292026-07-13 13:20:29+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100216paid_at2026-07-13 13:45:322026-07-13 16:45:32+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100393paid_at2026-07-17 18:58:462026-07-17 21:58:46+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100710paid_at2026-07-24 10:13:492026-07-24 13:13:49+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100828paid_at2026-07-27 17:30:412026-07-27 20:30:41+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100891paid_at2026-07-28 12:08:452026-07-28 15:08:45+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100902paid_at2026-07-29 23:41:472026-07-30 02:41:47+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100915paid_at2026-07-29 23:15:262026-07-30 02:15:26+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP100927paid_at2026-07-29 21:44:342026-07-30 00:44:34+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101023paid_at2026-08-01 22:50:522026-08-02 01:50:52+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101132paid_at2026-08-05 21:35:092026-08-06 00:35:09+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101148paid_at2026-08-06 17:38:212026-08-06 20:38:21+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101181paid_at2026-08-07 07:23:482026-08-07 10:23:48+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101311paid_at2026-08-11 10:11:112026-08-11 13:11:11+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101444paid_at2026-08-15 18:03:302026-08-15 21:03:30+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101795paid_at2026-08-24 00:01:222026-08-24 03:01:22+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP101956paid_at2026-08-27 14:19:462026-08-27 17:19:46+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP102109paid_at2026-08-31 11:25:102026-08-31 14:25:10+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP102193paid_at2026-09-02 00:03:442026-09-02 03:03:44+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP102590paid_at2026-09-15 23:11:412026-09-16 02:11:41+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP103198paid_at2026-09-28 10:05:432026-09-28 13:05:43+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP103225paid_at2026-09-29 17:17:412026-09-29 20:17:41+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP103314paid_at2026-09-30 23:41:042026-10-01 02:41:04+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP103326paid_at2026-09-30 07:38:212026-09-30 10:38:21+3.0000Time moved by whole hours: local time loaded as UTC.
paymentstime zone shiftP103340paid_at2026-09-30 22:11:532026-10-01 01:11:53+3.0000Time moved by whole hours: local time loaded as UTC.

How to run

Both demos from scratch: ./run.sh. The load check as an Airflow Dag, with its environment set up as the README says: airflow dags test load_check.

Made by Andrey Karazhev as a side project about data that moves between systems and quietly stops matching. Contact: hello@minihub.app