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.
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.
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:
-- 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.
-- 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.
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');
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);
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.
| Action | Privileges needed |
|---|---|
| Use a function as a filter or mask | EXECUTE on the function, plus USE SCHEMA and USE CATALOG on its parents |
| Create a table with a filter or mask | The above, plus CREATE TABLE on the schema |
| Add or remove one on an existing table | Table owner, or both MANAGE and SELECT on the table |
| Same statement also changes the schema | Additionally 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.
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.
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.
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.
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.
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.
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 →