Row Filtering Patterns
with ABAC

Learn how to filter table rows based on user group membership and governed tags in Databricks Unity Catalog. This tutorial covers regional access control, healthcare time-based filters, emergency access overrides, and fraud detection patterns β€” all without writing a single line of per-table access logic.

What is ABAC Row Filtering?

ABAC (Attribute-Based Access Control) row filtering lets you attach policies to a schema rather than individual tables. A policy says: "for tables whose columns are tagged X, apply filter function F to group G." When a new table gets the tag, it automatically inherits the policy β€” no ALTER TABLE needed.

πŸ’‘

Key Principle

UDFs contain only data logic (e.g. region_val = 'us'). Group membership logic belongs in the POLICY using TO group and EXCEPT group. Never put is_account_group_member() inside a UDF β€” it causes per-row identity lookups.

Prerequisites

Account Groups

  • abac_tut_admins
  • abac_tut_us_team
  • abac_tut_eu_team
  • abac_tut_apac_team
  • abac_tut_hr_team
  • abac_tut_finance_team

Governed Tags

  • abac_tut_region
  • abac_tut_pii
  • abac_tut_emergency

Privileges Needed

  • CREATE FUNCTION
  • CREATE TABLE
  • APPLY TAG
  • MANAGE POLICY

Core Pattern

Every row filter is built with two components: a UDF that contains the filter logic, and a POLICY that wires the UDF to a group and a tag condition.

Step 1 β€” Write the filter UDF

The UDF takes the column value and returns BOOLEAN. Pure data logic only.

SQL
CREATE OR REPLACE FUNCTION {catalog}.{schema}.region_filter_us(region_val STRING)
RETURNS BOOLEAN
DETERMINISTIC
RETURN region_val = 'us'

Step 2 β€” Create the policy

The policy binds the UDF to a group (TO), defines which columns to apply it to (MATCH COLUMNS has_tag()), and specifies which column to pass to the UDF (USING COLUMNS).

SQL
CREATE OR REPLACE POLICY region_row_filter_us_policy
ON SCHEMA {catalog}.{schema}
ROW FILTER {catalog}.{schema}.region_filter_us
TO abac_tut_us_team
FOR TABLES
MATCH COLUMNS has_tag('abac_tut_region') AS region_col
USING COLUMNS (region_col)

A companion deny policy ensures users not in any regional group see zero rows:

SQL
CREATE OR REPLACE POLICY region_row_filter_deny_policy
ON SCHEMA {catalog}.{schema}
ROW FILTER {catalog}.{schema}.deny_all_filter
TO `account users`
EXCEPT abac_tut_us_team, abac_tut_eu_team, abac_tut_apac_team, abac_tut_admins
FOR TABLES
MATCH COLUMNS has_tag('abac_tut_region') AS region_col
USING COLUMNS (region_col)

Who Sees What

abac_tut_admins
All rows
Exempt from all regional filters
abac_tut_us_team
US region only
region = 'us'
abac_tut_eu_team
EU region only
region = 'eu'
abac_tut_apac_team
APAC only
region = 'apac'
No group
0 rows
Deny policy applies

Sample output (US team user)

Query result β€” SELECT * FROM customers
+----+------------------+------+------------------+
|id |name |region|email |
+----+------------------+------+------------------+
|1 |Alice Johnson |us |alice@example.com |
|4 |Diana Prince |us |diana@example.com |
|7 |George Washington |us |george@example.com|
+----+------------------+------+------------------+
3 rows (12 total β€” 9 filtered by ABAC policy)

πŸ₯

Healthcare β€” Time-Based & Emergency Access

HIPAA-aligned row filtering with business hours, on-call overrides, and regional routing

The healthcare example introduces three advanced row filter patterns on a hospital_visits table:

Business hours filter

Restricts access to 7AM–7PM Monday–Friday for standard staff:

SQL
CREATE OR REPLACE FUNCTION {catalog}.{schema}.business_hours_filter()
RETURNS BOOLEAN
RETURN (
  HOUR(CURRENT_TIMESTAMP()) BETWEEN 7 AND 19
  AND DAYOFWEEK(CURRENT_DATE()) BETWEEN 2 AND 6
)

Emergency override

On-call staff always see emergency visits regardless of hours or region:

SQL
CREATE OR REPLACE POLICY abac_hc_emergency_access
ON SCHEMA {catalog}.{schema}
ROW FILTER {catalog}.{schema}.emergency_access_filter
TO `account users`
EXCEPT abac_tut_admins
FOR TABLES
MATCH COLUMNS has_tag('abac_tut_emergency') AS emergency_col
USING COLUMNS (emergency_col)
⚠️

Policy Isolation

Before adding healthcare policies, drop the general regional policies (region_row_filter_*) β€” a table can only have one active row filter. Multiple schema-level policies that match the same tagged column cause a runtime error.


🏦

Financial Services β€” Fraud Detection

PCI-DSS and SOX-aligned row filtering for transaction data

The fraud detection example applies multi-condition row filters on a transactions table tagged with abac_tut_region:

Fraud analyst access

Analysts only see flagged transactions (fraud_flag = TRUE), regardless of region. Finance team and admins bypass this filter.

SQL
CREATE OR REPLACE FUNCTION {catalog}.{schema}.fraud_row_filter(fraud_flag BOOLEAN)
RETURNS BOOLEAN
RETURN fraud_flag = TRUE

-- Policy: apply to all users except finance + admins
CREATE OR REPLACE POLICY fraud_detection_filter
ON SCHEMA {catalog}.{schema}
ROW FILTER {catalog}.{schema}.fraud_row_filter
TO `account users`
EXCEPT abac_tut_finance_team, abac_tut_admins
FOR TABLES
MATCH COLUMNS has_tag_value('abac_tut_pii', 'fraud') AS fraud_col
USING COLUMNS (fraud_col)
RoleSeesReason
abac_tut_adminsAll 10 transactionsEXCEPT clause
abac_tut_finance_teamAll 10 transactionsEXCEPT clause
abac_tut_us_teamFraud-flagged onlyfraud_flag = TRUE filter
No group0 rowsDeny policy applies

βœ…

Key Learnings

  • UDFs = data logic only. Never use is_account_group_member() in a UDF β€” use TO / EXCEPT in the POLICY.
  • One active row filter per table. Drop previous section's policies before creating new ones for the same tagged columns.
  • CREATE OR REPLACE POLICY doesn't clean up renamed policies. Old names accumulate and conflict β€” always DROP explicitly.
  • Catalog-level policies auto-apply to new schemas, not just the current one.
  • MATCH COLUMNS only allows has_tag() / has_tag_value() β€” comparison operators like name = 'col' are not supported.