Unity Catalog Row Filters and Column Masks: SQL, Privileges and Pitfalls
Databricks DE Associate · Deep dive

Unity Catalog Row Filters and Column Masks: SQL, Privileges and Pitfalls

In Unity Catalog, a row filter is a SQL function that returns true for each row a user may see, and a column mask is a SQL function that returns the value a user sees in one column. Both attach to the table with ALTER TABLE, run on every query, and never change the stored data.

Last updated October 2026.

What this deep dive adds

Chapter 34 of the course covers the idea: a mask redacts a column, a row filter hides rows, and a function that checks the caller's identity decides. This post is the working SQL behind that chapter: complete functions, the statements that attach and remove them, the privileges each step needs, and the limits that show up as exam distractors.

Fine-grained access sits in Governance and Security, the last of the seven sections in the May 2026 version of the Databricks Certified Data Engineer Associate exam.

What each user sees

The examples below put a row filter on region that lets admins through, and a mask on ssn that shows the real value only to the hr group. Two people running the same query against the same table get this:

One customers table, two callers An admin, in the admin and hr groups, sees all four rows with real SSNs. An analyst in neither group sees only the two US rows, Ana and Caro, with SSNs masked as stars; Ben (EU) and Dev (APAC) are filtered out. WHAT AN ADMIN SEES member of the admin and hr groups nameregionssn AnaUS123-45-6789 BenEU234-56-7891 CaroUS345-67-8912 DevAPAC456-78-9123 WHAT AN ANALYST SEES member of neither group nameregionssn AnaUS***-**-**** BenEUfiltered out CaroUS***-**-**** DevAPACfiltered out Row filter: only region = 'US' unless admin Column mask: real ssn only for hr
Same table, same query: the row filter removes rows, the column mask changes one value, the stored data is untouched.

A row filter, end to end

-- 1. The function: TRUE keeps the row, FALSE hides it
CREATE FUNCTION main.security.us_filter(region STRING)
RETURN IF(IS_ACCOUNT_GROUP_MEMBER('admin'), true, region = 'US');

-- 2. Attach it; ON (...) passes table columns to the parameters, in order
ALTER TABLE main.sales.orders
  SET ROW FILTER main.security.us_filter ON (region);

-- Or attach it when the table is created
CREATE TABLE main.sales.orders (region STRING, id INT)
  WITH ROW FILTER main.security.us_filter ON (region);

-- 3. Remove it
ALTER TABLE main.sales.orders DROP ROW FILTER;

The function receives the row's region value and returns a boolean. Members of admin get true for every row; everyone else sees only US rows. The parameter type must match the column type. Anyone who queries the table gets the filtered result, whether they use SQL, a notebook or a dashboard, because the filter belongs to the table, not to a view in front of it.

A column mask, end to end

-- 1. The function: returns what the caller sees in place of the value
CREATE FUNCTION main.security.ssn_mask(ssn STRING)
RETURN CASE WHEN IS_ACCOUNT_GROUP_MEMBER('hr') THEN ssn
            ELSE '***-**-****' END;

-- 2. Attach it at creation...
CREATE TABLE main.hr.employees (
  name STRING,
  ssn  STRING MASK main.security.ssn_mask
);

-- ...or to an existing column
ALTER TABLE main.hr.employees
  ALTER COLUMN ssn SET MASK main.security.ssn_mask;

-- 3. Remove it
ALTER TABLE main.hr.employees ALTER COLUMN ssn DROP MASK;

The mask's first parameter is the column being masked, and its return type must match the column type or be castable to it. A column has at most one mask. The row still comes back; only that value changes.

Masks that depend on another column

USING COLUMNS passes extra columns, or constants, to the mask. Here an address is visible only to the group for that row's country:

CREATE FUNCTION main.security.mask_address(
  address STRING, country STRING, group_suffix STRING)
RETURN IF(IS_ACCOUNT_GROUP_MEMBER(country || group_suffix),
          address, 'REDACTED');

ALTER TABLE main.crm.customers
  ALTER COLUMN address
  SET MASK main.security.mask_address USING COLUMNS (country, '_address_viewers');

