What you will be able to do
- Implement suppression with a mask that returns a constant
- Implement generalization by rounding values or revealing only part of them
- Implement deterministic pseudonymization with SHA2 and a version parameter for rotation
- Use USING COLUMNS for conditional masking, and apply one method across many tables with a tag-driven ABAC policy
1.Suppression: replace the value outright
A Unity Catalog column mask is a SQL UDF bound to a column. It is evaluated at query time and returns either the real value or a protected one. Each anonymization method is a different body for that function. The bluntest is suppression: the protected branch returns a constant, so nothing about the original value is visible. The SSN mask returning '***-**-****' and the address mask returning 'REDACTED' both work this way. So does this one-line PII mask from the ABAC pattern library.
CREATE FUNCTION catalog.schema.mask_pii(val STRING)
RETURNS STRING
RETURN '***';The constant has to fit the column. A STRING column can return '***'. A DOUBLE column has to suppress with a number such as 0.0, because the return value must be the column's type or castable to it.
Checkpoint 1 of 6· Check yourself
You need to suppress a DOUBLE salary column for non-HR users. Which ELSE branch is valid?
Every branch must return a value castable to DOUBLE. A text placeholder cannot be cast, so the mask fails.
“'CONFIDENTIAL' cannot be cast to a DOUBLE column type”Source: docs.databricks.com
Sources1
2.Generalization: keep the shape, lose the precision
Generalization keeps some analytic value. Instead of hiding the value completely, the mask makes it coarser. In the salary mask below, admins and HR see exact figures, managers see salaries rounded to the nearest thousand, and everyone else sees 0.0. One function therefore gives three audiences full access, generalized values and suppressed values.
Checkpoint 2 of 6· Fill the gap
Which function produces the generalized, rounded salary for managers?
-- Succeeds: Masks a DOUBLE column, returns DOUBLE in every branch
CREATE FUNCTION mask_salary(salary DOUBLE, user_role STRING)
RETURNS DOUBLE
RETURN CASE
WHEN user_role IN ('admin', 'hr') THEN salary
WHEN user_role = 'manager' THEN ? (salary / 1000) * 1000
ELSE 0.0
END;ROUND(salary / 1000) * 1000 rounds to the nearest thousand and returns a DOUBLE, so it fits the column type.
Source: docs.databricks.comPartial reveal is the string version of generalization: show the last few characters and hide the rest. Databricks recommends string functions for this rather than regular expressions, because regex masking scans the whole value on every row.
CREATE FUNCTION mask_ssn(ssn STRING, show_last INT) RETURNS STRING
DETERMINISTIC
RETURN CONCAT('***-**-', RIGHT(ssn, show_last));Regex masking scans the entire value for every row, which gets expensive on large text fields. Simple string functions avoid that cost, and fewer, simpler expressions also help the engine optimize masked queries.
Sources1
3.Hashing: deterministic pseudonymization
Suppression and generalization throw information away. Hashing keeps identity but hides the real value: the same input always produces the same pseudonym, so joins, counts of distinct customers and grouping still work. The documented pattern adds a version number to the value before hashing. Changing the version rotates the pseudonyms, while data hashed under the earlier version keeps its existing hashes. The DETERMINISTIC keyword tells the engine the output depends only on the inputs, which helps it optimize the query.
CREATE FUNCTION pseudonymize(val STRING, version INT) RETURNS STRING
DETERMINISTIC
RETURN SHA2(CONCAT(val, CAST(version AS STRING)), 256);Checkpoint 3 of 6· Check yourself
Your security team wants all pseudonyms produced by pseudonymize() to change from now on. What does the documented pattern suggest?
The version is concatenated into the hash input, so a new version produces new hashes. Historical data hashed under the previous version is not broken.
“Increment the version number through the policy's USING COLUMNS clause to generate new hashes without breaking historical data that used the previous version.”Source: docs.databricks.com
Checkpoint 4 of 6· Exam question
An analytics team is only permitted to study workforce age distribution in bands (e.g. "born in the 1990s") and must never see an employee's exact `date_of_birth`, while HR staff still need the full date for benefits administration. The engineer must implement this with a single Unity Catalog column mask on `date_of_birth`. Which masking approach satisfies both requirements?
Correct answer: B — Have the mask return `YEAR(date_of_birth)` cast back to the column's date type for everyone outside HR, generalizing the value to birth year only.
- A. Returning `NULL` suppresses the value entirely, which satisfies the privacy requirement but destroys the birth-year information the analytics team was explicitly granted access to. Suppression is too aggressive for this requirement.
- B. Reducing the date to its year component is generalization: it keeps exactly the granularity the analytics team is authorized to see for age-band studies while hiding the exact day and month, and HR still receives the untouched value through the mask's conditional logic.
- C. A cryptographic hash of the date produces an opaque fixed-length string that cannot be grouped into meaningful birth-year bands or cast back to a date, so it does not give the analytics team the age information they are supposed to have.
- D. Token-vault lookups are built for reversible pseudonymization of identifiers like account or card numbers, not for producing a coarser, still-analyzable version of a date; the analytics team would receive an opaque token instead of a usable birth-year signal.
Sources1
4.Conditional masking and applying a method at scale
The USING COLUMNS clause passes extra arguments to a mask. The first parameter is always the masked column. Further parameters can be other columns in the same row or constants. This is how the rotation version reaches pseudonymize, and it also lets one mask treat rows differently. In the example below, a user sees an address only if they belong to the group for that row's country.
CREATE FUNCTION mask_address_by_country(address STRING, country STRING, group_suffix STRING DEFAULT '_address_viewers')
RETURN IF(
is_account_group_member(country || group_suffix),
address,
'REDACTED'
);Binding a mask to each column with ALTER TABLE stops being practical once PII is spread across many tables. Databricks recommends ABAC policies for this. A policy is attached to a catalog or schema and applies the same masking function to every column whose governed tag matches. The policy below applies the mask_pii suppression function to any column tagged as a name or address, once the table is no longer tagged as unverified.
CREATE POLICY mask_reviewed_pii
ON CATALOG my_catalog
COLUMN MASK catalog.schema.mask_pii
TO `account users`
EXCEPT `data_admins`
FOR TABLES
WHEN NOT has_tag_value('classification', 'unverified')
MATCH COLUMNS (has_tag_value('pii', 'name') OR has_tag_value('pii', 'address')) AS m
ON COLUMN m;ABAC policies also allow one UDF that takes and returns VARIANT to mask columns of many types. That casting works only inside ABAC policies, not in table-level masks. Whichever mechanism you use, keep the number of masks down: mask only the columns that really are sensitive, and reuse functions.
| Method | Documented example | What survives |
|---|---|---|
| Suppression | mask_pii returns '***' | Nothing about the value |
| Generalization | mask_salary: ROUND(salary / 1000) * 1000 | Approximate magnitude |
| Partial reveal | mask_ssn: CONCAT('*--', RIGHT(ssn, show_last)) | The last characters |
| Hashing (pseudonymization) | pseudonymize: SHA2(CONCAT(val, CAST(version AS STRING)), 256) | Equality across tables, for joins |
Checkpoint 5 of 6· Match them up
Match each requirement to the technique that meets it
Tap a term, then the definition that fits it.
Hashing keeps equality, rounding coarsens values, USING COLUMNS passes in other row values, and an ABAC policy applies a mask by governed tag rather than table by table.
“USING COLUMNS enables conditional masking based on values beyond the column being masked.”Source: docs.databricks.com
Checkpoint 6 of 6· Exam question
For PCI compliance, a payments table stores full credit card numbers in `card_number`. Fraud investigators must be able to recover the original number for flagged transactions, while all other analysts should see only a surrogate value. The engineer implements this as a Unity Catalog column mask whose function queries a separate `card_token_vault` table keyed on the original number. Which statements correctly describe requirements for this tokenization design? (Select all that apply)(Select 3)
Correct answers: A, B, C — The `card_token_vault` table should live in a separate, tightly scoped schema with its own grants, so reversing a token is restricted independently of who can query `card_number`.; The same `card_number` value must always map to the same token, so joins and duplicate-detection logic on the masked column keep working the same way they did before masking was introduced.; Fraud investigators should be exempted from the mask function's `CASE` logic, for example via `is_account_group_member`, so they receive the original `card_number` value while everyone else receives the token.
- A. Isolating the vault table in its own schema with separate grants means access to reverse a token is a distinct, auditable privilege from access to the masked column, which is exactly the separation PCI-style tokenization designs rely on.
- B. Consistent token generation per input value is a core property of tokenization: it preserves referential integrity so downstream joins, counts, and duplicate checks on the masked column continue to behave correctly.
- C. The mask function needs a branch that returns the true `card_number` for the authorized fraud-investigator group and the token otherwise, which is precisely how a group-membership check inside the masking `CASE` expression is used.
- D. A cryptographic hash is a one-way transformation with no vault to reverse it, so it cannot restore the original card number for fraud investigators the way a token-to-value lookup can; hashing and tokenization solve different problems here.
- E. A masking function's return type must match or be castable to the type of the column it masks, not `BOOLEAN`; the function returns the value to display, and Unity Catalog does not track per-row tokenization success as a separate signal.
- F. If the function only takes `card_number` as its single parameter, a plain `SET MASK` clause without `USING COLUMNS` is fine, but that has nothing to do with whether the design queries a token vault — a `USING COLUMNS` clause is only needed when the function reads additional columns from the same table, not vault tables.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A suppression mask can return a text placeholder such as 'CONFIDENTIAL' for any column.Why is that wrong?
Every branch must return the column's type or a type castable to it. For a DOUBLE column, suppress with a number such as 0.0.
Covered in Suppression: replace the value outright
2.A generic VARIANT masking function works as a table-level mask set with ALTER TABLE.Why is that wrong?
The automatic VARIANT casting for complex types works only inside ABAC policies.
Covered in Conditional masking and applying a method at scale
Practise it for real
Bind a suppression mask to an SSN column, confirm that users outside the HR group see it masked, then remove it safely
1.Run CREATE FUNCTION ssn_mask(ssn STRING) RETURN CASE WHEN is_account_group_member('HumanResourceDept') THEN ssn ELSE '*--****' END;
Why: The mask must exist as a SQL UDF in Unity Catalog before it can be bound
You should see: The function is created in your current schema
2.Run CREATE TABLE users (name STRING, ssn STRING); then ALTER TABLE users ALTER COLUMN ssn SET MASK ssn_mask;
Why: This binds the mask to an existing column
You should see: The ssn column shows a column mask in Catalog Explorer
3.Insert a row, then run SELECT * FROM users as a user who is not in HumanResourceDept
Why: The mask is evaluated per query against the caller's group membership
You should see: ssn returns *--****
4.Run ALTER TABLE users ALTER COLUMN ssn DROP MASK; and only then DROP FUNCTION ssn_mask;
Why: Dropping the function first would leave the table inaccessible
You should see: Queries return original ssn values and the table remains readable
Stuck? Get a nudge
If you are in HumanResourceDept you will see real values. Test with another user, or temporarily change the group name in the function.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“When masking a column, return the same data type or a type that is castable to it.”
↩︎ Suppression: replace the value outright“Reveal part of a sensitive value using string operations instead of regex.”
↩︎ Generalization: keep the shape, lose the precision“Regex-based masking scans the entire value for every row, which is expensive on large text fields”
↩︎ Generalization: keep the shape, lose the precision“Consistent hashing (also called deterministic pseudonymization) replaces sensitive data with a hashed value that is the same across multiple tables.”
↩︎ Hashing: deterministic pseudonymization“Marking a function as DETERMINISTIC tells the engine that the function always returns the same result for the same input”
↩︎ Hashing: deterministic pseudonymization“A single masking UDF that accepts and returns a VARIANT can mask columns of many data types”
↩︎ Conditional masking and applying a method at scale“When masking a column, return the same data type or a type that is castable to it.”
↩︎ Exam trap 1“This casting works only within ABAC column mask and row filter policies, not in general SQL or table-level masks.”
↩︎ Exam trap 2“'CONFIDENTIAL' cannot be cast to a DOUBLE column type”
↩︎ Checkpoint“Increment the version number through the policy's USING COLUMNS clause to generate new hashes without breaking historical data that used the previous version.”
↩︎ Checkpoint - 2.https://docs.databricks.com/aws/en/data-governance/unity-catalog/filters-and-masks/manually-applyOfficial docs
“The first parameter of the masking function always maps to the masked column itself.”
↩︎ Conditional masking and applying a method at scale“USING COLUMNS enables conditional masking based on values beyond the column being masked.”
↩︎ Checkpoint - 3.
“ABAC policies attach at the catalog or schema level and apply automatically based on governed tags, rather than requiring per-table configuration.”
↩︎ Conditional masking and applying a method at scale“Apply masks only to truly sensitive columns and reuse masking functions where possible.”
↩︎ Conditional masking and applying a method at scale