<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Why DBSQL is Best for BI Workloads - Part 5: Query Optimization with Primary Key Constraints in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/why-dbsql-is-best-for-bi-workloads-part-5-query-optimization/ba-p/78967</link>
    <description>&lt;P&gt;&lt;SPAN&gt;&lt;STRONG data-stringify-type="bold"&gt;Authors&lt;/STRONG&gt;: Andrey Mirskiy (&lt;A class="c-link" href="https://community.databricks.com/t5/user/viewprofilepage/user-id/40437" target="_blank" rel="noopener" data-stringify-link="https://community.databricks.com/t5/user/viewprofilepage/user-id/14326" data-sk="tooltip_parent"&gt;@AndreyMirskiy&lt;/A&gt;) and Marco Scagliola (&lt;A class="c-link" href="https://community.databricks.com/t5/user/viewprofilepage/user-id/63093" target="_blank" rel="noopener" data-stringify-link="https://community.databricks.com/t5/user/viewprofilepage/user-id/82808" data-sk="tooltip_parent"&gt;@MarcoScagliola&lt;/A&gt;)&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;LI-TOC indent="30" liststyle="disc" maxheadinglevel="2"&gt;&lt;/LI-TOC&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;&lt;SPAN&gt;Introduction&lt;/SPAN&gt;&lt;/H1&gt;
