访问历史记录
本主题介绍了 Snowflake 中用户访问历史记录的概念。
概述
Snowflake 中的访问历史记录是指用户查询读取数据的时间,以及 SQL 语句执行从源数据对象到目标数据对象的数据写入操作(如 INSERT、UPDATE、DELETE 以及 COPY 命令变体)的时间。通过查询 ACCOUNT_USAGE 和 ORGANIZATION_USAGE 架构中的 ACCESS_HISTORY 视图,可以找到用户访问历史记录。这些视图中的记录有助于监管合规性审计,并提供有关常用和频繁访问的表和列的见解,因为用户(即查询操作员)、查询、表或视图、列和数据之间存在直接关联。
ACCESS_HISTORY 视图中的每一行均包含对应于每条 SQL 语句的一条记录。该记录包含以下类型的信息:
查询直接和间接访问的 源列,例如查询数据来自的基础表。
用户在查询结果中看到的 投影列,例如 SELECT 语句中指定的列。
用于确定查询结果但未投影的列,例如用于筛选结果的 WHERE 子句中的列。
例如:
CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
SELECT c1 as vc1,
c2 as vc2
FROM t
WHERE t.c3 > 0
;
C1 和 C2 列是视图直接访问的源列,这些列记录在 ACCESS_HISTORY 视图的 base_objects_accessed 列中。
C3 列用于筛选视图包含的行,这些行记录在 ACCESS_HISTORY 视图的 base_objects_accessed 列中。
VC1 和 VC2 列是用户在查询视图时看到的投影列,SELECT * FROM v1;,这些列记录在 ACCESS_HISTORY 视图的 direct_objects_accessed 列中。
同样的行为适用于 WHERE 子句中的键列。例如:
CREATE OR REPLACE VIEW join_v (vc1, vc2, c1) AS
SELECT
bt.c1 AS vc1,
bt.c2 AS vc2,
jt.c1
FROM bt, jt
WHERE bt.c3 = jt.c1;
创建视图需要两个不同的表:bt (基表)和 jt (联接表)。
基表中的 C1、C2 和 C3 列以及联接表中的 C1 列都记录在 ACCESS_HISTORY 视图的 base_objects_accessed 列中。
VC1、VC2 和 C1 列是用户在查询视图时看到的投影列,SELECT * FROM join_v;,这些列记录在 ACCESS_HISTORY 视图的 direct_objects_accessed 列中。
备注
Account Usage QUERY_HISTORY 视图中的记录并非始终会记录在 ACCESS_HISTORY 视图中。SQL 语句的结构决定 Snowflake 是否在 ACCESS_HISTORY 视图中记录一个条目。
有关 ACCESS_HISTORY 视图中 Snowflake 支持的读取和写入操作的详细信息,请参阅视图的 使用说明。
跟踪读取和写入操作
ACCOUNT_USAGE 和 ORGANIZATION_USAGE 架构中的 ACCESS_HISTORY 视图包括以下列:
query_id | query_start_time | user_name | direct_objects_accessed | base_objects_accessed | objects_modified | object_modified_by_ddl | policies_referenced | parent_query_id | root_query_id
读取操作通过前五列进行跟踪,而最后一列(即 objects_modified 列)指定涉及 Snowflake 列、表和暂存区的数据写入信息。
Snowflake 中的查询及数据库对象的创建方式决定了 Snowflake 在 direct_objects_accessed、base_objects_accessed 和 objects_modified 列中返回的信息。
同样,如果查询引用受行访问策略保护的对象或者受掩码策略保护的列,Snowflake 会在 policies_referenced 列中记录策略信息。
object_modified_by_ddl 列记录对数据库、架构、表、视图和列执行的 DDL 操作。这些操作还包括针对表或视图指定行访问策略、针对列指定掩码策略以及针对对象或列指定标签更新(例如设置标签、更改标签值)的语句。
parent_query_id 和 root_query_id 列记录对应于以下内容的查询 IDs:
有关列详细信息,请参阅 ACCESS_HISTORY 视图中的 Columns 部分。
读取
为了解读取查询以及 ACCESS_HISTORY 视图如何记录此信息,请考虑以下场景:
在本示例中,Snowflake 返回的是:
请注意, view_1 和 view_3 未包含在 direct_objects_accessed 和 base_objects_accessed 列中,因为查询中并未包含这些视图,它们也不是用作 view_2 中数据来源的基本对象。
写入
为了解写入操作以及 ACCESS_HISTORY 视图如何记录此信息,请考虑以下场景:
一个数据源: base_table
基于该数据源创建一个表(即 CTAS):
create table table_1 as select * from base_table;
在本示例中,Snowflake 返回的是:
base_objects_accessed 和 direct_objects_accessed 列中的 base_table,因为该表是直接访问的,并且是数据的来源。
objects_modified 列中的 table_1,包括在创建表时写入的列。
单个请求中的多个语句。
Snowflake 支持将多个语句作为单个请求同时执行。在访问历史记录中跟踪请求的方式取决于它是在 Snowsight 中执行,还是以编程方式执行。
使用 Snowsight 执行多个语句时,它一次运行一个查询,并返回上次执行的查询的 query_id。您可以在 ACCESS_HISTORY 视图中查找所有已执行的语句及其返回值。
Snowflake Python 连接器或 Snowflake SQL API 之类的功能会将多个 SQL 语句合并到一个请求中,并为所有语句返回一个 query_id。这个数字实际上是所有单个语句的父查询 ID。要返回包含请求的每个语句的 query_id,您必须使用 parent_query_id 查询 ACCESS_HISTORY。例如,如果请求返回 query_id = 6789,那么您可以通过执行以下命令返回各个语句的查询 ID:
SELECT query_id, parent_query_id, direct_objects_accessed
FROM snowflake.account_usage.access_history
WHERE parent_query_id = 6789;
优势
Snowflake 中的访问历史记录提供了与读取和写入操作相关的以下优势:
- 数据发现:
发现未使用的数据,以确定存档还是删除数据。
- 跟踪敏感数据的移动方式:
跟踪数据从外部云存储位置(例如 Amazon S3 桶)到目标 Snowflake 表的移动,反之亦然。
跟踪内部数据从一个 Snowflake 表到其他 Snowflake 表的移动。
跟踪敏感数据的移动后,应用策略(掩码 和 行访问 策略)以保护数据,更新 访问控制设置 以进一步规范对暂存区和表的访问,并设置 标签 以确保可跟踪包含敏感数据的暂存区、表和列,确保满足合规性要求。
- 数据验证:
报告、仪表板和数据可视化产品(如图表和图形)的准确性和完整性得到验证,因为数据可追溯到其原始来源。
数据专员还可在删除或更改给定表或视图之前通知用户。
- 合规性审计:
标识在表或暂存区上执行写入操作的 Snowflake 用户,以及执行写入操作的时间,以满足合规性法规,例如 GDPR (https://gdpr-info.eu/) 和 CCPA (https://oag.ca.gov/privacy/ccpa)。
- 增强整体数据治理:
ACCESS_HISTORY 视图提供了有关访问哪些数据、何时发生数据访问以及所访问的数据如何从数据源对象移动到数据目标对象的更统一的视图。
列沿袭
列沿袭(即列的访问历史记录)扩展了 Account Usage ACCESS_HISTORY 视图,以指定在写入操作中数据如何从源列流向目标列。Snowflake 会通过引用源列数据的所有后续表对象(例如 INSERT、MERGE、CTAS)跟踪源列中的数据,前提是沿袭链中的对象未被删除。Snowflake 通过增强 ACCESS_HISTORY 视图中的 objects_modified 列来讲列沿袭置于可访问状态。
列沿袭提供以下优势:
- 保护派生的对象:
数据专员可以轻松地为敏感源列添加 标签,而无需在创建派生对象(例如 CTAS)后执行额外的工作。随后,数据专员可以使用 行访问策略 保护包含敏感列的表,或者使用 掩码策略 或 基于标签的掩码策略 来保护敏感列本身。
- 敏感列的复制频率:
数据隐私官可快速确定包含敏感数据的列的对象数量(例如 1 个表、2 个视图)。通过了解包含敏感数据的列在表对象中出现的次数,数据隐私官即可证明他们满足监管合规标准(例如,满足《欧盟通用数据保护条例》(GDPR) 标准)的情况。
- 根本原因分析:
列沿袭提供了一种追溯数据直至其源的机制,这有助于查明由于数据质量差而导致的故障点,并减少故障排除过程中要分析的列数。
有关列沿袭的其他详细信息,请参阅:
掩码和行访问策略引用
POLICY_REFERENCED 列指定在表上设置了行访问策略或在列上设置了掩码策略的对象,包括受行访问策略或掩码策略保护的任何中间对象。Snowflake 记录在表或列上强制实施的策略。
考虑以下对象:
t1 » v1 » v2
其中:
t1 是基表。
v1 是基于基表构建的视图。
v2 是基于 v1 构建的视图。
如果用户查询 v2,则 policies_referenced 列会记录保护 v2 的行访问策略、保护 v2 中的列的各掩码策略,或同时记录这两种策略(如果适用)。此外,此列还会记录保护 t1 和 v1 的任何掩码或行访问策略。
这些记录有助于数据治理者了解如何访问其受策略保护的对象。
policies_referenced 列还为 ACCESS_HISTORY 视图提供了额外的优势:
账户级与企业级访问历史记录
管理员通过查询账户 ACCOUNT_USAGE 架构中的 ACCESS_HISTORY 视图,来监控账户级访问历史记录。没有与 ACCOUNT_USAGE.ACCESS_HISTORY 视图关联的任何额外成本。
ORGANIZATION_USAGE 架构中的 ACCESS_HISTORY 视图将组织中所有账户的访问历史记录收集到一个视图中,以提供组织级访问历史记录。此 ORGANIZATION_USAGE.ACCESS_HISTORY 视图只能在 组织账户 中找到。
ORGANIZATION_USAGE 架构中的组织级访问历史记录与 ACCOUNT_USAGE 架构中的访问历史记录在以下方面有所不同:
- 其他列:
组织账户中的 ORGANIZATION_USAGE.ACCESS_HISTORY 视图包含其他列,这些列提供与 组织列表 相关的见解。这些列可用于确定使用者的查询访问了哪些附加到组织列表的数据产品,以及这些数据产品是否受到策略(例如掩码策略)的保护。有关更多信息,请参阅 组织列表治理。
- 其他成本:
组织账户中的 ORGANIZATION_USAGE.ACCESS_HISTORY 视图是高级视图,会产生以下成本:
有关这些成本的更多信息,请参阅 与高级视图相关的成本。
受支持对象
使用下表来确定当 SQL 语句涉及特定类型的对象时,ACCESS_HISTORY 视图是否包含记录。SQL 语句包括:
数据操作语言 (DML) 语句。例如,用于向表中插入数据的语句。
数据查询语言 (DQL) 语句。例如,使用 SELECT 语句投影数据的语句。
数据定义语言 (DDL) 语句。例如,创建或更改 Snowflake 对象的语句。
查询 ACCESS_HISTORY 视图
以下各部分提供了视图的 ACCESS_HISTORY 查询示例。
请注意,某些查询示例会对 query_start_time 列进行筛选,以提高查询性能。提高性能的另一种选择是在较窄的时间范围内执行查询。
Access history examples
Read queries
以下小节详细介绍了如何查询 ACCESS_HISTORY 视图,以获取以下用例的读取操作:
返回用户访问历史记录
返回用户访问历史记录,按用户和查询开始时间排序,从最近的访问开始。
SELECT user_name
, query_id
, query_start_time
, direct_objects_accessed
, base_objects_accessed
FROM access_history
ORDER BY 1, 3 desc
;
促进合规性审计
以下示例有助于促进合规性审计:
添加 object_id 值,以确定在过去 30 天内谁访问过敏感表:
SELECT distinct user_name
FROM access_history
, lateral flatten(base_objects_accessed) f1
WHERE f1.value:"objectId"::int=<fill_in_object_id>
AND f1.value:"objectDomain"::string='Table'
AND query_start_time >= dateadd('day', -30, current_timestamp())
;
使用 32998411400350 的 object_id 值,确定过去 30 天内发生访问的时间:
SELECT query_id
, query_start_time
FROM access_history
, lateral flatten(base_objects_accessed) f1
WHERE f1.value:"objectId"::int=32998411400350
AND f1.value:"objectDomain"::string='Table'
AND query_start_time >= dateadd('day', -30, current_timestamp())
;
使用 32998411400350 的 object_id 值,确定过去 30 天内访问过哪些列:
SELECT distinct f4.value AS column_name
FROM access_history
, lateral flatten(base_objects_accessed) f1
, lateral flatten(f1.value) f2
, lateral flatten(f2.value) f3
, lateral flatten(f3.value) f4
WHERE f1.value:"objectId"::int=32998411400350
AND f1.value:"objectDomain"::string='Table'
AND f4.key='columnName'
;
Write operations
以下小节详细介绍了如何查询 ACCESS_HISTORY 视图,以获取以下用例的写入操作:
将数据从暂存区加载到表
将一组值从外部云存储中的数据文件加载到目标表的列中。
copy into table1(col1, col2)
from (select t.$1, t.$2 from @mystage1/data1.csv.gz);
direct_objects_accessed 和 base_objects_accessed 列指定已访问外部命名暂存区:
{
"objectDomain": STAGE
"objectName": "mystage1",
"objectId": 1,
"stageKind": "External Named"
}
objects_modified 列指定将数据写入表的两列之中:
{
"columns": [
{
"columnName": "col1",
"columnId": 1
},
{
"columnName": "col2",
"columnId": 2
}
],
"objectId": 1,
"objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
"objectDomain": TABLE
}
将数据从表卸载到暂存区
将一组值从 Snowflake 表卸载到云存储。
copy into @mystage1/data1.csv
from table1;
direct_objects_accessed 和 base_objects_accessed 列指定所访问的表列:
{
"objectDomain": TABLE
"objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
"objectId": 123,
"columns": [
{
"columnName": "col1",
"columnId": 1
},
{
"columnName": "col2",
"columnId": 2
}
]
}
objects_modified 列指定要将访问的数据写入到的暂存区:
{
"objectId": 1,
"objectName": "mystage1",
"objectDomain": STAGE,
"stageKind": "External Named"
}
使用 PUT 命令将本地文件上传到暂存区
将数据文件复制到内部(即 Snowflake)暂存区。
put file:///tmp/data/mydata.csv @my_int_stage;
direct_objects_accessed 和 base_objects_accessed 列指定所访问文件的本地路径:
{
"location": "file:///tmp/data/mydata.csv"
}
objects_modified 列指定要将访问的数据写入的暂存区:
{
"objectId": 1,
"objectName": "my_int_stage",
"objectDomain": STAGE,
"stageKind": "Internal Named"
}
使用 GET 命令将数据文件从暂存区检索到本地目录
将数据文件从内部暂存区检索到本地计算机上的目录。
get @%mytable file:///tmp/data/;
direct_objects_accessed 和 base_objects_accessed 列指定访问的暂存区和本地目录:
{
"objectDomain": Stage
"objectName": "mytable",
"objectId": 1,
"stageKind": "Table"
}
objects_modified 列指定将访问的数据写入到的目录:
{
"location": "file:///tmp/data/"
}
跟踪敏感的暂存区数据移动
跟踪敏感的暂存区数据,了解它按时间顺序执行的一系列查询。
执行以下查询。请注意,其中 5 个语句会访问暂存区数据。因此,在查询 ACCESS_HISTORY 视图以了解暂存区访问权限时,结果集应包含五行。
use test_db.test_schema;
create or replace table T1(content variant);
insert into T1(content) select parse_json('{"name": "A", "id":1}');
-- T1 -> T6
insert into T6 select * from T1;
-- S1 -> T1
copy into T1 from @S1;
-- T1 -> T2
create table T2 as select content:"name" as name, content:"id" as id from T1;
-- T1 -> S2
copy into @S2 from T1;
-- S1 -> T3
create or replace table T3(customer_info variant);
copy into T3 from @S1;
-- T1 -> T4
create or replace table T4(name string, id string, address string);
insert into T4(name, id) select content:"name", content:"id" from T1;
-- T6 -> T7
create table T7 as select * from T6;
其中:
T1、 T2 ...``T7`` 指定表的名称。
S1 和 S2 指定暂存区名称。
查询访问历史记录,以确定对 S1 暂存区的访问。
下表显示了 direct_objects_accessed、base_objects_accessed 和 objects_modified 列的数据。
请注意有关查询示例的以下方面:
使用 递归公共表表达式。
使用 JOIN 结构而非 USING 子句。
with access_history_flatten as (
select
r.value:"objectId" as source_id,
r.value:"objectName" as source_name,
r.value:"objectDomain" as source_domain,
w.value:"objectId" as target_id,
w.value:"objectName" as target_name,
w.value:"objectDomain" as target_domain,
c.value:"columnName" as target_column,
t.query_start_time as query_start_time
from
(select * from TEST_DB.ACCOUNT_USAGE.ACCESS_HISTORY) t,
lateral flatten(input => t.BASE_OBJECTS_ACCESSED) r,
lateral flatten(input => t.OBJECTS_MODIFIED) w,
lateral flatten(input => w.value:"columns", outer => true) c
),
sensitive_data_movements(path, target_id, target_name, target_domain, target_column, query_start_time)
as
-- Common Table Expression
(
-- Anchor Clause: Get the objects that access S1 directly
select
f.source_name || '-->' || f.target_name as path,
f.target_id,
f.target_name,
f.target_domain,
f.target_column,
f.query_start_time
from
access_history_flatten f
where
f.source_domain = 'Stage'
and f.source_name = 'TEST_DB.TEST_SCHEMA.S1'
and f.query_start_time >= dateadd(day, -30, date_trunc(day, current_date))
union all
-- Recursive Clause: Recursively get all the objects that access S1 indirectly
select sensitive_data_movements.path || '-->' || f.target_name as path, f.target_id, f.target_name, f.target_domain, f.target_column, f.query_start_time
from
access_history_flatten f
join sensitive_data_movements
on f.source_id = sensitive_data_movements.target_id
and f.source_domain = sensitive_data_movements.target_domain
and f.query_start_time >= sensitive_data_movements.query_start_time
)
select path, target_name, target_id, target_domain, array_agg(distinct target_column) as target_columns
from sensitive_data_movements
group by path, target_id, target_name, target_domain;
该查询生成以下与 S1 暂存区数据移动相关的结果集:
列沿袭
下面的示例查询 ACCESS_HISTORY 视图,并使用 FLATTEN 函数展平 objects_modified 列。
作为代表性示例,在 Snowflake 账户中执行以下 SQL 查询以生成下表,其中编号的注释指示了以下内容:
// 1
select
directSources.value: "objectId" as source_object_id,
directSources.value: "objectName" as source_object_name,
directSources.value: "columnName" as source_column_name,
'DIRECT' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "directSources",
outer => true
) directSources
union
// 2
select
baseSources.value: "objectId" as source_object_id,
baseSources.value: "objectName" as source_object_name,
baseSources.value: "columnName" as source_column_name,
'BASE' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "baseSources",
outer => true
) baseSources
;
返回:
Track row access policy references
在表、视图或物化视图上设置行访问策略且无重复时,为每个实例返回一行:
use role accountadmin;
select distinct
obj_policy.value:"policyName"::VARCHAR as policy_name
from snowflake.account_usage.access_history as ah
, lateral flatten(ah.policies_referenced) as obj
, lateral flatten(obj.value:"policies") as obj_policy
;
Track masking policy references
在掩码策略保护无重复的列时,为每个实例返回一行。请注意,policies_referenced 列指定的列掩码策略比表上的行访问策略深一级,因此必须执行额外的展平:
use role accountadmin;
select distinct
policies.value:"policyName"::VARCHAR as policy_name
from snowflake.account_usage.access_history as ah
, lateral flatten(ah.policies_referenced) as obj
, lateral flatten(obj.value:"columns") as columns
, lateral flatten(columns.value:"policies") as policies
;
Track the enforced policy in a query
返回给定时间范围内给定查询的策略更新时间 (POLICY_CHANGED_TIME) 和策略条件 (POLICY_BODY)。
在使用此查询之前,更新 WHERE 子句输入值:
where query_start_time > '2023-07-07' and
query_start_time < '2023-07-08' and
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777')
其中:
query_start_time > '2023-07-07'指定开始时间戳。
query_start_time < '2023-07-08'指定结束时间戳。
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777'指定 Account Usage ACCESS_HISTORY 视图中的查询标识符。
运行查询:
SELECT *
from(
select j1.*,j2.QUERY_START_TIME as POLICY_CHANGED_TIME, POLICY_BODY
from
(
select distinct t1.*,
t4.value:"policyId"::number as PID
from (select *
from SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
where query_start_time > '2023-07-07' and
query_start_time < '2023-07-08' and
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777') as t1, //
lateral flatten (input => t1.POLICIES_REFERENCED,OUTER => TRUE) t2,
lateral flatten (input => t2.value:"columns", OUTER => TRUE) t3,
lateral flatten (input => t3.value:"policies",OUTER => TRUE) t4
) as j1
left join
(
select OBJECT_MODIFIED_BY_DDL:"objectId"::number as PID,
QUERY_START_TIME,
OBJECT_MODIFIED_BY_DDL:"properties"."policyBody"."value" as POLICY_BODY
from SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
where OBJECT_MODIFIED_BY_DDL is not null and
(OBJECT_MODIFIED_BY_DDL:"objectDomain" ilike '%masking%' or OBJECT_MODIFIED_BY_DDL:"objectDomain" ilike '%row%')
) as j2
On j1.POLICIES_REFERENCED is not null and j1.pid = j2.pid and j1.QUERY_START_TIME>j2.QUERY_START_TIME) as j3
QUALIFY ROW_NUMBER() OVER (PARTITION BY query_id,pid ORDER BY policy_changed_time DESC) = 1;
UDFs
这些 UDF 示例显示了 Account Usage ACCESS_HISTORY 视图如何记录以下项:
调用 UDF
考虑以下 SQL UDF,它计算两个数字的乘积,并假设它存储在名为 mydb.udfs 的架构中:
CREATE FUNCTION MYDB.UDFS.GET_PRODUCT(num1 number, num2 number)
RETURNS number
AS
$$
NUM1 * NUM2
$$
;
直接 调用 get_product 会在 direct_objects_accessed 列中记录 UDF 详细信息:
[
{
"objectDomain": "FUNCTION",
"objectName": "MYDB.UDFS.GET_PRODUCT",
"objectId": "2",
"argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)",
"dataType": "NUMBER(38,0)"
}
]
此示例类似于 调用存储过程 (本主题内容)。
UDF 及 INSERT DML
考虑以下 INSERT 语句,它用于更新名为 mydb.tables.t1 的表中名为 1 和 2 的列:
insert into t1(product)
select get_product(c1, c2) from mydb.tables.t1;
ACCESS_HISTORY 视图将 get_product 函数记录在以下位置:
同样, t1 表记录在以下列中:
共享 UDFs
可以直接或间接引用共享 UDFs :
直接引用与 显式调用 UDF 相同(本主题内容),但会导致 UDF 记录到 base_objects_accessed 和 direct_objects_accessed 列中。
间接引用的一个示例是调用 UDF 以创建视图:
create view v as
select get_product(c1, c2) as vc from t;
base_objects_accessed 列记录 UDF 和表。
direct_objects_accessed 列记录视图。
Tracking objects modified by a DDL operation
使用 ALLOWED_VALUES 创建标签
创建标签:
create tag governance.tags.pii allowed_values 'sensitive','public';
列值:
{
"objectDomain": "TAG",
"objectName": "governance.tags.pii",
"objectId": "1",
"operationType": "CREATE",
"properties": {
"allowedValues": {
"sensitive": {
"subOperationType": "ADD"
},
"public": {
"subOperationType": "ADD"
}
}
}
}
备注
如果在创建标签时没有指定允许的值,则 properties 字段为空数组(即 {})。
使用标签和掩码策略创建表
创建具有列上的掩码策略、列上的标签和表上的标签的表:
create or replace table hr.data.user_info(
email string
with masking policy governance.policies.email_mask
with tag (governance.tags.pii = 'sensitive')
)
with tag (governance.tags.pii = 'sensitive');
列值:
{
"objectDomain": "TABLE",
"objectName": "hr.data.user_info",
"objectId": "1",
"operationType": "CREATE",
"properties": {
"tags": {
"governance.tags.pii": {
"subOperationType": "ADD",
"objectId": {
"value": "1"
},
"tagValue": {
"value": "sensitive"
}
}
},
"columns": {
"email": {
objectId: {
"value": 1
},
"subOperationType": "ADD",
"tags": {
"governance.tags.pii": {
"subOperationType": "ADD",
"objectId": {
"value": "1"
},
"tagValue": {
"value": "sensitive"
}
}
},
"maskingPolicies": {
"governance.policies.email_mask": {
"subOperationType": "ADD",
"objectId": {
"value": 2
}
}
}
}
}
}
}
在标签上设置掩码策略
在标签上设置掩码策略(即 基于标签的掩码):
alter tag governance.tags.pii set masking policy governance.policies.email_mask;
列值:
{
"objectDomain": "TAG",
"objectName": "governance.tags.pii",
"objectId": "1",
"operationType": "ALTER",
"properties": {
"maskingPolicies": {
"governance.policies.email_mask": {
"subOperationType": "ADD",
"objectId": {
"value": 2
}
}
}
}
}
交换表
将名为 t2 的表交换为名为 t3 的表:
alter table governance.tables.t2 swap with governance.tables.t3;
请注意视图中的两条不同记录。
记录 1:
{
"objectDomain": "Table",
"objectId": 0,
"objectName": "GOVERNANCE.TABLES.T2",
"operationType": "ALTER",
"properties": {
"swapTargetDomain": {
"value": "Table"
},
"swapTargetId": {
"value": 0
},
"swapTargetName": {
"value": "GOVERNANCE.TABLES.T3"
}
}
}
记录 2:
{
"objectDomain": "Table",
"objectId": 0,
"objectName": "GOVERNANCE.TABLES.T3",
"operationType": "ALTER",
"properties": {
"swapTargetDomain": {
"value": "Table"
},
"swapTargetId": {
"value": 0
},
"swapTargetName": {
"value": "GOVERNANCE.TABLES.T2"
}
}
}
删除掩码策略
删除掩码策略:
drop masking policy governance.policies.email_mask;
列值:
{
"objectDomain" : "MASKING_POLICY",
"objectName": "governance.policies.email_mask",
"objectId" : "1",
"operationType": "DROP",
"properties" : {}
}
备注
列值具有代表性,适用于对标签和行访问策略执行的 DROP 操作。
properties 字段是一个空数组,在执行 DROP 操作之前不会提供有关策略的任何信息。
跟踪列上的标签引用
查询 object_modified_by_ddl 列以监控标签在列上是如何设置的。
以表管理员的身份在列上设置标签、取消设置标签,然后使用不同的字符串值更新标签:
alter table hr.tables.empl_info
alter column email set tag governance.tags.test_tag = 'test';
alter table hr.tables.empl_info
alter column email unset tag governance.tags.test_tag;
alter table hr.tables.empl_info
alter column email set tag governance.tags.data_category = 'sensitive';
以数据工程师的身份更改标签值:
alter table hr.tables.empl_info
alter column email set tag governance.tags.data_category = 'public';
查询 ACCESS_HISTORY 视图以监视更改:
select
query_start_time,
user_name,
object_modified_by_ddl:"objectName"::string as table_name,
'EMAIL' as column_name,
tag_history.value:"subOperationType"::string as operation,
tag_history.key as tag_name,
nvl((tag_history.value:"tagValue"."value")::string, '') as value
from
TEST_DB.ACCOUNT_USAGE.access_history ah,
lateral flatten(input => ah.OBJECT_MODIFIED_BY_DDL:"properties"."columns"."EMAIL"."tags") tag_history
where true
and object_modified_by_ddl:"objectDomain" = 'Table'
and object_modified_by_ddl:"objectName" = 'TEST_DB.TEST_SH.T'
order by query_start_time asc;
返回:
+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
| QUERY_START_TIME | USER_NAME | TABLE_NAME | COLUMN_NAME | OPERATION | TAG_NAME | VALUE |
+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
| Mon, Feb. 14, 2023 12:01:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.TEST_TAG | test |
| Mon, Feb. 14, 2023 12:02:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | DROP | GOVERNANCE.TAGS.TEST_TAG | |
| Mon, Feb. 14, 2023 12:03:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.DATA_CATEGORY | sensitive |
| Mon, Feb. 14, 2023 12:04:01 -0600 | DATA_ENGINEER | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.DATA_CATEGORY | public |
+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
Call a stored procedure
考虑以下存储过程,并假定它存储在名为 mydb.procedures 的架构中:
create or replace procedure get_id_value(name string)
returns string not null
language javascript
as
$$
var my_sql_command = "select id from A where name = '" + NAME + "'";
var statement = snowflake.createStatement( {sqlText: my_sql_command} );
var result = statement.execute();
result.next();
return result.getColumnValue(1);
$$
;
直接 调用 my_procedure 会导致在 direct_objects_accessed 和 base_objects_accessed 列中记录过程详细信息,如下所示:
[
{
"objectDomain": "PROCEDURE",
"objectName": "MYDB.PROCEDURES.GET_ID_VALUE",
"argumentSignature": "(NAME STRING)",
"dataType": "STRING"
}
]
此示例类似于 调用 UDF (本主题内容)。
Ancestor queries with stored procedures
您可以使用 parent_query_id 和 root_query_id 列了解存储过程调用之间的关系。
假设您有三个不同的存储过程语句并按以下顺序运行这些语句:
CREATE OR REPLACE PROCEDURE myproc_child()
RETURNS INTEGER
LANGUAGE SQL
AS
$$
BEGIN
SELECT * FROM mydb.mysch.mytable;
RETURN 1;
END
$$;
CREATE OR REPLACE PROCEDURE myproc_parent()
RETURNS INTEGER
LANGUAGE SQL
AS
$$
BEGIN
CALL myproc_child();
RETURN 1;
END
$$;
CALL myproc_parent();
对 ACCESS_HISTORY 视图的查询将按如下方式记录信息:
SELECT
query_id,
parent_query_id,
root_query_id,
direct_objects_accessed
FROM
SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY;
+----------+-----------------+---------------+-----------------------------------+
| QUERY_ID | PARENT_QUERY_ID | ROOT_QUERY_ID | DIRECT_OBJECTS_ACCESSED |
+----------+-----------------+---------------+-----------------------------------+
| 1 | NULL | NULL | [{"objectName": "myproc_parent"}] |
| 2 | 1 | 1 | [{"objectName": "myproc_child"}] |
| 3 | 2 | 1 | [{"objectName": "mytable"}] |
+----------+-----------------+---------------+-----------------------------------+
第一行对应的是调用名为 myproc_parent 的第二个存储过程,如 direct_objects_accessed 列所示。
parent_query_id 和 root_query_id 列返回 NULL,因为您直接调用了此存储过程。
第二行对应的是调用名为 myproc_child 的第一个存储过程的查询,如 direct_objects_accessed column 所示。
parent_query_id 和 root_query_id 列返回相同的查询 ID,因为调用 myproc_child 的查询是由调用 :code:`myproc_parent`(直接调用)的查询启动的。
第三行对应的是访问 myproc_child 过程中名为 mytable 的表的查询,如 direct_objects_accessed 列中所示。
parent_query_id 列返回访问 mytable 的查询的查询 ID,对应于调用 myproc_child。该存储过程由调用 myproc_parent 启的查询动,该查询显示在 root_query_id 列中。
Sequence
考虑以下创建序列的 SQL 语句:
CREATE SEQUENCE SEQ
START = 2
INCREMENT = 7
COMMENT = 'Comment on sequence';
创建此序列会在访问历史记录中生成以下条目:
{
"objectDomain": "Sequence",
"objectId": 1,
"objectName": "TEST_DB.TEST_SCHEMA.SEQ",
"operationType": "CREATE",
"properties": {
"start": {
"value": "2"
},
"increment": {
"value": "7"
},
"comment": {
"value": "Comment on Sequence"
}
}
}
Join
查询中的联接操作在访问历史记录中会以 joinObject 的形式显示在 :code:direct_accessed_objects 列中。joinObject 不会出现在其他列中,因为访问历史记录只跟踪查询中明确提及的联接。
例如,考虑以下联接表 t1 和表 t2 的查询:
CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
SELECT
t1.c1 AS vc1,
t2.c2 AS vc2
FROM t1 LEFT OUTER JOIN t2
ON t1.c2 = t2.c1;
执行此查询会导致 direct_accessed_objects 列中的 t1 对象出现以下内容:
{
"columns": [
{
"columnId": 0,
"columnName": "C1"
},
{
"columnId": 0,
"columnName": "C2"
}
],
"joinObjects": [
{
"joinType": "LEFT_OUTER_JOIN",
"node": {
"objectDomain": "Table",
"objectId": 0,
"objectName": "DB1.SCH.T2"
}
}
],
"objectDomain": "Table",
"objectId": 0,
"objectName": "DB1.SCH.T1"
}
备注
在这个例子中,访问历史记录中不会包含 ``t2``对象的 ``joinObject`` 信息,因为该信息已由表 ``t1`` 的 ``joinObject`` 提供,属于冗余数据。