Hi Danish, good push-back. You are right that getTable in the Unity Catalog audit logs is metadata-only: it fires when a client resolves a table reference or inspects its schema/properties, not when rows are actually scanned.

If you want a signal that a principal actually read data, the cleanest sources in system.access.audit are:

  • generateTemporaryTableCredential on the unityCatalog service. Unity Catalog issues a short-lived credential to read the underlying files whenever an engine is about to actually access table data (external readers, Delta Sharing recipients, serverless query engines, Lakehouse Federation, etc.). Filter on action_name = "generateTemporaryTableCredential" and pull request_params.table_full_name / request_params.operation (READ vs READ_WRITE). This is the strongest "the bytes were handed out" signal.
  • commandSubmit / commandFinish (clusters) and executeAdhocQuery / executeSavedQuery / queryStart (Databricks SQL warehouses). These include the actual commandText / statement, so you can confirm a SELECT against the table in question.
  • For Delta Sharing reads, look for deltaSharingQueriedTable and deltaSharingQueriedTableChanges.

The pragmatic pattern most folks land on: combine generateTemporaryTableCredential (the "credential was vended for this table" signal) with the query-execution events (the "this is the SQL that ran" signal), and join on request_id or by user_identity.email + time window. That gives you both who got read access and what they ran.

If you also want a higher-level view without parsing SQL, system.access.table_lineage records actual read/write lineage per user per table per source, which is often easier to query for "did user X read table Y in the last 30 days".

One caveat: external-engine reads via Iceberg REST Catalog or credential vending will show up as generateTemporaryTableCredential but you will not get the executed SQL, since the query ran outside Databricks. That is expected and is usually the best you can do for those paths.


Sources: