Snowflake

Snowflake ACCESS_HISTORY: Column-Level Tracking Guide

2026-03-26
更新: 2026-03-27
NicheeLab Editorial Team

SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY is a view that records, at the column level, which columns of which objects were accessed by every query run in your Snowflake account. It serves both as an audit trail for compliance requirements such as GDPR, CCPA, and SOX, and as a foundation for data lineage tracking. It is available on Enterprise Edition or higher.

Structure of ACCESS_HISTORY

The ACCESS_HISTORY view contains several semi-structured columns (ARRAY / OBJECT types), each recording access information at a different level.

ColumnTypeDescription
query_idVARCHARUnique identifier for the query
query_start_timeTIMESTAMP_LTZTime the query started executing
user_nameVARCHARUser who executed the query
direct_objects_accessedARRAYObjects directly referenced by the query (including views) and their column info
base_objects_accessedARRAYUnderlying base tables that were ultimately accessed, with column info
objects_modifiedARRAYObjects modified by INSERT/UPDATE/DELETE and similar statements, with column info
object_modified_by_ddlOBJECTObject targeted by a DDL operation (CREATE/ALTER/DROP, etc.)
policies_referencedARRAYPolicies evaluated during access (masking, row access, etc.)

direct_objects_accessed vs base_objects_accessed

-- 例: ビュー経由でテーブルにアクセスした場合
-- CREATE VIEW v_customers AS SELECT name, email FROM customers;
-- SELECT name FROM v_customers;

-- direct_objects_accessed の内容:
-- [{ "objectName": "V_CUSTOMERS",
--    "objectDomain": "View",
--    "columns": [{"columnName": "NAME"}] }]

-- base_objects_accessed の内容:
-- [{ "objectName": "CUSTOMERS",
--    "objectDomain": "Table",
--    "columns": [{"columnName": "NAME"}] }]

-- → ビュー V_CUSTOMERS を直接参照したが、
--   実際にアクセスされたベーステーブルは CUSTOMERS の NAME 列

Audit Query for READ Operations

-- 特定テーブルの列にアクセスしたユーザーと頻度を分析
SELECT
  ah.user_name,
  bo.value:objectName::STRING AS table_name,
  col.value:columnName::STRING AS column_name,
  COUNT(*) AS access_count,
  MIN(ah.query_start_time) AS first_access,
  MAX(ah.query_start_time) AS last_access
FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY ah,
  LATERAL FLATTEN(input => ah.base_objects_accessed) bo,
  LATERAL FLATTEN(input => bo.value:columns) col
WHERE ah.query_start_time >= DATEADD(DAY, -30, CURRENT_TIMESTAMP())
  AND bo.value:objectName::STRING = 'CUSTOMERS'
GROUP BY 1, 2, 3
ORDER BY access_count DESC;

Audit Query for WRITE Operations

-- テーブルへのWRITE操作を追跡
SELECT
  ah.query_id,
  ah.user_name,
  ah.query_start_time,
  om.value:objectName::STRING AS modified_table,
  om.value:objectDomain::STRING AS object_type,
  col.value:columnName::STRING AS modified_column
FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY ah,
  LATERAL FLATTEN(input => ah.objects_modified) om,
  LATERAL FLATTEN(input => om.value:columns) col
WHERE ah.query_start_time >= DATEADD(DAY, -7, CURRENT_TIMESTAMP())
  AND om.value:objectName::STRING = 'CUSTOMER_PII'
ORDER BY ah.query_start_time DESC;

Policy Auditing with policies_referenced

When a masking policy or row access policy is evaluated during query execution, the policies_referenced column records the policy name and kind. This lets you see which policies are applied and how often.

-- マスキングポリシーの適用状況を分析
SELECT
  pol.value:policyName::STRING AS policy_name,
  pol.value:policyKind::STRING AS policy_kind,
  COUNT(DISTINCT ah.query_id) AS query_count,
  COUNT(DISTINCT ah.user_name) AS user_count
FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY ah,
  LATERAL FLATTEN(input => ah.policies_referenced) pol
