创建自定义 Clean Room 模板¶
关于 Clean Room 模板¶
Clean Room 模板用 JinjaSQL (https://github.com/sripathikrishnan/jinjasql) 编写。JinjaSQL 是 Jinja 模板语言的扩展,生成 SQL 查询作为输出。JinjaSQL 支持逻辑语句和运行时变量解析,让用户在运行时自定义查询。变量通常在模板中使用,以允许用户指定要在查询中使用的表名称、表列和自定义值。
Snowflake 为常见用例提供了一系列预先设计的模板。这些股票模板只能在 Web 应用程序中使用。但是,提供商和使用者都可以为 Clean Room 创建自定义模板。自定义模板只能在代码中创建,但可以在代码中运行或通过 Web 应用程序运行。
模板一般有两种类型:
分析模板,其计算结果为一条 SELECT 语句(或一组 SELECT 运算)。
激活模板,其计算结果为嵌套在 CREATE TABLE 语句中的 SELECT 语句,并返回表名称。此模板生成的数据将导出到使用者或提供商的 Snowflake 账户或第三方,具体取决于 Clean Room 的配置方式。激活模板与具有 一些额外要求 的分析模板非常相似。
在 Clean Room UI 中,分析模板可以与激活模板相关联,以使调用方能够进行分析,然后将数据发送给自己或第三方。激活模板无需解析为与关联分析模板相同的查询。
创建和运行自定义模板¶
在默认设置的 Clean Room 中,提供商向 Clean Room 添加模板,使用者可以选择、配置和运行它:
**提供者**设计一个自定义模板,通过调用
provider.add_custom_sql_template
将其添加到 Clean Room。**使用者**调用
consumer.run_analysis
运行提供商的模板,传递模板所需的任何变量的值。
此流程不需要另一方的许可,但使用者须获得提供商邀请才可进入 Clean Room。此过程存在变体,例如使用者提供的模板和提供商运行的模板,在其他部分有所涵盖。
数据保护¶
模板只能访问提供商和使用者链接到 Clean Room 的数据集。
提供商和使用者都能够对其数据设置联接、列和激活策略,以确保哪些列可以联接或投射到激活的结果中。
快速示例¶
下面是一个简单的 SQL 示例,它通过电子邮件加入提供商和使用者表,并显示每个城市的重叠计数:
SELECT COUNT(*), city FROM consumer_table
INNER JOIN provider_table
ON consumer_table.hashed_email = provider_table.hashed_email
GROUP BY city;
下面是该查询的模板,允许调用方选择/分组以及联接列和表:
SELECT COUNT(*), IDENTIFIER({{ group_by_col | column_policy }})
FROM IDENTIFIER({{ my_table[0] }}) AS C
INNER JOIN IDENTIFIER({{ source_table[0] }}) AS P
ON IDENTIFIER({{ consumer_join_col | join_policy }}) = IDENTIFIER({{ provider_join_col | join_policy }})
GROUP BY IDENTIFIER({{ group_by_col | column_policy }});
模板备注:
{{双括号对}} 内的值是自定义变量。
group_by_col
、my_table
、source_table
、consumer_join_col
、provider_join_col
和group_by_col
都是调用方填充的自定义变量。source_table
和my_table
是由调用方填充的 Snowflake 定义的字符串数组变量。数组成员是链接到 Clean Room 的提供商表和使用者表的完全限定名称。调用方指定每个数组中应该包括哪些表。在模板中,提供商表别名须为
P
,使用者表别名须为C
。如果您有多个表,则可以将它们索引为P1
、P2
、C1
、C2
等。所有列名和表名都需要 IDENTIFIER,因为 {{双括号}} 中的变量计算为字符串字面量,而其并非有效标识符。
JinjaSQL 筛选器 可以应用于变量。Snowflake 实现了自定义筛选器
join_policy
和column_policy
,它们分别验证列是否符合 Clean Room 中的联接或列策略,如果不符合则查询失败。筛选器以{{ column_name | filter_name }}
格式应用于列名称。
所有这些要点将在后面详细讨论。
下面介绍使用者如何在代码中运行此模板。请注意如何通过模板中声明的表别名来限定列名。
CALL SAMOOHA_BY_SNOWFLAKE_LOCAL_DB.CONSUMER.RUN_ANALYSIS(
$cleanroom_name,
$template_name,
['my_db.my_sch.consumer_table], -- Populates the my_table variable
['my_db.my_sch.provider_table'], -- Populates the source_table variable
OBJECT_CONSTRUCT( -- Populates custom named variables
'consumer_join_col','c.age_band',
'provider_join_col','p.age_band',
'group_by_col','p.device_type'
)
);
为了在 Web 应用程序中使用此模板,提供商必须 为模板创建自定义 UI 表单。UI 表单具有与模板变量名称对应的命名表单元素,表单中提供的值传递到模板中。
小技巧
在早期开发中,您可以使用 consumer.get_sql_jinja 过程来查看模板渲染时的样子。但请注意,此过程不支持 Clean Room 筛选器扩展,例如 join_policy
,因此,在发送给该过程的任何模版中,您需要省略这些筛选器。
自定义模板语法¶
Snowflake Data Clean Room 支持 V3 JinjaSQL,并提供如前所述的一些扩展。
本部分包括以下主题:
模板命名规则¶
创建模板时,名称必须全部为小写字母、数字、空格或下划线。激活模板(使用者运行的提供商激活除外)的名称必须以“activation”开头。调用 provider.add_custom_sql_template
或 consumer.create_template_request
时分配模板名称。
有效名称示例:
my_template
activation_template_1
无效名称示例:
my template
- 不允许存在空格My_Template
- 只允许使用小写模板
模板变量¶
模板调用方可以将值传递给模板变量。JinjaSQL 语法允许对 {{ double_brackets }} 内的任何变量名进行变量绑定,但 Snowflake 保留了一些变量名,不应替换,如下所述。
小心
所有变量,无论是 Snowflake 定义的还是自定义的,都是由用户填充的,因此应谨慎对待。Snowflake Data Clean Room 模板必须解析为单个 SELECT 语句,但您仍应记住所有变量均由调用方传入。
Snowflake 定义的变量¶
所有 Clean Room 模板都可以访问由 Snowflake 定义但由调用方传入的以下全局变量:
source_table
:一个从零开始的字符串数组,表示 Clean Room 中可由模板使用的提供商关联表和视图。表名是完全限定的,例如:
my_db.my_sch.provider_customers
例如:
SELECT col1 FROM IDENTIFIER({{ source_table[0] }}) AS p;
my_table
:一个从零开始的字符串数组,表示 Clean Room 中可由模板使用的使用者表和视图。表名是完全限定的,例如:
my_db.my_sch.consumer_customers
例如:
SELECT col1 FROM IDENTIFIER({{ my_table[0] }}) AS c;
privacy
:一组与用户和模板关联的隐私相关值。查看可用子字段列表。可以为用户 显式设置 这些值,但如果未设置这些值,您的模板应始终提供默认值。直接在模板中访问子字段,例如
privacy.threshold
。示例: 以下是模板的示例片段,该模板使用
threshold_value
在聚合子句中强制执行最小组规模。SELECT IFF(a.overlap > ( {{ privacy.threshold_value | default(2) | sqlsafe }} ), a.overlap,1 ) AS overlap, c.total_count AS total_count ...
备注
有两个旧版 Clean Room 全局变量:measure_columns
和 dimensions
。尽管已不再建议使用,但由于其仍存在于部分旧版模板和文档中,因此为避免命名冲突,请勿将其用作表或列的别名。
自定义变量¶
模板创建者可以在模板中包含可由调用方填充的任意变量。除了 Snowflake 定义的变量或表别名之外,这些变量可以具有任何符合 Jinja 规范的任意名称。如果您希望模板在 Web 应用程序中可用,还必须为 Web 应用程序用户提供 UI 表单。对于 API 用户,您应为必填变量和可选变量提供完善的文档。
自定义变量可由模板访问,如这里所示的自定义变量 max_income
:
SELECT income FROM my_db.my_sch.customers WHERE income < {{ max_income }};
用户可以通过两种不同方式将变量传递给模板:
在 Web 应用程序中, 通过模板开发者创建的 UI 表单选择或提供值。此 UI 表单包含表单元素,用户可以在其中为您的模板提供值。表单元素的名称为变量的名称。模板仅使用表单元素的名称来访问值。使用 provider.add_ui_form_customizations 创建 UI 表单。
在代码中, 使用者调用 consumer.run_analysis 并将表名作为实参数组传入,将自定义变量作为名称-值对传入
analysis_arguments
实参。
备注
如果需要访问上传到 Clean Room 的任何自定义 Python 代码中的用户提供的值,必须通过 Python 函数实参 将变量值显式传递 给代码;不能使用 {{jinja variable binding syntax}}
直接在 Python 代码中访问模板变量。
正确解析变量¶
传入模板的字符串值解析为最终模板中的字符串字面量。如果绑定变量处理不当,可能会导致 SQL 解析或逻辑错误:
SELECT {{ my_col }} FROM P;
解析为SELECT 'my_col' from P;
,仅返回字符串“my_col”,这可能不是您想要的。SELECT age FROM {{ my_table[0] }} AS P;
解析为SELECT age FROM 'somedb.somesch.my_table' AS P;
,这会导致分析错误,因为表必须为标识符,而非字面量字符串。SELECT age FROM IDENTIFIER({{ my_table[0] }}) AS P {{ where_clause }};
传入“WHERE age < 50”后计算为SELECT age FROM mytable AS P 'WHERE age < 50';
,这是由字面量字符串 WHERE 子句导致的解析错误。
因此,在适当情况下,您必须解析变量。下面介绍如何在模板中正确解析变量:
- 表和列名称
指定表或列名称的变量必须通过以下两种方式之一转换为模板中的标识符:
IDENTIFIER:例如:
SELECT IDENTIFIER({{ my_column }}) FROM P;
sqlsafe (https://github.com/sripathikrishnan/jinjasql?tab=readme-ov-file#sql-safe-strings):此 JinjaSQL 筛选器将标识符字符串解析为 SQL 文字。与前一个项目符号等效的语句是
SELECT {{ my_column | sqlsafe }} FROM P;
您的特定用法决定了何时使用 IDENTIFIER 或
sqlsafe
。例如,c.{{ my_column | sqlsafe }}
不易使用 IDENTIFIER 重写。- 动态 SQL
当有字符串变量用作字面量 SQL 时,例如 WHERE 子句,请使用模板中的
sqlsafe
筛选器。例如:SELECT age FROM IDENTIFIER({{ my_table[0] }}) AS C WHERE {{ where_clause }};
如果用户将“age < 50”传递给
where_clause
,查询将解析为SELECT age FROM sometable AS C WHERE 'age < 50';
,由于字面量字符串 WHERE 条件导致其为无效 SQL。在这种情况下,您应该使用sqlsafe
筛选器:SELECT age FROM IDENTIFIER( {{ my_table[0] }} ) as C {{ where_clause | sqlsafe }};
必填表别名¶
在查询顶级,所有表或子查询必须使用别名 P``(用于提供商表)或 ``C``(用于使用者表),以便 Snowflake 在查询中正确验证联接和列策略。必须对照联接或列策略验证的任何列都属于别名为 ``P
或 C
的表。(指定 P
或 C
告诉后端是分别针对提供商还是使用者策略验证列。)
如果在查询中使用多个提供商表或使用者表,请在第一个表别名后为每个表别名添加一个从 1 开始的连续后缀。因此,P
、P1
、P2
等表示第一、第二和第三个提供商表,C
、C1
、C2
等表示第一、第二和第三个使用者表。P
或 C
索引应该是连续的且无间隙(即创建别名 P
、P1
和 P2
,而非 P
、P2
和 P4
)。
示例
SELECT col1 FROM IDENTIFIER({{ source_table[0] }}) AS P;
模板筛选器¶
Snowflake 支持所有 标准 Jinja 筛选器 (https://jinja.palletsprojects.com/en/stable/templates/#builtin-filters) 和大多数标准 JinjaSQL 筛选器 (https://github.com/search?q=repo%3Asripathikrishnan%2Fjinjasql+self.env.filters+path%3Ajinjasql%2Fcore.py&type=code&path+jinjasql%2Fcore.py=),以及一些扩展:
join_policy
:验证表的联接策略是否允许列,如果不允许则失败。column_policy
:验证模板的列策略是否允许该列(允许投射)。activation_policy
:验证 Clean Room 的激活策略(provider.set_activation_policy
或consumer.set_activation_policy
)是否允许筛选列。join_and_column_policy
:验证联接、激活或列策略是否允许列。用于在 Clean Room 中提供更大的灵活性,允许合作者在不更改模板的情况下更新联接和列策略。Snowflake 模板 不支持
identifier
JinjaSQL 筛选器。
JinjaSQL 筛选器从左到右进行分析:
{{ my_col | column_policy }}
正确{{ my_col | sqlsafe | column_policy }}
正确{{ column_policy | my_col }}
不正确{{ my_col | column_policy | sqlsafe }}
不正确
强制执行 Clean Room 策略¶
Clean Room 不会自动根据模板中使用的列检查 Clean Room 策略。如果您要对某列强制执行策略,则必须对模板中的该列应用相应的 策略筛选器。例如:
JOIN IDENTIFIER({{ source_table[0] }}) AS p
ON {{ c_join_col | sqlsafe | join_policy }} = {{ p_join_col | sqlsafe }}
这将针对传递到 c_join_col
的列测试联接策略,但不会针对 p_join_col
测试。
请注意,测试策略时,列名不能含糊不清,就像任何其他 SQL 用法一样。因此,如果两个表中有同名的列,则必须限定列名才能针对该列测试策略。
运行自定义 Python 代码¶
模板可以运行上传到 Clean Room的 Python 代码。模板可以调用 Python 函数,该函数接受一行数据中的值,并返回值以在查询中使用或投射。
当 提供商 将自定义的 Python 代码上传到 Clean Room 时,模板会使用语法
cleanroom.function_name
调用 Python 函数。更多详细信息请点击此处。当 使用者 将自定义 Python 代码上传到 Clean Room 时,模板会直接使用传递给
consumer.generate_python_request_template
的裸function_name
值调用该函数(不会像提供商代码那样限定在Clean Room
)。更多详细信息请点击此处。
提供商代码示例:
-- Provider uploads a Python function that takes two numbers and returns the sum.
call samooha_by_snowflake_local_db.provider.load_python_into_cleanroom(
$cleanroom_name,
'simple_addition', -- Function name to use in the template
['someval integer', 'added_val integer'], -- Arguments
[], -- No packages needed
'integer', -- Return type
'main', -- Handler for function name
$$
def main(input, added_val):
return input + int(added_val)
$$
);
-- Template passes value from each row to the function, along with a
-- caller-supplied argument named 'increment'
call samooha_by_snowflake_local_db.provider.add_custom_sql_template(
$cleanroom_name,
'simple_python_example',
$$
SELECT val, cleanroom.simple_addition(val, {{ increment | sqlsafe }})
FROM VALUES (5),(8),(12),(39) AS P(val);
$$
);
安全注意事项¶
模板必须计算到由 Clean Room 原生应用程序执行的单个 SELECT 查询。模板不以当前用户的身份执行。
用户不能直接访问 Clean Room 内的任何数据;所有访问都是通过模板结果,借助原生应用程序进行的。
在查询中使用列时应用策略筛选器, 即使在模板中明确定义了列名,或列或表是由您提供的情况下也是如此。您以后可能会更改联接或列策略,或更改列,而忘记更新模板。对于用户提供的任何列,应用 join_policy
、column_policy
、join_and_column_policy
或 activation_policy
筛选器。
激活模板¶
模板还可用于将查询结果保存到 Clean Room 外的表中;这称为 激活。目前,自定义模板支持的激活形式仅限提供商激活和使用者激活(分别将结果存储到提供商或使用者的 Snowflake 账户)。了解如何实施激活。
激活模板是具有以下附加要求的分析模板:
激活模板是计算结果为 SQL 脚本块的 JinjaSQL 语句,可以是简单的 SELECT 语句,这点与分析模板不同。
激活 模板 的名称必须以字符串
activation
开头(使用者运行的提供商激活模板除外)。例如:activation_my_template
。激活模板必须创建一个 表,其名称取决于启用的激活类型:
提供商运行的提供商激活: 生成的表名必须是
cleanroom.temp_result_data
。所有其他激活类型: 生成的表名必须以
cleanroom.activation_data_
为前缀,例如:cleanroom.activation_data_cross_activation_results
。在 Clean Room 中,表名应该是唯一的。
此生成的表是一个中间表;您不应该尝试直接访问它。
脚本块应以一条 RETURN 语句结尾,该语句返回生成的表的名称,减去任何
cleanroom.
或cleanroom.activation_data_
前缀。任何被激活的列都必须在已链接数据的提供商或使用者的 激活策略 中列出,并且应对其应用
activation_policy
筛选器。请注意,列既可以是激活列,也可以是联接列。如果要在 Clean Room UI 中运行模板,您应 提供一个 Web 表单,其中包含
activation_template_name
和enabled_activations
字段。在 UI 中使用的模板必须同时具有分析模板和关联的激活模板。由于表正在生成,因此所有计算的列都必须显式使用别名,而不是推断的名称。即:
SELECT COUNT(*), P.status from T AS P;
FAILS,因为 COUNT 列名是推断出来的。SELECT COUNT(*) AS COUNT_OF_ITEMS, P.status from T AS P;
SUCCEEDS,因为它为 COUNT 列显式设置了别名。
以下是两个基本激活模板示例。一个用于提供商运行的服务器激活,另一个用于其他激活类型。它们的不同之处在于两个突出显示的行,这两行包含结果表名。
-- These are the required table name strings.
BEGIN
CREATE OR REPLACE TABLE cleanroom.temp_result_data AS
SELECT COUNT(c.status) AS ITEM_COUNT, c.status, c.age_band
FROM IDENTIFIER({{ my_table[0] }}) AS c
JOIN IDENTIFIER({{ source_table[0] }}) AS p
ON {{ c_join_col | sqlsafe | activation_policy }} = {{ p_join_col | sqlsafe | activation_policy }}
GROUP BY c.status, c.age_band
ORDER BY c.age_band;
RETURN 'temp_result_data';
END;
-- analysis_results can be whatever name you want.
BEGIN
CREATE OR REPLACE TABLE cleanroom.activation_data_analysis_results AS
SELECT COUNT(c.status) AS ITEM_COUNT, c.status, c.age_band
FROM IDENTIFIER({{ my_table[0] }}) AS c
JOIN IDENTIFIER({{ source_table[0] }}) AS p
ON {{ c_join_col | sqlsafe | activation_policy }} = {{ p_join_col | sqlsafe | activation_policy }}
GROUP BY c.status, c.age_band
ORDER BY c.age_band;
RETURN 'analysis_results';
END;
后续步骤¶
掌握模板系统后,请阅读使用模板类型实现 Clean Room 的详细说明:
更多信息¶
Jinja 文档 (https://jinja.palletsprojects.com/en/stable/)
JinjaSQL 文档 (https://github.com/sripathikrishnan/jinjasql)