Skip to content

Commit 6e9cc76

Browse files
Revise README for clarity and detail on SQLCompare functionality and usage
1 parent 2754cb0 commit 6e9cc76

1 file changed

Lines changed: 174 additions & 84 deletions

File tree

README.md

Lines changed: 174 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,229 @@
11
# SQLCompare
22

3-
Professional CLI for comparing datasets across database tables.
3+
SQLCompare helps you understand how a change impacted your data.
4+
When you modify logic, filters, or inputs, SQLCompare lets you compare the previous and current versions of a dataset—whether they come from tables, SQL queries, or files.
45

5-
SQLCompare performs row-level comparisons between a "previous" and "current" dataset using a composite key, materializes a join table in a comparison schema, computes column-level differences, and stores a diff run ID for repeatable analysis.
6+
You can compare datasets in two complementary ways:
67

7-
## Key Features
8-
- Compare tables or dataset queries using composite keys.
9-
- Persist comparison runs with metadata for later analysis.
10-
- Generate per-column stats, missing-row checks, and samples of differences.
11-
- Designed for repeatable CLI workflows in data engineering and QA.
8+
1) Row-by-row comparison (with an ID): detect missing rows on either side, identify the columns with the most changes, and inspect before/after values for any record.
9+
2) Statistical comparison: compare column-level statistics such as null counts, distinct counts, and other aggregates to quickly understand overall impact.
1210

13-
## Installation
14-
Use your project setup and dependency manager of choice. For local development:
11+
---
12+
13+
## What you get
14+
15+
- **Repeatable checks** for releases, backfills, migrations, and vendor drops
16+
- **Clear summaries**: missing-row detection + per-column change counts
17+
- **One workflow** for warehouses *and* local files (DuckDB-powered)
18+
19+
---
20+
21+
## Install
22+
23+
Recommended:
1524

1625
```bash
17-
uv sync --extra dev
26+
uv tool install sqlcompare
1827
```
1928

20-
## Quick Start
21-
Compare two tables on a single key:
29+
Install optional connector extras as needed:
2230

2331
```bash
24-
sqlcompare table analytics.fact_sales analytics.fact_sales_new id
32+
uv tool install "sqlcompare[<connector>]"
2533
```
2634

27-
Compare using a composite key:
35+
Examples:
2836

2937
```bash
30-
sqlcompare table analytics.users analytics.users_new user_id,tenant_id
38+
uv tool install "sqlcompare[snowflake]"
39+
uv tool install "sqlcompare[databricks]"
3140
```
3241

33-
Use a named connector and custom schema:
42+
---
43+
44+
## Quick start (tables)
45+
46+
Compare two tables on a key:
3447

3548
```bash
36-
sqlcompare table public.orders public.orders_latest order_id -c prod --schema sqlcompare
49+
export SQLCOMPARE_CONN_DEFAULT="postgresql://<user>:<pass>@<host>/<db>"
50+
sqlcompare table analytics.fact_sales analytics.fact_sales_new id
3751
```
3852

39-
Follow up with analysis:
53+
That command prints a **diff_id**. Use it for follow-up analysis:
4054

4155
```bash
42-
sqlcompare report <diff_id> --stats
43-
sqlcompare report <diff_id> --column revenue --limit 100
44-
sqlcompare report <diff_id> --missing-current
56+
sqlcompare analyze-diff <diff_id> --stats
57+
sqlcompare analyze-diff <diff_id> --column revenue --limit 100
58+
sqlcompare analyze-diff <diff_id> --missing-current
4559
```
4660

47-
## Command Reference
61+
---
62+
63+
## Core idea: compare once, analyze many times
64+
65+
SQLCompare does two things:
66+
67+
1. **Compare** two datasets using an index (single or composite key)
68+
2. Persist comparison results and return a **diff_id** you can use to:
69+
70+
* get overall stats
71+
* drill into a specific column’s changes
72+
* list missing rows (previous-only / current-only)
73+
* pull samples for debugging
4874

49-
### Compare tables
75+
---
76+
77+
## Usage by use case
78+
79+
### 1) Compare two tables
80+
81+
Best for production validation and regression checks across supported connectors.
5082

