<?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>topic The 32nd Column: Why Your Z-Order or Clustering Key May Be Doing Nothing in Community Articles</title>
    <link>https://community.databricks.com/t5/community-articles/the-32nd-column-why-your-z-order-or-clustering-key-may-be-doing/m-p/170251#M1618</link>
    <description>&lt;P&gt;You cluster a table on a column, run the query, and nothing improves. The layout command completed successfully. The column is definitely in the filter. The file count in the query profile has not moved.&lt;/P&gt;&lt;P&gt;Before assuming the clustering did not work, check whether statistics exist for that column at all. In a wide table they may not, and nothing in the process will tell you.&lt;/P&gt;&lt;P&gt;How skipping actually works&lt;/P&gt;&lt;P&gt;Delta records statistics for each data file: the minimum value, the maximum value, and the null count per column, plus the record count. At query time the engine compares your predicate against those numbers and discards files that cannot possibly contain a match. A file is never opened, so the cost of that file drops to zero.&lt;/P&gt;&lt;P&gt;Clustering exists to make those ranges narrow. If a file spans the entire range of a column, the engine cannot rule it out. If the file covers a small slice, most queries can discard it immediately.&lt;/P&gt;&lt;P&gt;The whole mechanism rests on one assumption: that statistics exist for the column you are filtering on.&lt;/P&gt;&lt;P&gt;Where that assumption breaks&lt;/P&gt;&lt;P&gt;For Unity Catalog external tables, statistics are collected on the first 32 columns defined in your schema. Not the 32 most useful columns. Not the columns you filter on. The first 32, in declaration order.&lt;/P&gt;&lt;P&gt;Column 33 onward has no statistics. A filter on one of those columns cannot skip a single file. The query returns correct results, runs at full scan cost, and reports no error.&lt;/P&gt;&lt;P&gt;This is easy to walk into. Wide tables grow by accretion. Someone adds a field, then another, then a nested struct whose fields also count toward the limit. Two years later a new filter column sits at position 41 and the table quietly stops being skippable on it.&lt;/P&gt;&lt;P&gt;The documentation is explicit on the consequence for Z-order: Databricks recommends not using ZORDER BY on columns that do not have statistics collected, because it is ineffective and consumes compute for nothing. Liquid clustering reads the same per-file statistics, so the same reasoning applies to a clustering key that sits past the boundary.&lt;/P&gt;&lt;P&gt;One important exception&lt;/P&gt;&lt;P&gt;For Unity Catalog managed tables, predictive optimization does not use a fixed 32-column list. It runs ANALYZE and collects skipping statistics on the columns that actually appear most often in your query filters, so the limit does not apply.&lt;/P&gt;&lt;P&gt;This matters because it means two tables in the same workspace can behave completely differently, and the difference is invisible in the query text. Before debugging, establish which regime your table is in. If it is a managed table with predictive optimization enabled, the column position is not your problem and you should look elsewhere.&lt;/P&gt;&lt;P&gt;How to check&lt;/P&gt;&lt;P&gt;Start with what the table is configured to collect:&lt;/P&gt;&lt;P&gt;SHOW TBLPROPERTIES my_catalog.my_schema.my_table;&lt;/P&gt;&lt;P&gt;Look for delta.dataSkippingStatsColumns and delta.dataSkippingNumIndexedCols. If neither is set and the table is external, you are on the 32-column default.&lt;/P&gt;&lt;P&gt;To see what was actually written rather than what is configured, read the statistics out of the transaction log. Get the path first, then read the add actions:&lt;/P&gt;&lt;P&gt;DESCRIBE DETAIL my_catalog.my_schema.my_table;&lt;/P&gt;&lt;P&gt;Take the location value, then:&lt;/P&gt;&lt;P&gt;SELECT add.stats FROM json.&amp;lt;location&amp;gt;/_delta_log/*.json WHERE add IS NOT NULL LIMIT 5;&lt;/P&gt;&lt;P&gt;The stats field is a JSON document with minValues, maxValues and nullCount objects. The column you care about either appears in there or it does not, and that single check settles the question.&lt;/P&gt;&lt;P&gt;The most direct behavioural evidence is in the query profile. Compare files pruned against files read for a filter on the suspect column, then run the same shape of query filtered on a column you know is early in the schema. If one prunes and the other does not, you have your answer.&lt;/P&gt;&lt;P&gt;Three ways to fix it&lt;/P&gt;&lt;P&gt;Name the columns explicitly. On DBR 13.3 LTS and above this is the cleanest option, because it decouples statistics from schema position entirely:&lt;/P&gt;&lt;P&gt;ALTER TABLE my_table SET TBLPROPERTIES ('delta.dataSkippingStatsColumns' = 'region, order_date, promo_code');&lt;/P&gt;&lt;P&gt;This property supersedes the numeric limit. Be deliberate about the list, since statistics are not free to compute or store.&lt;/P&gt;&lt;P&gt;Raise the count. Works on all runtimes, but remains order dependent, so it is a blunter instrument:&lt;/P&gt;&lt;P&gt;ALTER TABLE my_table SET TBLPROPERTIES ('delta.dataSkippingNumIndexedCols' = '48');&lt;/P&gt;&lt;P&gt;Reorder the schema. Move the columns you filter on to the front. Correct, but it usually means rewriting the table, so it is rarely the pragmatic choice on an existing large table.&lt;/P&gt;&lt;P&gt;The step almost everyone misses&lt;/P&gt;&lt;P&gt;Changing either property does not recompute anything. It changes the behaviour of future writes only. Your existing files keep whatever statistics they were written with, and until they are rewritten your queries will not improve. People make the change, see no difference, and conclude the setting does not work.&lt;/P&gt;&lt;P&gt;On DBR 14.3 LTS and above you can force the recomputation:&lt;/P&gt;&lt;P&gt;ANALYZE TABLE my_table COMPUTE DELTA STATISTICS;&lt;/P&gt;&lt;P&gt;Two related traps&lt;/P&gt;&lt;P&gt;Long string columns are truncated during statistics collection. A column holding URLs, JSON blobs or free text will have min and max values that are effectively useless for skipping, while still costing you to collect. These are good candidates to exclude from the statistics column list rather than to include.&lt;/P&gt;&lt;P&gt;Nested fields count toward the limit individually. For statistics purposes each scalar field inside a struct is treated as its own column, so a struct with fifteen fields consumes fifteen of your slots. A table with twelve top-level columns can already be past the boundary if several of them are nested. Map and array columns cannot have statistics collected at all.&lt;/P&gt;&lt;P&gt;The takeaway&lt;/P&gt;&lt;P&gt;Clustering and statistics are two halves of the same mechanism, and only one of them is visible in the command you run. Layout decides whether a file can be ruled out. Statistics decide whether the engine is able to ask the question in the first place. If the second half is missing, the first half is doing nothing, and nothing in the platform will raise its hand to tell you.&lt;/P&gt;&lt;P&gt;Before choosing clustering keys on a wide table, check that statistics exist for those columns. It takes one command and it occasionally explains months of confusing results.&lt;/P&gt;&lt;P&gt;Has anyone else lost time to this one? And for teams on managed tables with predictive optimization, has letting it choose statistics columns from query history worked better than the columns you would have picked yourself?&lt;/P&gt;</description>
    <pubDate>Wed, 30 Sep 2026 13:39:18 GMT</pubDate>
    <dc:creator>Islam_hoti</dc:creator>
    <dc:date>2026-09-30T13:39:18Z</dc:date>
    <item>
      <title>The 32nd Column: Why Your Z-Order or Clustering Key May Be Doing Nothing</title>
      <link>https://community.databricks.com/t5/community-articles/the-32nd-column-why-your-z-order-or-clustering-key-may-be-doing/m-p/170251#M1618</link>
      <description>&lt;P&gt;You cluster a table on a column, run the query, and nothing improves. The layout command completed successfully. The column is definitely in the filter. The file count in the query profile has not moved.&lt;/P&gt;&lt;P&gt;Before assuming the clustering did not work, check whether statistics exist for that column at all. In a wide table they may not, and nothing in the process will tell you.&lt;/P&gt;&lt;P&gt;How skipping actually works&lt;/P&gt;&lt;P&gt;Delta records statistics for each data file: the minimum value, the maximum value, and the null count per column, plus the record count. At query time the engine compares your predicate against those numbers and discards files that cannot possibly contain a match. A file is never opened, so the cost of that file drops to zero.&lt;/P&gt;&lt;P&gt;Clustering exists to make those ranges narrow. If a file spans the entire range of a column, the engine cannot rule it out. If the file covers a small slice, most queries can discard it immediately.&lt;/P&gt;&lt;P&gt;The whole mechanism rests on one assumption: that statistics exist for the column you are filtering on.&lt;/P&gt;&lt;P&gt;Where that assumption breaks&lt;/P&gt;&lt;P&gt;For Unity Catalog external tables, statistics are collected on the first 32 columns defined in your schema. Not the 32 most useful columns. Not the columns you filter on. The first 32, in declaration order.&lt;/P&gt;&lt;P&gt;Column 33 onward has no statistics. A filter on one of those columns cannot skip a single file. The query returns correct results, runs at full scan cost, and reports no error.&lt;/P&gt;&lt;P&gt;This is easy to walk into. Wide tables grow by accretion. Someone adds a field, then another, then a nested struct whose fields also count toward the limit. Two years later a new filter column sits at position 41 and the table quietly stops being skippable on it.&lt;/P&gt;&lt;P&gt;The documentation is explicit on the consequence for Z-order: Databricks recommends not using ZORDER BY on columns that do not have statistics collected, because it is ineffective and consumes compute for nothing. Liquid clustering reads the same per-file statistics, so the same reasoning applies to a clustering key that sits past the boundary.&lt;/P&gt;&lt;P&gt;One important exception&lt;/P&gt;&lt;P&gt;For Unity Catalog managed tables, predictive optimization does not use a fixed 32-column list. It runs ANALYZE and collects skipping statistics on the columns that actually appear most often in your query filters, so the limit does not apply.&lt;/P&gt;&lt;P&gt;This matters because it means two tables in the same workspace can behave completely differently, and the difference is invisible in the query text. Before debugging, establish which regime your table is in. If it is a managed table with predictive optimization enabled, the column position is not your problem and you should look elsewhere.&lt;/P&gt;&lt;P&gt;How to check&lt;/P&gt;&lt;P&gt;Start with what the table is configured to collect:&lt;/P&gt;&lt;P&gt;SHOW TBLPROPERTIES my_catalog.my_schema.my_table;&lt;/P&gt;&lt;P&gt;Look for delta.dataSkippingStatsColumns and delta.dataSkippingNumIndexedCols. If neither is set and the table is external, you are on the 32-column default.&lt;/P&gt;&lt;P&gt;To see what was actually written rather than what is configured, read the statistics out of the transaction log. Get the path first, then read the add actions:&lt;/P&gt;&lt;P&gt;DESCRIBE DETAIL my_catalog.my_schema.my_table;&lt;/P&gt;&lt;P&gt;Take the location value, then:&lt;/P&gt;&lt;P&gt;SELECT add.stats FROM json.&amp;lt;location&amp;gt;/_delta_log/*.json WHERE add IS NOT NULL LIMIT 5;&lt;/P&gt;&lt;P&gt;The stats field is a JSON document with minValues, maxValues and nullCount objects. The column you care about either appears in there or it does not, and that single check settles the question.&lt;/P&gt;&lt;P&gt;The most direct behavioural evidence is in the query profile. Compare files pruned against files read for a filter on the suspect column, then run the same shape of query filtered on a column you know is early in the schema. If one prunes and the other does not, you have your answer.&lt;/P&gt;&lt;P&gt;Three ways to fix it&lt;/P&gt;&lt;P&gt;Name the columns explicitly. On DBR 13.3 LTS and above this is the cleanest option, because it decouples statistics from schema position entirely:&lt;/P&gt;&lt;P&gt;ALTER TABLE my_table SET TBLPROPERTIES ('delta.dataSkippingStatsColumns' = 'region, order_date, promo_code');&lt;/P&gt;&lt;P&gt;This property supersedes the numeric limit. Be deliberate about the list, since statistics are not free to compute or store.&lt;/P&gt;&lt;P&gt;Raise the count. Works on all runtimes, but remains order dependent, so it is a blunter instrument:&lt;/P&gt;&lt;P&gt;ALTER TABLE my_table SET TBLPROPERTIES ('delta.dataSkippingNumIndexedCols' = '48');&lt;/P&gt;&lt;P&gt;Reorder the schema. Move the columns you filter on to the front. Correct, but it usually means rewriting the table, so it is rarely the pragmatic choice on an existing large table.&lt;/P&gt;&lt;P&gt;The step almost everyone misses&lt;/P&gt;&lt;P&gt;Changing either property does not recompute anything. It changes the behaviour of future writes only. Your existing files keep whatever statistics they were written with, and until they are rewritten your queries will not improve. People make the change, see no difference, and conclude the setting does not work.&lt;/P&gt;&lt;P&gt;On DBR 14.3 LTS and above you can force the recomputation:&lt;/P&gt;&lt;P&gt;ANALYZE TABLE my_table COMPUTE DELTA STATISTICS;&lt;/P&gt;&lt;P&gt;Two related traps&lt;/P&gt;&lt;P&gt;Long string columns are truncated during statistics collection. A column holding URLs, JSON blobs or free text will have min and max values that are effectively useless for skipping, while still costing you to collect. These are good candidates to exclude from the statistics column list rather than to include.&lt;/P&gt;&lt;P&gt;Nested fields count toward the limit individually. For statistics purposes each scalar field inside a struct is treated as its own column, so a struct with fifteen fields consumes fifteen of your slots. A table with twelve top-level columns can already be past the boundary if several of them are nested. Map and array columns cannot have statistics collected at all.&lt;/P&gt;&lt;P&gt;The takeaway&lt;/P&gt;&lt;P&gt;Clustering and statistics are two halves of the same mechanism, and only one of them is visible in the command you run. Layout decides whether a file can be ruled out. Statistics decide whether the engine is able to ask the question in the first place. If the second half is missing, the first half is doing nothing, and nothing in the platform will raise its hand to tell you.&lt;/P&gt;&lt;P&gt;Before choosing clustering keys on a wide table, check that statistics exist for those columns. It takes one command and it occasionally explains months of confusing results.&lt;/P&gt;&lt;P&gt;Has anyone else lost time to this one? And for teams on managed tables with predictive optimization, has letting it choose statistics columns from query history worked better than the columns you would have picked yourself?&lt;/P&gt;</description>
      <pubDate>Wed, 30 Sep 2026 13:39:18 GMT</pubDate>
      <guid>https://community.databricks.com/t5/community-articles/the-32nd-column-why-your-z-order-or-clustering-key-may-be-doing/m-p/170251#M1618</guid>
      <dc:creator>Islam_hoti</dc:creator>
      <dc:date>2026-09-30T13:39:18Z</dc:date>
    </item>
  </channel>
</rss>