&lt;P&gt;Welcome to the fifth part of our blog series on 'Why Databricks SQL Serverless is the best fit for BI workloads'.&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;In the previous blog posts we have covered the following topics:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;A href="https://community.databricks.com/t5/technical-blog/why-databricks-sql-serverless-is-the-best-for-bi-workloads-part/ba-p/54925" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Part #1 - Disk Cache&lt;/SPAN&gt;&lt;/A&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;A href="https://community.databricks.com/t5/technical-blog/why-databricks-sql-serverless-is-the-best-for-bi-workloads-part/ba-p/58074" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Part #2 - Apache jMeter for Databricks SQL performance testing&lt;/SPAN&gt;&lt;/A&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;A href="https://community.databricks.com/t5/technical-blog/why-serverless-databricks-sql-is-the-best-for-bi-workloads-part/ba-p/60018" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Part #3 - Query Result Cache&lt;/SPAN&gt;&lt;/A&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;A href="https://community.databricks.com/t5/technical-blog/why-serverless-databricks-sql-is-the-best-for-bi-workloads-part/ba-p/65507" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Part #4 - Remote Query Result Cache&lt;/SPAN&gt;&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;In this blog post, we will discuss query optimization capability within Databricks SQL which leverages primary key constraints to improve query performance by eliminating unnecessary operations, such as DISTINCT aggregations and unnecessary joins.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This blog explores how to implement and benefit from this optimisation technique, providing practical examples demonstrating how this capability improves query efficiency in Databricks SQL.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;&lt;SPAN&gt;Query optimization using primary key constraints - Overview&lt;/SPAN&gt;&lt;/H1&gt;
&lt;P&gt;&lt;SPAN&gt;Databricks SQL Warehouse allows users to specify informational PK and FK constraints. With the introduction of this new query optimization technique, users are now able to specify Primary Key (PK) constraints with the &lt;/SPAN&gt;&lt;STRONG&gt;RELY&lt;/STRONG&gt;&lt;SPAN&gt; option, allowing the Databricks query optimizer to utilize these constraints to optimize query execution plans and eliminate unnecessary operations. This technique can improve query performance, particularly in BI workloads..&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The specification of primary keys with the &lt;/SPAN&gt;&lt;STRONG&gt;RELY&lt;/STRONG&gt;&lt;SPAN&gt; option in table alteration statements is shown below in Code 1.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 1: Creating primary key (PK) constraint with RELY option.&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;USE CATALOG catalo_name;
ALTER TABLE schema_name.table_name ADD PRIMARY KEY (column_name) RELY;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Currently Databricks SQL leverages this information to optimize query execution in the following scenarios:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;A DISTINCT operation on a primary key column can be skipped. Because of the RELY option the engine knows that all values in that column are unique, therefore no need to perform additional operation.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;When using a star-schema like model a fact table can be joined with multiple dimension tables. When a query uses LEFT JOIN on the primary key column with the RELY option the engine may exclude that join operation from actual execution because this operation neither increases nor decreases the resultset.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;SPAN&gt;These optimizations lead to more efficient query execution, making Databricks SQL faster and more effective.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;&lt;SPAN&gt;Demo Scenario&lt;/SPAN&gt;&lt;/H1&gt;
&lt;P&gt;&lt;SPAN&gt;In this demo scenario we are creating two synthetic environments, in order to create an A/B test on tables without any Primary Key (PK) defined and the same tables with Primary Key (PK)&amp;nbsp; defined with the &lt;/SPAN&gt;&lt;STRONG&gt;RELY&lt;/STRONG&gt;&lt;SPAN&gt; option.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Preparation&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;First, we created test tables by replicating some of the tables from the &lt;/SPAN&gt;&lt;STRONG&gt;tpch&lt;/STRONG&gt;&lt;SPAN&gt; schema contained within &lt;/SPAN&gt;&lt;STRONG&gt;samples&lt;/STRONG&gt;&lt;SPAN&gt; catalog.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 2: Create a Catalog and Schema in order to run the test.&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;CREATE CATALOG IF NOT EXISTS join_optimization;
CREATE SCHEMA IF NOT EXISTS join_optimization.tpch;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 3: Create the test tables as CTAS&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;CREATE OR REPLACE TABLE lineitem AS SELECT * FROM samples.tpch.lineitem;
CREATE OR REPLACE TABLE orders AS SELECT * FROM samples.tpch.orders;
CREATE OR REPLACE TABLE part AS SELECT * FROM samples.tpch.part;
CREATE OR REPLACE TABLE supplier AS SELECT * FROM samples.tpch.supplier;
CREATE OR REPLACE TABLE customer AS SELECT * FROM samples.tpch.customer;
CREATE OR REPLACE TABLE nation AS SELECT * FROM samples.tpch.nation;
CREATE OR REPLACE TABLE region AS SELECT * FROM samples.tpch.region;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Executing Sample Query without PKs&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Before executing sample queries we may need to drop any primary keys which may exist after previous tests. We used Code 4 for that purpose.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 4: Drop any primary keys within the test tables&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;ALTER TABLE orders DROP PRIMARY KEY IF EXISTS;
ALTER TABLE part DROP PRIMARY KEY IF EXISTS;
ALTER TABLE supplier DROP PRIMARY KEY IF EXISTS;
ALTER TABLE customer DROP PRIMARY KEY IF EXISTS;
ALTER TABLE nation DROP PRIMARY KEY IF EXISTS;
ALTER TABLE region DROP PRIMARY KEY IF EXISTS;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;On Code 5, you can see the sample query which we used for testing.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 5: Test query&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;set use_cached_result=false;
select sum(l_quantity), min(l_tax)
from lineitem
  left join orders on l_orderkey=o_orderkey
  left join part on l_partkey=p_partkey
  left join supplier on l_suppkey=s_suppkey
  left join customer on o_custkey=c_custkey
  left join nation on c_nationkey=n_nationkey
  left join region on n_regionkey=r_regionkey;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the very first time (cold execution) we observed the following query execution metrics - see Image 1 below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 5 s 723 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 54.84 s&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 358.80 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_0-1721135999369.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9599iF74ED272CD9F3B03/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_0-1721135999369.png" alt="AndreyMirskiy_0-1721135999369.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 1: Cold execution without PK constraints&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;SPAN&gt;When executed immediately for the second time (warm execution) we observed the following query execution metrics - see Image 2 below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 4 s 749 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 18.96 s&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 358.80 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_2-1721136151105.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9600iE6545390DF43E674/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_2-1721136151105.png" alt="AndreyMirskiy_2-1721136151105.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 2: Warm execution without PK constraints&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Next, we tested the same query using a view that encapsulates the logic of joining tables. In code 6, you can see the DDL we created to encapsulate the test logic&amp;nbsp; within a view.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;Code 6: Test query wrapped within a view&lt;BR /&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;create or replace view v_join_optimization as
select *
from lineitem
  left join orders   on l_orderkey=o_orderkey
  left join part     on l_partkey=p_partkey
  left join supplier on l_suppkey=s_suppkey
  left join customer on o_custkey=c_custkey
  left join nation   on c_nationkey=n_nationkey
  left join region   on n_regionkey=r_regionkey;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;After restarting the SQL Warehouse to reset the Disk Cache, we executed the test query using a view twice. See Code 7 below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 7: Test query wrapped within a view&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;set use_cached_result=false;