5183
```bash
52-
sqlcompare table TABLE1 TABLE2 IDS [--connection/-c CONNECTION] [--schema SCHEMA]
84+
sqlcompare table analytics.users analytics.users_new user_id,tenant_id
5385
```
5486

55-
#### Arguments
56-
- `TABLE1`: Fully qualified name of the "previous" table to compare. Passed directly to SQL (example: `analytics.schema.table`).
57-
- `TABLE2`: Fully qualified name of the "current" table to compare. Passed directly to SQL.
58-
- `IDS`: Comma-separated list of columns that uniquely identify a row (example: `id` or `id,sub_id`). Whitespace around commas is stripped.
87+
Why it’s useful:
5988

60-
#### Options
61-
- `--connection`, `-c`: Name of a connector profile to use. If omitted, SQLCompare reads the default connector from configuration.
62-
- `--schema`: Schema used to store the join table created for the comparison. If omitted, it uses the configured comparison schema.
89+
* Handles **composite keys**
90+
* Produces a **saved diff ID** for repeatable review
6391

64-
## Configuration
65-
SQLCompare is configured through environment variables:
92+
---
93+
94+
### 2) Compare SQL query results
95+
96+
Use this when tables aren’t materialized yet or you want a filtered slice.
97+
98+
Create a dataset config:
99+
100+
```yaml
101+
previous:
102+
select_sql: "SELECT * FROM analytics.orders WHERE order_date < '2024-01-01'"
103+
index:
104+
- ORDER_ID
105+
106+
new:
107+
select_sql: "SELECT * FROM analytics.orders WHERE order_date >= '2024-01-01'"
108+
index:
109+
- ORDER_ID
110+
```
111+
112+
Run the compare:
113+
114+
```bash
115+
sqlcompare dataset path/to/dataset.yaml
116+
```
117+
118+
Why it’s useful:
119+
120+
* Compare slices without touching production tables
121+
* Keep an auditable **diff_id**
66122

67-
- `SQLCOMPARE_COMPARISON_SCHEMA`:
68-
Schema used for storing comparison tables.
69-
- `SQLCOMPARE_CONN_DEFAULT`:
70-
Name of the default connector profile.
71-
- `SQLCOMPARE_CONN_{NAME}`:
72-
SQLAlchemy URL for a named connector profile. Use with `-c {NAME}`.
123+
---
73124

74-
## How It Works
125+
### 3) Compare local CSV / XLSX files (DuckDB)
75126

76-
1) Resolve configuration, connector, and comparison schema.
77-
2) Parse `IDS` into a list of index columns.
78-
3) Create a unique comparison name and diff ID.
79-
4) Use `DatabaseComparator` to:
80-
- Verify index columns exist in both tables (case-insensitive match).
81-
- Create a `FULL OUTER JOIN` table in the comparison schema.
82-
- Count rows present only in either table.
83-
- Compute per-column differences for shared columns.
84-
- Persist metadata for later analysis.
85-
5) Emit a summary and a follow-up report command.
127+
Great for ad hoc QA, one-off deliveries, or vendor drops.
128+
SQLCompare uses DuckDB under the hood — no DB server required.
86129

87-
## Diff IDs and Metadata
88-
Each run is recorded in:
130+
Create a dataset config (supports `{{here}}` for relative paths):
89131

132+
```yaml
133+
previous:
134+
file_name: "{{here}}/previous.csv"
135+
index:
136+
- id
137+
138+
new:
139+
file_name: "{{here}}/current.xlsx"
140+
index:
141+
- id
90142
```
91-
~/.config/sqlcompare/db_test_runs.yaml
143+
144+
Set a local default connector and run:
145+
146+
```bash
147+
export SQLCOMPARE_CONN_DEFAULT="duckdb:///:memory:"
148+
sqlcompare dataset path/to/dataset.yaml
92149
```
93150

94-
The record includes:
95-
- `tables`: mapping of previous, current, and join table names
96-
- `index_cols`: resolved index columns (as found in the database)
97-
- `cols_prev` / `cols_new`: column lists for each table
98-
- `conn`: connector name (or `duckdb` for file-based runs)
151+
Why it’s useful:
152+
153+
* Same diff workflow as warehouses
154+
* Fast local comparisons, zero infra
155+
156+
---
157+
158+
### 4) Compare tables inside a DuckDB file
99159

