-
Notifications
You must be signed in to change notification settings - Fork 128
Adding support for running checks on multiple tables #566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 72 commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
1086154
initial version
mwojtyczka b8f97b9
fmt
mwojtyczka 949f803
Merge branch 'main' into feature/multi-table
mwojtyczka e186ad4
increase allowed local vars due to tests
mwojtyczka c6c4f32
Merge remote-tracking branch 'origin/feature/multi-table' into featur…
mwojtyczka 229beb6
Merge branch 'main' into feature/multi-table
mwojtyczka 0d4cdd6
Merge branch 'main' into feature/multi-table
mwojtyczka a401ab3
Merge branch 'main' into feature/multi-table
mwojtyczka ef1aee8
Merge branch 'main' into feature/multi-table
mwojtyczka 4e4df41
fix import issues
mwojtyczka 4d5e317
added telemetry
mwojtyczka 09953ac
added integration tests
mwojtyczka 899f106
updated demo and docs
mwojtyczka 1ac1e71
updated docs
mwojtyczka c918534
updated demo and docs
mwojtyczka e391bdf
refactor
mwojtyczka 8f30e04
refactor
mwojtyczka 6c10874
refactor
mwojtyczka 53514f3
refactor
mwojtyczka 023fdd4
refactor
mwojtyczka b52003c
increase random names to 10 to minimize clashes
mwojtyczka 857cc75
updated docs
mwojtyczka 1c2ff2a
Support running workflows for multiple all run configs.
mwojtyczka 5416895
Support running workflows for multiple all run configs.
mwojtyczka 04eace2
fixed tests
mwojtyczka ebc36eb
refactor
mwojtyczka f172cd6
updated docs
mwojtyczka 6b36ca3
updated docs
mwojtyczka 7f67d53
updated docs
mwojtyczka 31ca824
updated docs
mwojtyczka 2edab9a
fix tests
mwojtyczka 3293b75
fix tests
mwojtyczka b05549e
refactor
mwojtyczka 26a4008
updated tests
mwojtyczka 95f551c
fixed tests
mwojtyczka bc57613
fixed tests, updated docs
mwojtyczka 241f576
updated demos
mwojtyczka 2b509e2
updated docs
mwojtyczka f32b939
updated demo
mwojtyczka 9176d63
refactor
mwojtyczka f784236
optimize list tables logic
mwojtyczka c9309c4
refactor
mwojtyczka ed07bfe
refactor
mwojtyczka c8d6c67
fixed tests
mwojtyczka 2be6e05
Merge branch 'main' into feature/multi-table
mwojtyczka a8a3aa9
refactor
mwojtyczka 36ff06b
fmt
mwojtyczka 5eb4c13
test
mwojtyczka ba36518
test
mwojtyczka 9061e13
added exclusion pattern
mwojtyczka c44159a
added exclusion pattern to workflows
mwojtyczka 19855e4
refactor
mwojtyczka d44a689
fixed tests
mwojtyczka 6088721
updated docs
mwojtyczka 4dcd015
updated docs
mwojtyczka 64723a6
updated docstrings
mwojtyczka c323c61
updated docstrings
mwojtyczka 694c64a
refactor
mwojtyczka 0ad7e08
docs update
mwojtyczka 66f6c24
docs update
mwojtyczka 18870d7
code review feedback implementation
mwojtyczka 74d6c0d
fixed tests
mwojtyczka 86638e6
Merge branch 'main' into feature/multi-table
mwojtyczka 36a36a9
fixed tests
mwojtyczka 139f132
added integration test
mwojtyczka b743c89
make tests deterministic
mwojtyczka f3a8a75
Merge branch 'main' into feature/multi-table
mwojtyczka 8e1a6b8
added instruction for installing dqx cli in windows
mwojtyczka da5e385
Add pytest-benchmark performance baseline
mwojtyczka 8fd27ec
added comments
mwojtyczka 24f1ee3
added comments
mwojtyczka 6a5265d
updated demo
mwojtyczka b217ac9
refactor, updated docs
mwojtyczka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| # Databricks notebook source | ||
| # MAGIC %md | ||
| # MAGIC # DQX Multi-Table Data Quality Checks Demo | ||
| # MAGIC | ||
| # MAGIC This notebook demonstrates how to profile and apply data quality checks to multiple tables in a single method call. | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC ## Installing DQX | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| dbutils.widgets.text("test_library_ref", "", "Test Library Ref") | ||
|
|
||
| if dbutils.widgets.get("test_library_ref") != "": | ||
| %pip install '{dbutils.widgets.get("test_library_ref")}' | ||
| else: | ||
| %pip install databricks-labs-dqx | ||
|
|
||
| %restart_python | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC ## Setup and Configuration | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| import yaml | ||
| from databricks.labs.dqx.config import InputConfig, OutputConfig, RunConfig | ||
| from databricks.labs.dqx.config import TableChecksStorageConfig | ||
| from databricks.labs.dqx.engine import DQEngine | ||
| from databricks.sdk import WorkspaceClient | ||
|
|
||
| # Default configuration values | ||
| default_catalog = "main" | ||
| default_schema = "default" | ||
|
|
||
| # Create widgets for configuration | ||
| dbutils.widgets.text("demo_catalog_name", default_catalog, "Catalog Name") | ||
| dbutils.widgets.text("demo_schema_name", default_schema, "Schema Name") | ||
|
|
||
| # Get configuration values | ||
| demo_catalog_name = dbutils.widgets.get("demo_catalog_name") | ||
| demo_schema_name = dbutils.widgets.get("demo_schema_name") | ||
|
|
||
| print(f"Using catalog: {demo_catalog_name}") | ||
| print(f"Using schema: {demo_schema_name}") | ||
|
|
||
| # Initialize the DQX engine | ||
| ws = WorkspaceClient() | ||
| dq_engine = DQEngine(ws, spark) | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC ## Checking multiple tables by providing specific configuration (run configs) | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # Create a sample users table | ||
| users_data = [ | ||
| [1, "john@email.com", "John Doe", "2023-01-01"], | ||
| [2, "invalid-email", "Jane Smith", "2023-02-01"], | ||
| [3, "bob@email.com", "Bob Wilson", "2023-03-01"], | ||
| [None, "alice@email.com", "Alice Brown", "2023-04-01"], | ||
| ] | ||
|
|
||
| users_df = spark.createDataFrame( | ||
| users_data, | ||
| schema="user_id int, email string, name string, created_on string" | ||
| ) | ||
| users_table = f"{demo_catalog_name}.{demo_schema_name}.users" | ||
| users_df.write.mode("overwrite").saveAsTable(users_table) | ||
|
|
||
| # Create a sample orders table | ||
| orders_data = [ | ||
| [1, 1, 100.50, "2023-01-15"], | ||
| [2, 2, -10.00, "2023-02-15"], | ||
| [3, 3, 75.25, "2023-03-15"], | ||
| [None, 4, 50.00, "2023-04-15"] | ||
| ] | ||
|
|
||
| orders_df = spark.createDataFrame( | ||
| orders_data, | ||
| schema="order_id int, user_id int, total_amount double, order_on string" | ||
| ) | ||
| orders_table = f"{demo_catalog_name}.{demo_schema_name}.users_orders" | ||
| orders_df.write.mode("overwrite").saveAsTable(orders_table) | ||
|
|
||
| # Define checks | ||
| user_checks = yaml.safe_load(""" | ||
| - criticality: error | ||
| check: | ||
| function: is_not_null | ||
| arguments: | ||
| column: user_id | ||
| - criticality: warn | ||
| check: | ||
| function: regex_match | ||
| arguments: | ||
| column: email | ||
| regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$ | ||
| """) | ||
|
|
||
| order_checks = yaml.safe_load(""" | ||
| - criticality: error | ||
| check: | ||
| function: is_not_null | ||
| arguments: | ||
| column: order_id | ||
| - criticality: warn | ||
| check: | ||
| function: is_not_less_than | ||
| arguments: | ||
| column: total_amount | ||
| limit: 0 | ||
| """) | ||
|
|
||
| # Save checks in a table | ||
| checks_table = f"{demo_catalog_name}.{demo_schema_name}.checks" | ||
| dq_engine.save_checks(user_checks, config=TableChecksStorageConfig(location=checks_table, run_config_name=users_table, mode="overwrite")) | ||
| dq_engine.save_checks(order_checks, config=TableChecksStorageConfig(location=checks_table, run_config_name=orders_table, mode="overwrite")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.checks")) | ||
|
|
||
| # Define run configs | ||
| run_configs = [ | ||
| RunConfig( | ||
| name=users_table, | ||
| input_config=InputConfig(location=users_table), | ||
| output_config=OutputConfig( | ||
| location=f"{demo_catalog_name}.{demo_schema_name}.users_checked", | ||
| mode="overwrite" | ||
| ), | ||
| # quarantine bad data | ||
| quarantine_config=OutputConfig( | ||
| location=f"{demo_catalog_name}.{demo_schema_name}.users_quarantine", | ||
| mode="overwrite" | ||
| ), | ||
| checks_location=checks_table | ||
| ), | ||
| RunConfig( | ||
| name=orders_table, | ||
| input_config=InputConfig(location=orders_table), | ||
| # don't quarantine bad data | ||
| output_config=OutputConfig( | ||
| location=f"{demo_catalog_name}.{demo_schema_name}.users_orders_checked", | ||
| mode="overwrite" | ||
| ), | ||
| checks_location=checks_table | ||
| ) | ||
| ] | ||
|
|
||
| # Apply checks to multiple tables and save the results | ||
| dq_engine.apply_checks_and_save_in_tables(run_configs=run_configs) | ||
|
|
||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_checked")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_quarantine")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_orders_checked")) | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # Clean up tables | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_checked") | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_quarantine") | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_orders_checked") | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC ## Checking multiple tables using wildcard patterns | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # Apply checks to multiple tables using patterns, but skip existing output and quarantine tables based on the suffixes | ||
| dq_engine.apply_checks_and_save_in_tables_for_patterns( | ||
| patterns=[f"{demo_catalog_name}.{demo_schema_name}.users*"], # apply quality checks for all tables matching the patterns | ||
| exclude_patterns=["*_checked", "*_quarantine"], # skip existing output tables | ||
| checks_location=checks_table, # as delta table or absolute workspace or volume directory. For file based locations, checks are expected to be found under {checks_location}/{table_name}.yml. | ||
| run_config_template=RunConfig( | ||
| # input config is auto-created if not provided; location is skipped in any case and derived from patterns | ||
| input_config=InputConfig(""), | ||
| # input config is auto-created if not provided; location is skipped in any case and derived from patterns + output_table_suffix | ||
| output_config=OutputConfig(location="", mode="overwrite"), | ||
| # (optional) quarantine bad data; location is skipped in any case and derived from patterns + quarantine_table_suffix | ||
| quarantine_config=OutputConfig(location="", mode="overwrite"), | ||
| # skip checks_location of the run config as it is derived separately | ||
| ), | ||
| output_table_suffix="_checked", # default _dq_output | ||
| quarantine_table_suffix="_quarantine" # default _dq_quarantine | ||
| ) | ||
|
|
||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_checked")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_quarantine")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_orders_checked")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_orders_quarantine")) | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # clean up tables | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_checked") | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_quarantine") | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_orders_checked") | ||
| spark.sql(f"drop table {demo_catalog_name}.{demo_schema_name}.users_orders_quarantine") | ||
| spark.sql(f"drop table {checks_table}") | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC ## End-to-End approach: generate and apply checks based on wildcard patterns | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC Profile input tables, generate and save checks. | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| from databricks.labs.dqx.profiler.profiler import DQProfiler | ||
| from databricks.labs.dqx.profiler.generator import DQGenerator | ||
|
|
||
| profiler = DQProfiler(ws, spark) | ||
| generator = DQGenerator(ws) | ||
|
|
||
| # Include tables matching the patterns, but skip existing output and quarantine tables based on the suffixes | ||
| patterns = [f"{demo_catalog_name}.{demo_schema_name}.users*"] | ||
| exclude_patterns=["*_checked", "*_quarantine"] # skip existing output tables based on suffixes | ||
|
|
||
| results = profiler.profile_tables_for_patterns( | ||
| patterns=patterns, | ||
| exclude_patterns=exclude_patterns, | ||
| ) | ||
|
|
||
| for table, (summary_stats, profiles) in results.items(): | ||
| checks = generator.generate_dq_rules(profiles) | ||
| print(f"Generated checks: {checks}") | ||
| # run config name must be equal to the input table name | ||
| dq_engine.save_checks(checks, config=TableChecksStorageConfig(location=checks_table, run_config_name=table, mode="overwrite")) | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| display(spark.table(checks_table)) | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
| # MAGIC %md | ||
| # MAGIC Apply the generated checks | ||
|
|
||
| # COMMAND ---------- | ||
|
|
||
|
|
||
| # Apply checks on multiple tables using patterns | ||
| dq_engine.apply_checks_and_save_in_tables_for_patterns( | ||
| patterns=patterns, | ||
| exclude_patterns=exclude_patterns, # skip existing output tables | ||
| checks_location=checks_table, | ||
| output_table_suffix="_checked", | ||
| # run_config_template with quarantine_config not provided - don't quarantine bad data | ||
| ) | ||
|
|
||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_checked")) | ||
| display(spark.table(f"{demo_catalog_name}.{demo_schema_name}.users_orders_checked")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.