Self-service without chaos
Self-service means people answer their own questions without waiting for the data team. Done carelessly, it gives you hundreds of dashboards, five versions of "sales" and data in front of people who should not see it. This lesson shows the controls that let many people explore safely: row-level access, certified and sandbox content on governed datasets, plain-language questions with checks, and a routine for retiring what nobody uses.
By the end you can
- Design who-sees-what rules by role and enforce them in the database or semantic layer, with Postgres row-level security as the example
- Set up certified, team and sandbox content tiers on governed datasets, with a path to promote good work
- Judge an AI-generated answer: check the metric, the joins, the dates and the filters it used
- Monitor real usage and run a retire loop: no human views in 90 days, owner asked, archived, then deleted
The everyday picture
Think of a good public library. Anyone can walk in, take a book from the shelf and read it. Nobody has to ask a librarian first. That is self-service, and it is the whole point of the place.
Yet the library is not a free-for-all. The reference shelf (dictionaries, maps, the official statistics) is chosen and checked by librarians, and people trust it because of that. The community notice board lets anyone pin up a page, and everyone knows those pages are unchecked. Some rooms, like the archive of private letters, need a card with the right permission. And every year the librarians go through the shelves and move books nobody has borrowed in years to storage, so the useful books are easy to find. Self-service BI needs the same four things: open shelves, a trusted shelf, locked rooms and regular weeding.
Who can see what: row-level security
Once hundreds of people can build and open reports, the first question is what each of them is allowed to see. Most rules have two parts. Rows: a store manager sees her own store, a city manager sees the stores in her city, head office sees everything. Columns: almost nobody needs customer phone numbers or emails in a sales report, so those columns stay out of the shared datasets altogether.
Row-level security (RLS) is the mechanism for the first part. It has three pieces:
- A mapping from each user (or group) to the rows they may see, for example a table
user_sitewith one row per user and store. It should be fed from the HR or staff system every night, so a manager who moves to another store loses the old one the next day. - A policy: a filter the system adds to every query on the protected table, such as "only rows whose
site_idis in this user's mapping". - The user's identity reaching the place where the policy runs. This is the part that most often goes wrong.
The key property is that the filter is added by the server, after the user has written or chosen the query. The user cannot remove it. A filter set in the dashboard, such as a "Store" drop-down that defaults to the manager's store, is a convenience. Anyone who can edit the chart, export the data or open the dataset in another report can remove it.
In PostgreSQL the policy is part of the table. You switch row security on, then write a policy whose USING condition every row must pass:
ALTER TABLE sale ENABLE ROW LEVEL SECURITY;
CREATE POLICY sale_by_store ON sale
FOR SELECT
TO bi_reader -- the role the BI tool connects as
USING (site_id IN (
SELECT site_id FROM user_site
WHERE user_email = current_setting('app.user_email', true)
));
-- Before each user's queries, the BI tool (not the user) runs:
SET app.user_email = 'ama@mangrove.example';Three details matter. First, BI tools usually connect with one shared account, so current_user is the same for everyone. The tool has to pass the real viewer, here in a custom setting that current_setting reads. This is only safe when users cannot run their own SET on that connection. Second, if the setting is missing, current_setting(…, true) returns null, no row matches and the user sees nothing. That is the right way to fail. Third, the table owner, superusers and roles with the BYPASSRLS attribute skip policies, so the BI account must be an ordinary role that does not own the table (or the table needs FORCE ROW LEVEL SECURITY).
You can also enforce the rule in the BI tool or semantic layer instead of the database. Power BI defines row-level security as roles with DAX filter expressions; Looker uses access filters tied to user attributes; most other tools have a similar feature. Pick one place and enforce it there for every path to the data, including exports, scheduled emails, extracts and summary tables. A policy on sale does not protect a weekly_store_sales table built from it.
Quick check
The store managers' dashboard opens with the Store filter set to the viewer's own store, read from their profile. Is that enough to keep Ama from seeing other stores' sales?
Hiding or pre-setting a filter changes what the page shows; the dataset behind it still returns every store. Row-level security is applied by the database or the BI server to every query, whatever report or export it comes from. Hiding the control is a common shortcut that fails the first time someone exports.