In BigQuery, how do row access policies differ from publishing one authorized view per audience?
answer
- the rule lives on the table, not in objects
- grantee list plus a filter predicate
- one policy present changes it for everyone
- multiple matching policies combine, not intersect
- TRUE is how an admin group sees all
basics
~20 sA BigQuery row access policy attaches a named filter and a grantee list to the table itself, so every query by those principals is filtered automatically. Views push the same logic into one object per audience, which duplicates and drifts.
solid answer
~50 sA row access policy is DDL on the table: `CREATE ROW ACCESS POLICY emea ON sales.transactions GRANT TO ('group:[email protected]') FILTER USING (region = 'EMEA')`. Once any policy exists on a table, filtering is default-deny — a principal not matched by any policy queries successfully but sees zero rows. A principal matched by several policies sees the union of their filters, and `FILTER USING (TRUE)` is how you give an admin group everything. The practical difference from views is *where the rule lives*. With one authorized view per audience you maintain N objects that must all be updated when the schema or the business rule changes, and every downstream consumer has to be pointed at the right one. With row-level security the rule sits on the table, so a dashboard, a Connected Sheets refresh and an ad-hoc query all inherit it from the same definition. Row-level security filters rows only. Hiding a column is a separate mechanism — policy tags plus a fine-grained reader role.
code
sql · 9 linesCREATE OR REPLACE ROW ACCESS POLICY emea_only
ON `sales.transactions`
GRANT TO ('group:[email protected]')
FILTER USING (region = 'EMEA');
CREATE OR REPLACE ROW ACCESS POLICY leadership_all
ON `sales.transactions`
GRANT TO ('group:[email protected]')
FILTER USING (TRUE);go deeper
Know that BigQuery can filter rows per user with a policy defined on the table, and that the alternative — one view per audience — duplicates the same rule many times.
Be ready to write the DDL and explain default-deny once a policy exists, the union of multiple matching policies, and the FILTER USING (TRUE) escape hatch.
Demonstrate production judgment: identity propagation from BI tools, the cost of an expensive filter predicate, and what breaks when a pipeline copies the table under a broader identity.
Own the governance model — where row-level, column-level and dataset-level controls each belong, how policies are reviewed and audited, and how the design scales as audiences multiply.
## What a row access policy is Row-level security in BigQuery is expressed as named policy objects attached to a table. Each policy carries two things: a grantee list of IAM principals, and a filter predicate over the table's columns. When a principal in the grantee list queries the table — directly, through a view, from a dashboard, from a spreadsheet — BigQuery applies the predicate before anything else in the query sees the rows. ```sql CREATE OR REPLACE ROW ACCESS POLICY emea_only ON `sales.transactions` GRANT TO ('group:[email protected]') FILTER USING (region = 'EMEA'); ``` ## Default deny, and the union rule The two semantics that get asked about are these. As soon as **one** policy exists on a table, the table is filtered for everyone: a principal who is not named in any policy's grantee list still has table read access, but the visible row set is empty. Their query does not error — it returns nothing, which is the behaviour to expect and to warn stakeholders about, because an empty dashboard looks like a data problem rather than a permissions problem. When a principal is matched by **several** policies, they see the union of those filters, not the intersection. That makes composition additive: a regional policy plus a product-line policy grants both slices. It also means you cannot express "EMEA *and* only non-PII rows" with two separate policies — combine those conditions in a single filter. To grant an audience the whole table without removing security from everyone else, give them a policy whose predicate is trivially true: ```sql CREATE OR REPLACE ROW ACCESS POLICY all_rows ON `sales.transactions` GRANT TO ('group:[email protected]') FILTER USING (TRUE); ``` ## Self-service filtering with SESSION_USER A filter can reference `SESSION_USER()`, the identity running the query. That turns one policy into per-person filtering without one policy per person: ```sql FILTER USING (owner_email = SESSION_USER()) ``` Or, more usefully, join against a mapping table of user-to-territory inside the predicate, so the access rule is data you maintain rather than DDL you redeploy. This is the same trick that predates row-level security inside authorized views, now applied where it belongs. ## Versus a view per audience Authorized views remain the right tool for **shaping**: dropping columns, renaming, joining reference data, exposing a curated model of a messy table. They become the wrong tool for **row filtering by audience**, for three reasons. First, duplication. Ten audiences means ten near-identical view bodies, and a schema change or a corrected business rule means ten edits — with the failure mode being the one you missed. Second, routing. Every consumer must be told which view is theirs. A single shared dashboard cannot serve two audiences, because it names one object. With policies on the base table, one dashboard definition serves everyone and each viewer sees their own rows — provided the dashboard runs under each viewer's identity rather than a shared service account. Third, inheritance. A policy on the table applies wherever the table is read. That includes tooling nobody told you about. The cases where views still win: when the audiences need genuinely different *shapes*, when you want to precompute an expensive slice, and when the consumer must be denied even the knowledge of the table's existence. ## Limits and gotchas **Identity must reach the database.** Everything above depends on the querying identity being the real person. Point a BI tool at BigQuery with one shared service account and every viewer resolves to that account, so row-level security either shows everyone everything or nothing. This is the single most common way an otherwise-correct policy design fails in production. **Filters cost something.** The predicate is applied at scan time; a policy that filters on a clustered or partitioned column is close to free, one that joins a mapping table is not. **Caching is per-user.** BigQuery's query result cache is scoped to the identity that ran the query, so one user's filtered results are not served to another. Do not rely on that as your security boundary, but do not fear it either. **Rows only.** A row access policy cannot hide a column. Column-level control is a different mechanism: tag the column with a policy tag from a taxonomy and require a fine-grained reader role to read it. Combine the two when a table has both restricted rows and restricted columns. **Copies escape.** Any pipeline that reads the table under a broad identity and writes the result somewhere less protected has just erased the filtering. Governance has to cover the downstream tables, not only the source.
- A user with table read access is not named in any row access policy on that table. What do they see?Nothing. Once at least one policy exists, the table is filtered for every principal, and one matched by no policy sees an empty result rather than an error. Plan for that: an empty dashboard reads as a data outage to the person looking at it, so pair the rollout with a communication, and give any audience that legitimately needs everything a policy with FILTER USING (TRUE).
- Why does row-level security often fail once a BI tool is put in front of the table?Because the tool frequently connects with one shared service account. Every viewer then resolves to that identity, so the policies grant them all the same rows — usually everything the service account is allowed, or nothing at all. Row-level security only works when the querying identity is the end user, which means per-user credentials, identity delegation, or filtering enforced in the BI layer instead.
- How would you restrict a sensitive column rather than a set of rows?Row access policies cannot do it. Column-level access control uses a taxonomy of policy tags: tag the column, then require a fine-grained reader role on that tag. Users without the role see an error when they select the column and can still query the rest of the table. The two mechanisms compose, so a table can have both restricted rows and restricted columns.
saying these in an interview costs you the question
- Thinks unmatched users get a permission error rather than zero rows
- Assumes multiple matching policies intersect instead of union
- Expects a row policy to hide a sensitive column too
- Runs the BI tool on one service account and expects per-user filtering
- Forgets that a pipeline copying the table erases the filtering downstream