WHERE ah.query_start_time >= DATEADD(DAY, -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY query_count DESC;

Building Data Lineage

By combining base_objects_accessed and objects_modifiedin ACCESS_HISTORY, you can build a data lineage graph that shows which table's data flowed into which other tables.

-- データフロー(ソーステーブル → ターゲットテーブル)の可視化
SELECT
  bo.value:objectName::STRING AS source_table,
  om.value:objectName::STRING AS target_table,
  ah.user_name,
  COUNT(*) AS flow_count,
  MAX(ah.query_start_time) AS latest_flow
FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY ah,
  LATERAL FLATTEN(input => ah.base_objects_accessed) bo,
  LATERAL FLATTEN(input => ah.objects_modified) om
WHERE ah.query_start_time >= DATEADD(DAY, -30, CURRENT_TIMESTAMP())
  AND ARRAY_SIZE(ah.objects_modified) > 0
GROUP BY 1, 2, 3
ORDER BY flow_count DESC;

Detecting Access to Sensitive Columns

-- PII列(email, ssn, phone等)へのアクセスを検出
SELECT
  ah.user_name,
  bo.value:objectName::STRING AS table_name,
  col.value:columnName::STRING AS column_name,
  COUNT(*) AS access_count
FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY ah,
  LATERAL FLATTEN(input => ah.base_objects_accessed) bo,
  LATERAL FLATTEN(input => bo.value:columns) col
WHERE ah.query_start_time >= DATEADD(DAY, -7, CURRENT_TIMESTAMP())
  AND col.value:columnName::STRING IN ('EMAIL', 'SSN', 'PHONE_NUMBER', 'CREDIT_CARD')
GROUP BY 1, 2, 3
ORDER BY access_count DESC;

Use Case Summary

Use CaseColumns UsedRegulations / Requirements
PII column access auditingbase_objects_accessed → columnsGDPR / CCPA
Data lineage trackingbase_objects_accessed + objects_modifiedSOX / internal controls
Masking policy application auditpolicies_referencedSecurity policy compliance
Detecting unused tables / columnsbase_objects_accessed (identifying columns with no access)Cost optimization
Audit trail for data changesobjects_modified → columnsHIPAA / financial regulations

Best Practices

  • Use LATERAL FLATTEN to expand ARRAY columns into relational rows: base_objects_accessed and policies_referenced are ARRAY-typed, so expand them into rows with LATERAL FLATTEN before aggregating.
  • Automate daily audit reports with Tasks: Aggregate PII column access summaries on a schedule with a Task, and raise alerts when anomalies are detected.
  • Integrate with Tag-based Masking: Classify sensitive columns with tags and audit policy application via policies_referenced in ACCESS_HISTORY.
  • Effective even for tracking dropped objects: ACCOUNT_USAGE retains 365 days of history, so you can still track past access to tables that have already been dropped.

Check Your Understanding

Security & Governance

問題 1

For a GDPR compliance audit, you need to identify every user who accessed the EMAIL column of the CUSTOMERS table in the last 30 days, either directly or through a view. Which approach is most appropriate?

  1. Search INFORMATION_SCHEMA.QUERY_HISTORY for SELECT statements whose query_text matches LIKE '%EMAIL%'
  2. Flatten direct_objects_accessed in ACCESS_HISTORY and extract records where the column name is EMAIL
  3. Flatten base_objects_accessed in ACCESS_HISTORY and extract accesses to the EMAIL column of the CUSTOMERS table
  4. Use LOGIN_HISTORY to check the login history of users who accessed the CUSTOMERS table

正解: C

To identify users who accessed the EMAIL column of the actual base table — including access through views — you must use base_objects_accessed. direct_objects_accessed records the view name and is not suitable for column-level tracking on base tables. Option A's text search on query_text lacks accuracy because the column name can appear in other contexts. Option D's LOGIN_HISTORY only contains login information and no details about data access.

Frequently Asked Questions

What is the difference between base_objects_accessed and direct_objects_accessed in ACCESS_HISTORY?

direct_objects_accessed records the objects directly referenced in the query's FROM clause (views, Dynamic Tables, etc.). base_objects_accessed records the underlying base tables that those objects internally accessed. For example, when you read data from table T through view V, V appears in direct_objects_accessed while T appears in base_objects_accessed. For data lineage tracking and security audits, base_objects_accessed lets you see the actual data access behind the views.

Does ACCESS_HISTORY require Enterprise Edition or higher?

Yes, the ACCESS_HISTORY view is available only on Enterprise Edition or higher. Standard Edition cannot access this view. Column-level data access tracking is an advanced governance feature, so organizations with compliance requirements such as GDPR or SOX must choose Enterprise Edition or higher. Note that accessing the ACCESS_HISTORY view also requires IMPORTED PRIVILEGES on the SNOWFLAKE database.

Can ACCESS_HISTORY also track WRITE operations (INSERT/UPDATE/DELETE)?

Yes, ACCESS_HISTORY tracks WRITE operations in addition to READ operations. The objects_modified column records information about objects changed by INSERT / UPDATE / DELETE / MERGE and similar statements. This lets you trace who wrote data to which column of which table and when, providing an audit trail for data changes. The columns array contains the list of accessed column names, so column-level WRITE auditing is also possible.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Snowflake

Snowflake Certifications: All 11 Exams Explained (2026)

Every SnowPro certification — Associate, Core, Specialty, Ad...

Snowflake

Snowflake Exam Difficulty Ranking: All 11 Certs Compared (2026)

All 11 SnowPro exams ranked by difficulty with study-time es...

Snowflake

Snowflake Study Guide: Fastest Pass Route by Exam (2026)

How to pass SnowPro certifications efficiently — official ma...

Snowflake

SnowPro Core (COF-C03): Complete Exam Guide (2026)

Pass the SnowPro Core exam — six domains, scope, sample ques...

Snowflake

SnowPro Associate Platform (SOL-C01): Complete Guide (2026)

The entry-level SnowPro Associate exam — scope, weighting, s...

Browse all Snowflake articles (103)
© 2026 NicheeLab All rights reserved.