Mapping tables: access rules as data

Hard-coding group names in a function means editing SQL every time access changes. A mapping table moves the rules into rows instead:

CREATE TABLE main.security.region_access (username STRING, region STRING);

CREATE FUNCTION main.security.region_filter(row_region STRING)
RETURN IS_ACCOUNT_GROUP_MEMBER('admin')
    OR EXISTS (SELECT 1 FROM main.security.region_access a
               WHERE a.username = SESSION_USER()
                 AND a.region = row_region);

ALTER TABLE main.sales.orders
  SET ROW FILTER main.security.region_filter ON (region);
How a mapping table decides what Ben sees The region_access table maps ana@example.com to US and ben@example.com to EU and APAC. When Ben queries the orders table, SESSION_USER() returns Ben's address, the filter keeps rows for EU and APAC, and the result holds orders 102 (EU) and 104 (APAC). Ben needs no SELECT on region_access because the filter runs with its owner's rights. REGION_ACCESS (MAPPING TABLE) username region ana@example.comUS ben@example.comEU ben@example.comAPAC Ben runs SELECT * FROM sales.orders SESSION_USER() = ben@example.com The filter keeps Ben's regions: EU, APAC BEN'S RESULT idregionamount 102EU40.00 104APAC75.50 Ben needs no SELECT on region_access: the filter runs with its owner's rights.
The access rules live in rows, so granting a region is an INSERT, not a code change.

Granting a manager a new region is now an INSERT. The filter runs with the rights of its owner, so analysts do not need SELECT on region_access. Identity functions such as SESSION_USER() and IS_ACCOUNT_GROUP_MEMBER() are the exception: they always evaluate as the person running the query, which is what makes the filter personal.

Who can do what

ActionPrivileges needed
Use a function as a filter or maskEXECUTE on the function, plus USE SCHEMA and USE CATALOG on its parents
Create a table with a filter or maskThe above, plus CREATE TABLE on the schema
Add or remove one on an existing tableTable owner, or both MANAGE and SELECT on the table
Same statement also changes the schemaAdditionally MODIFY

Sourced from the Databricks documentation pages "Row filters and column masks" and "Manually apply row filters and column masks".

Order matters when removing: run DROP ROW FILTER or DROP MASK before DROP FUNCTION. Dropping the function first leaves the table pointing at a function that no longer exists, and the table becomes unreadable.

Limits worth knowing

What this means for the exam

Exam tip: "see the table but not this column's real value" is a column mask. "See only some rows" is a row filter. "One copy of the table, governed centrally" rules out per-team copies and per-team views. "Same rule across hundreds of tagged tables" is ABAC, not a function attached table by table.

Distractors often offer REVOKE SELECT, which removes the whole table, or a mask where a filter is needed (the row would still appear, just with a redacted value). How plain grants and the privilege hierarchy work is in the Unity Catalog vs Lake Formation post and Chapter 33 of the course.

FAQ

What is the difference between a row filter and a column mask in Unity Catalog?

A row filter decides which rows a user sees; a column mask decides what value a user sees in one column. Both are SQL functions attached to the table, so every query gets the result without the stored data changing.

Can I apply a row filter or column mask to a view?

No. Row filters and column masks attach to tables only. For a view, the older approach is a dynamic view that checks the caller with functions such as IS_ACCOUNT_GROUP_MEMBER in its own SQL.

What privileges do I need to add a column mask to a table?

You need to own the table, or hold both MANAGE and SELECT on it, plus EXECUTE on the mask function and USE SCHEMA and USE CATALOG on its parents. If the same statement also changes the table schema, you also need MODIFY.

Should I use row filters or ABAC policies?

Use a row filter for one table with its own rule. Databricks recommends ABAC policies when the same filtering or masking must apply consistently across many tables, because they are applied centrally from tags and table owners cannot override them.

Back to the chapter

Column masking and row-level security is Chapter 34 of the Databricks Data Engineer Associate course, in Unit 7, Governance and Security, with the access decision built up card by card and exam-style checks.

Open Chapter 34 →