Row and Column Level Security in Unity Catalog: ABAC, Table-Level Filters, and Dynamic Views Compared
- Jul 16
- 5 min read
A comparison of three genuinely different approaches to row and column level security on Databricks — and the operational question that actually decides which one to use
Introduction
Row and column level security on Unity Catalog is not a single decision. It's three genuinely different mechanisms, each solving the same underlying problem — controlling who sees which rows and which column values — with different scope, different maintenance overhead, and a different failure mode when someone gets the configuration wrong.
The comparison usually gets flattened into "use whichever one you're familiar with," which is how you end up with the mistake that trips up more rollouts than any actual misconfigured filter: a security control that looks like it's working right up until a genuine, restricted user hits it for the first time.

Three Approaches to Row and Column Level Security
Attribute-Based Access Control (ABAC)
ABAC is Databricks' current recommended approach, and the sensible default unless there's a specific reason not to use it. It works by attaching governed tags — centrally defined, access-controlled tag keys and values — to catalogs, schemas, tables, or columns, and then writing a policy that references those tags rather than a specific table.
The distinction that matters in practice: this policy is written once, and every table carrying the matching tag inherits it — including tables that don't exist yet at the point the policy is written. A new table, tagged correctly, picks up the protection automatically. That's the entire value proposition, and it's a meaningfully different operating model to per-table Unity Catalog security.
Two things worth confirming before adopting this. ABAC policies require either Serverless compute or Databricks Runtime 16.4 or above with fine-grained access control filtering enabled — standard or dedicated compute on an older runtime can't access an ABAC-secured table at all, not a degraded experience, a hard failure. And ABAC policies can't be attached directly to a view. Where a view sits on top of an ABAC-protected table, the policy still evaluates but using the view owner's identity — which means the view owner needs genuine access to the underlying table for the whole arrangement to function, a detail that's easy to miss until access breaks for everyone querying that view.
Table-Level Row Filters and Column Masks
The direct predecessor to ABAC, and still the right call for a single table that needs bespoke logic that genuinely doesn't warrant a tag-driven policy.
The mechanism is straightforward — the UDF attaches directly to the table via ALTER TABLE, and filtering or masking happens transparently at query time. The genuine limitation is that the scope is exactly one table at a time. It's not uncommon to inherit an environment where the same masking logic has been copy-pasted across a dozen tables, each with a subtly different variant because one got adjusted later and the rest were never revisited. That's the real cost of this approach at scale, and it's exactly the problem ABAC was built to solve.
Dynamic Views
The right approach specifically when the same underlying data needs to look different to different consumers, rather than needing rows or values filtered out entirely at the source.
The structural difference that matters: a dynamic view is an entirely separate, secured object. Users query the view, not the base table, and the security logic lives inside the view's SQL definition. This tends to be the easiest of the three to maintain well, since the CREATE VIEW statement can sit in the same Git repository as the rest of the platform code and get reviewed like any other pull request — something that's much harder to do cleanly with logic buried in an ALTER TABLE statement.
The Question That Actually Decides Which to Use
The comparison above is useful for understanding the mechanics, but the decision that matters in practice comes down to a different question: how many things are querying the same data.
One table, queried directly by one tool: a table-level row filter or column mask is simple and does the job. Reaching for ABAC here isn't wrong, but it's more structure than the situation calls for.
Same underlying data queried by more than one BI tool, or by different teams who each need a different slice of it: dynamic views are easier to manage in version control, and updating the logic in one place doesn't mean touching the base table at all.
The same protection applied consistently across a growing number of tables, including ones that don't exist yet: ABAC is the mechanism actually built for that, and it's the sensible default for any Data Integration, Analytics, and AI platform where the table count is only going to grow.
The Mistake That Trips Up the Most Rollouts
Regardless of which mechanism is chosen, one mistake shows up more often than any actual misconfigured filter or mask: testing the security control while logged in as an admin.
Admin accounts typically bypass row filters, column masks, and dynamic view restrictions entirely. A row filter can look completely fine in review — because everyone testing it was an account admin, and the restriction was never actually being exercised — ship, and then break the first time a genuine, restricted user queries the table, in production, in front of exactly the person the whole thing was supposed to protect data from.
The only reliable test is logging in as, or impersonating, an actual member of the target group before calling any of this production-ready. This is worth treating as a non-negotiable step in any Data Integration, Analytics, and AI security rollout, not an optional nice-to-have.
What This Looks Like as a Production Pattern
For a Data Integration, Analytics, and AI platform maintaining row and column level security across a genuinely growing number of tables, the shape worth building toward is ABAC as the default mechanism, driven by a governed tag taxonomy designed deliberately at the start rather than retrofitted later, with table-level row filters and column masks reserved for genuinely one-off cases, and dynamic views kept specifically for situations where multiple consuming tools or teams need materially different, version-controlled views of the same underlying data. Every configuration, regardless of mechanism, should be verified against a genuine non-admin test account before it is considered done.
If this was useful
All three mechanisms — ABAC policies, table-level filters and masks, and dynamic views — plus a non-admin verification helper, are available in full on GitHub, built against the same sample data so the differences are directly comparable:
This article was written by Keith Jenneke, Principal Consultant & Practice Lead at Cypher Agency. Keith leads our data platform and AI engineering practice, building on Azure Databricks across professional services, resources, and government.




Comments