100-
Diff ID format:
160+
Use this when your data lives in a local `.duckdb` file.
101161

162+
```bash
163+
export SQLCOMPARE_CONN_LOCAL="duckdb:////absolute/path/to/warehouse.duckdb"
164+
sqlcompare table raw.customers staged.customers id -c local
102165
```
103-
compare_{table1}_{table2}_{timestamp}_{random}
166+
167+
Why it’s useful:
168+
169+
* Local + fast, without Snowflake/warehouse costs
170+
* Easy to integrate into lightweight pipelines
171+
172+
---
173+
174+
## Configuration
175+
176+
SQLCompare resolves connectors in this order:
177+
178+
1. default connector
179+
2. direct URL
180+
3. environment variables
181+
4. YAML files
182+
183+
### Environment variables
184+
185+
* `SQLCOMPARE_CONN_DEFAULT` — default connector URL (used when `-c` is omitted)
186+
* `SQLCOMPARE_CONN_{NAME}` — SQLAlchemy URL for a named connector
187+
* `SQLCOMPARE_COMPARISON_SCHEMA` — schema for comparison tables (default: `sqlcompare`)
188+
189+
Example:
190+
191+
```bash
192+
export SQLCOMPARE_CONN_DEFAULT="postgresql://..."
193+
export SQLCOMPARE_CONN_LOCAL="duckdb:////abs/path/to/db.duckdb"
104194
```
105195

106-
Table names are sanitized to alphanumeric plus underscores.
196+
### YAML connections file (optional)
107197

108-
## Output Summary
109-
The CLI prints a concise report to stdout (via `data_toolkit.core.log`), including:
110-
- Missing rows in either table (based on null key columns in the join table).
111-
- Total number of value differences across common rows.
112-
- Per-column difference counts (when common columns exist).
113-
- A sample of the first 10 differences.
114-
- A follow-up command to inspect the diff (`sqlcompare report <diff_id>`).
198+
Location:
115199

116-
## Difference Detection
200+
```
201+
~/.sqlcompare/connections.yml
202+
```
117203

118-
`DatabaseComparator` builds a join table with the following shape:
119-
- For every column in `TABLE1`: `column_previous`
120-
- For every column in `TABLE2`: `column_new`
204+
Example:
205+
206+
```yaml
207+
snowflake:
208+
drivername: snowflake
209+
username: my_user
210+
password: my_password
211+
host: my_account
212+
database: ANALYTICS
213+
schema: PUBLIC
214+
query:
215+
warehouse: COMPUTE_WH
216+
```
121217
122-
It uses a `FULL OUTER JOIN` on the provided `IDS`.
218+
---
123219
124-
Differences are detected when:
125-
- The row exists on both sides (key columns are not null in both).
126-
- A common column does not match (or one side is `NULL` and the other is not).
220+
## Operational notes
127221
128-
Analysis queries are generated on-demand:
129-
- `get_diff_query`: per-row differences across all common columns
130-
- `get_stats_query`: counts of differences per column
131-
- `get_in_current_only_query` / `get_in_previous_only_query`: missing-row checks
222+
* SQLCompare creates a **physical join table** in the comparison schema. Ensure your connector has `CREATE SCHEMA` and `CREATE TABLE` privileges.
223+
* Table names are passed through directly to SQL. Provide fully-qualified or quoted names when required by your database.
224+
* Index columns must exist in both datasets; otherwise the command fails with a clear error.
132225

133-
## Operational Notes
134-
- The command creates a physical join table in the comparison schema. Ensure the connector has `CREATE SCHEMA` and `CREATE TABLE` privileges.
135-
- Table names are passed through directly to SQL. Provide fully-qualified or quoted names when required by your database.
136-
- Index columns must exist in both tables; otherwise the command fails with a clear error that includes a sample of available columns.
226+
---
137227

138228
## Development
139229

0 commit comments

Comments
 (0)