select sum(l_quantity), min(l_tax) from v_join_optimization;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the first time (cold execution) we observed the following query execution metrics - see Image 3 below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 7 s 850 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 55.61 s&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 358.80 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_3-1721136205275.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9601iECDFB518F030EE39/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_3-1721136205275.png" alt="AndreyMirskiy_3-1721136205275.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 3: Cold execution without PK constraints using a view&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the second time (warm execution) we observed the following query execution metrics - see Image 4 below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 2 s 987 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 18.54 s&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 358.80 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_4-1721136258275.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9602i083AFCE88F436C34/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_4-1721136258275.png" alt="AndreyMirskiy_4-1721136258275.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 4: Warm execution without PK constraints using a view&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Creating PK Constraints&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Next, in order to test the benefits of query optimizations we created primary keys with the &lt;/SPAN&gt;&lt;STRONG&gt;RELY&lt;/STRONG&gt;&lt;SPAN&gt; option - see Code 7 below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;SPAN&gt;&lt;SPAN&gt;Code 7: Alter the tables to define the primary key using the RELY option&lt;BR /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;ALTER TABLE orders ALTER COLUMN o_orderkey SET NOT NULL;
ALTER TABLE orders ADD PRIMARY KEY (o_orderkey) RELY;
ALTER TABLE part ALTER COLUMN p_partkey SET NOT NULL;
ALTER TABLE part ADD PRIMARY KEY (p_partkey) RELY;
ALTER TABLE supplier ALTER COLUMN s_suppkey SET NOT NULL;
ALTER TABLE supplier ADD PRIMARY KEY (s_suppkey) RELY;
ALTER TABLE customer ALTER COLUMN c_custkey SET NOT NULL;
ALTER TABLE customer ADD PRIMARY KEY (c_custkey) RELY;
ALTER TABLE nation ALTER COLUMN n_nationkey SET NOT NULL;
ALTER TABLE nation ADD PRIMARY KEY (n_nationkey) RELY;
ALTER TABLE region ALTER COLUMN r_regionkey SET NOT NULL;
ALTER TABLE region ADD PRIMARY KEY (r_regionkey) RELY;&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Executing Sample Query with PK Constraints&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;After restarting SQL Warehouse in order to reset Disk Cache, we executed sample queries again.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the first time (cold execution) we observed the following query execution metrics - see Image 5.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 5 s 309 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 16.03 s&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 35.90 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_0-1721136353262.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9603i4F2ACCA7AB84F7DD/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_0-1721136353262.png" alt="AndreyMirskiy_0-1721136353262.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 5: Cold query execution with PK RELY constraints&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the second time (warm execution) we observed the following query execution metrics - see Image 6.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 1 s 357 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 338 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 35.90 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_1-1721136392831.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9604i757F6A62EEDA58A8/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_1-1721136392831.png" alt="AndreyMirskiy_1-1721136392831.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 6: Warm query execution with PK RELY constraints&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Based on the query metrics we can clearly see that query performance is much better and it consumed less CPU (Tasks total time) and scanned less data (Bytes read).&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Finally, after restarting SQL Warehouse again in order to reset Disk Cache, we executed sample queries using views.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the first time (cold execution) we could see the following query execution metrics - see Image 7.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 5 s 821 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 16.99 s&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 35.90 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_2-1721136428281.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9605i61D92FC5BE562C8E/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_2-1721136428281.png" alt="AndreyMirskiy_2-1721136428281.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 7: Cold execution with PK RELY constraints using a view&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;When executed for the first time (cold execution) we could see the following query execution metrics - see Image 8.&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Total wall-clock duration = 1 s 823 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Tasks total time = 336 ms&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Bytes read = 35.90 MB&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_3-1721136467567.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9606iEDFF08DA02BD20B6/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_3-1721136467567.png" alt="AndreyMirskiy_3-1721136467567.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 8: Warm execution with PK RELY constraints using a view&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Summary Results&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;We have summarized test results in the table below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Table 1: Statistics on the test run with and without Primary Key RELY enabled&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE class=" lia-align-center"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Scenario&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Scenario&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Disk Cache&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Total wall clock&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Tasks total time&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Bytes Read&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Files read&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Plain query&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;No PKs&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Cold&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;5 s 723 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;54.84 s&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;358.8 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;19&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Plain query&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;No PKs&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Warm&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;4 s 749 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;18.96 s&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;358.8 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;19&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;View&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;No PKs&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Cold&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;7 s 850 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;55.61 s&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;358.8 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;19&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;View&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;No PKs&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Warm&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;2 s 987 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;18.54 s&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;358.8 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;19&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Plain query&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;PK RELY&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Cold&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;5 s 309 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;16.03 s&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;35.9 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;11&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Plain query&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;PK RELY&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Warm&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;1 s 357 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;338 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;35.9 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;11&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;View&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;PK RELY&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Cold&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;5 s 821 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;16.99 s&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;35.9 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;11&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;View&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;PK RELY&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Warm&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;1 s 823 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;336 ms&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;35.9 MB&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;11&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;You can also find total wall clock duration statistics on the chart below; lower is better.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AndreyMirskiy_4-1721136502581.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9607i452F9293D8E4BB34/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_4-1721136502581.png" alt="AndreyMirskiy_4-1721136502581.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;We can clearly see that by leveraging primary key RELY constraints Databricks SQL builds a more efficient execution plan, scans less data from storage, and spends less CPU cycles. Which leads to better overall query performance.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The reason for this performance difference becomes obvious when we compare the query profiles. The image below demonstrates the two query profiles - without PK and with PK RELY. We can see that when the query engine leverages information about the primary key the query profile is much simpler. The query optimizer excluded unnecessary joins, hence the query profile is more efficient.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_7-1721136692998.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9610iE6575DE4EBF37801/image-size/large?v=v2&amp;amp;px=999" role="button" title="AndreyMirskiy_7-1721136692998.png" alt="AndreyMirskiy_7-1721136692998.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 9: Query profiles without PK and with PK RELY&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;JMeter test plan&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;To see the impact under high workload we implemented a JMeter test plan simulating 10 concurrent users each executing a sample query 100 times.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_10-1721136794176.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9612iFC5FD360B88C1308/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_10-1721136794176.png" alt="AndreyMirskiy_10-1721136794176.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 10: JMeter test results - sample query when no PKs&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AndreyMirskiy_8-1721136750075.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/9611i85EEDAAE14924B01/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AndreyMirskiy_8-1721136750075.png" alt="AndreyMirskiy_8-1721136750075.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="lia-align-center"&gt;&lt;SPAN&gt;Image 11: JMeter test results - sample query when using PK RELY&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The results are self-explanatory. The query performance when using PK RELY is significantly better. Not only was the average query performance more than 5 times better, but also the standard deviation was much lower. Meaning that end users will experience more consistent performance. Which is very important in high concurrency BI workloads.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;&lt;SPAN&gt;Conclusion&lt;/SPAN&gt;&lt;/H1&gt;
&lt;P&gt;&lt;SPAN&gt;In conclusion, leveraging primary key constraints with the RELY option in Databricks SQL can significantly enhance query performance for BI workloads. By specifying these constraints, the Databricks query optimizer can eliminate unnecessary operations such as DISTINCT aggregations and redundant joins, leading to more efficient query execution plans. Our tests demonstrated that queries with primary key constraints executed faster, consumed less CPU, and scanned less data compared to those without constraints. This optimization is particularly beneficial in high concurrency BI workloads, providing consistent and improved performance for end users. Implementing these techniques can help organizations maximize the efficiency and effectiveness of their BI workloads on Databricks SQL.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;For more information on Query optimization using primary key constraints, refer to the documentation (&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/databricks/sql/user/queries/query-optimization-constraints" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Azure&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; | &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/en/sql/user/queries/query-optimization-constraints.html" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;AWS&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; | &lt;/SPAN&gt;&lt;A href="https://docs.gcp.databricks.com/en/sql/user/queries/query-optimization-constraints.html" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;GCP&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;).&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;&lt;SPAN&gt;Resources&lt;/SPAN&gt;&lt;/H1&gt;
&lt;P&gt;&lt;SPAN&gt;The code artifacts featured in this blog are available in the following &lt;A href="https://github.com/AMirskiy/databricks-sql-perf-demos/tree/main/5.%20Query%20optimization%20using%20PK/" target="_blank" rel="noopener"&gt;GitHub repository&lt;/A&gt;.&lt;/SPAN&gt;&lt;/P&gt;</description>
    <pubDate>Tue, 16 Jul 2024 14:18:12 GMT</pubDate>
    <dc:creator>AndreyMirskiy</dc:creator>
    <dc:date>2024-07-16T14:18:12Z</dc:date>
    <item>
      <title>Why DBSQL is Best for BI Workloads - Part 5: Query Optimization with Primary Key Constraints</title>
      <link>https://community.databricks.com/t5/technical-blog/why-dbsql-is-best-for-bi-workloads-part-5-query-optimization/ba-p/78967</link>
      <description>&lt;P&gt;??&lt;/P&gt;</description>
      <pubDate>Tue, 16 Jul 2024 14:18:12 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/why-dbsql-is-best-for-bi-workloads-part-5-query-optimization/ba-p/78967</guid>
      <dc:creator>AndreyMirskiy</dc:creator>
      <dc:date>2024-07-16T14:18:12Z</dc:date>
    </item>
  </channel>
</rss>

