查询受差分隐私保护的数据¶
这个主题帮助分析师对受差分隐私保护的数据(即,受隐私保护的表和视图)运行查询,并理解和调整查询返回的结果。
要对受隐私保护的表执行查询,用户必须拥有该表的 SELECT 权限。
限制
差分隐私支持一部分 Snowflake 数据类型、运算符、查询语法和函数。有关您可以在查询中使用的受支持 SQL 的列表,请参阅 差分隐私 SQL 参考。
对隐私保护表的查询需要更长时间,因为 Snowflake 必须运行额外的计算来确定要添加多少噪声。对于基本查询,此延迟至少为 7 秒。复杂的查询(例如下面的查询)可能需要更长的时间:
包含多个联接和子查询的查询。
输出多个结果行的查询,例如,当使用 GROUP BY 子句时,结果会产生数百或数千个组。
在受差分隐私保护的表中,最外层 SELECT 子句中的字段只能应用聚合、GROUP BY 或 DP_INTERVAL_LOW/HIGH。不允许其他操作(例如数学运算和并置)。示例:
SELECT key, COUNT(*) AS 'c', DP_INTERVAL_LOW('c') FROM T GROUP BY key
成功: 未针对
key
、COUNT(*)
或c
执行不支持的操作。SELECT key, 1 + COUNT(*) AS 'c', DP_INTERVAL_LOW('c') FROM T GROUP BY key
失败: 针对最外层 SELECT 子句中的字段指定了
1 + COUNT(*)
。SELECT key, AVG(1 + x) AS 'avg', DP_INTERVAL_LO('avg') FROM T GROUP BY key
成功: 在外层 SELECT 子句中的
1 + x
之后出现 ``AVG``(一个允许的聚合)。SELECT key, COUNT(x) AS 'c', DP_INTERVAL_LOW('c') FROM (SELECT key, 1 + income AS x FROM table) GROUP BY key
成功: 在嵌套的 SELECT 子句应用了
1 + income
,这是允许的。
查询基础知识¶
本节讨论针对受隐私保护的表运行时查询时,查询的基本组成部分。它包括:
聚合数据¶
所有针对受隐私保护的表的查询必须聚合结果,而不是检索单个记录。并不是查询的每个部分都需要使用聚合函数,只要聚合最终结果即可。
除 COUNT 函数外,查询不能对列进行汇总,除非该列有 隐私域。
有关支持的聚合列表,请参阅 聚合函数。
使用联接¶
以下各节提供了在差分隐私查询中使用联接的指南:
要了解联接两个受隐私保护的表对隐私域的影响,请参阅 隐私域与联接。
联接运算符¶
每个联接必须是使用单个运算符的相等联接。例如,t1.c1 == t2.c1
受支持的,但 col1 > col2
和 col1 + 10 = col2
不受支持。不支持无条件联接。
联接必须使用 JOIN 运算符。不支持使用 WHERE 语法进行联接。有关联接语法的更多信息,请参阅 实施联接。
支持的联接¶
差分隐私查询中的联接必须是以下之一:
INNER
{ LEFT | RIGHT | FULL } OUTER
NATURAL
联接的两侧必须具有相同的查询模式。例如,支持以下联接:
两侧都是标识符
SELECT COUNT(*) FROM t1 INNER JOIN t2 ON t1.a=t2.a;
两侧都是子查询
SELECT COUNT(*) FROM (SELECT a, SUM(b) FROM t1 GROUP BY a) AS g1 INNER JOIN (SELECT * FROM t2) AS g2 ON g1.a=g2.a;
目前不支持将标识符与子查询联接。
有关与联接相关的受支持查询语法的信息,请参阅 查询语法。
在联接中使用实体键¶
在处理受 实体级隐私 保护的表时,您可以通过将实体键列包括为联接键的一部分来尽量减少噪声,特别是当它不会在语义上更改查询时。
例如,考虑以下以客户为实体的表格:
表
描述
customers
客户目录,其中每行都是一个客户,并且有
customer_id
。
transactions
客户事务,每个客户可以有多个事务。
transaction_lines
在事务中购买的唯一项目。单个事务中可以有多个行。
如果他们遵循最佳实践,数据提供者已经将数据结构化,使得这些表中的每个表都有实体键 customer_id
。对于这个数据架构,每个交易行只能属于一个交易,而每个交易只能属于一个客户。从数据本身来看,这种关系并不明显,因此,如果没有额外的信息,为差分隐私添加的噪声量将高于所需的量。
通过包括实体键 customer_id
作为联接键的一部分,即使它是多余的,您可以将噪声量减至最少。例如,将表 transactions
与 transaction_lines
联接通常只需要联接键 transaction_id
。然而,在 transaction_id
和 customer_id
上进行联接将导致更少的噪音。
数据类型和隐私域¶
当联接两个表时,任一侧的联接键列的数据类型必须相同。对于差分隐私,列的数据类型包括它是否具有 隐私域。
例如,如果您有一个受隐私保护的表 transactions
和一个不受保护的表 product_lookup
,并且您想在 product_id
上联接它们,则两个表中的 product_id
列必须具有相同的数据类型(例如,字符串)并且必须各自具有隐私域。
为了满足这一要求,分析师的管理员可能需要定义一个隐私域,就像数据提供者定义它们一样。有关如何为表设置隐私域的信息,请参阅 设置隐私域。
唯一性要求¶
联接可能会重复数据行,这可能导致查询结果中添加的噪声量变得无绑定。为了确保受隐私保护的数据在联接中不会重复,受隐私保护的表的联接键(即联接表依据的列)必须在另一个表中仅匹配一条记录。这意味着在与受隐私保护的表联接时,对面的联接键必须删除重复项。
重要
联接的唯一性要求并不总是适用于针对受 实体级隐私 保护的表的查询。对于实体级隐私,必须在聚合之前针对实体键对查询执行删除重复项的操作。只要在联接之后但在聚合之前完成此操作,联接就不需要针对已去除重复项的数据进行。有关满足这些要求的更多信息,请参阅 查询受实体级隐私保护的数据。
为了满足联接的唯一性要求,查询可以针对联接列的子集使用 GROUP BY 将重复的行分组到一个结果中。
例如,假设 patients
表受差分隐私保护,而 geo_lookup
表不受保护。分析师希望在 zip_code
上联接这两个表,以便他们可以根据 State
对 patients
表进行过滤。为了确保受隐私保护的 patients
表中的记录不重复,查询必须针对联接键对 zip_code
表执行删除重复项操作。此操作必须明确完成,即 使 geo_lookup
表在 zip_code
上已经是唯一的。这可确保 Snowflake 可以正确地考虑隐私。
SELECT COUNT(*)
FROM patients
LEFT JOIN (SELECT zip_code, ANY_VALUE(state) AS residence_state
FROM geo_lookup
GROUP BY zip_code)
USING zip_code
WHERE birth_state = residence_state;
df_patients = session.table("patients")
df_geo = session.table("geo_lookup")
df_geo_deduped = df_geo.group_by("zip_code").agg(f.any_value("state").as_("state"))
df_patients.join(df_geo_deduped, on="zip_code", join_type="left")\
.where(f.col("birth_state") == f.col("residence_state")).select(f.count("*"))
查询受实体级隐私保护的数据¶
在配置差分隐私时,大多数数据提供者使用实体键来实现 实体级隐私。当一个表受实体级隐私保护时,如果每个实体可能有无限数量的行,则 Snowflake 不允许对字段进行聚合。这意味着查询必须满足以下要求:
在查询中的某个点,受隐私保护的表必须针对实体键执行删除重复项操作。可以用于删除数据重复项的操作有:
COUNT( DISTINCT <entity_key_column> )
GROUP BY <entity_key_column>
UNION(但 UNION ALL 不可用) – 当仅投射实体键时。
如果联接使用的联接键不是实体键列,则该联接不能通过聚合在删除重复项和最终 SELECT 子句之间发生。
备注
如果数据提供者实现了行级隐私,则联接的删除重复项要求不同。有关这些要求的更多信息,请参阅 唯一性要求。
为了帮助说明实体级隐私的要求,假设您有一个受隐私保护的表 patients
,其中包含实体键列 patient_id
。您还有一个非敏感、不受保护的表 geo_lookup
。以下示例展示了一个失败的查询,后面是一个成功的重写版本。
- 示例:删除重复项
以下查询失败,因为它不符合删除重复项要求。尽管 表``patients`` 在
patient_id
上可能已经是唯一的,但查询失败,因为它没有明确删除重复项。SELECT COUNT(*) FROM patients WHERE insurance_type = 'Commercial';
为了使查询成功,须在实体键列上包含一个独特的计数,以便明确对实体键执行删除重复项。例如:
SELECT COUNT(DISTINCT patient_id) FROM patients WHERE insurance_type = 'Commercial';
- 示例:联接的位置
以下查询失败,尽管它使用了 GROUP BY 子句来满足删除重复项要求。它失败的原因是表正在与另一个表联接,而使用的列不是实体键列。
SELECT AVG(bmi) FROM (SELECT patient_id, ANY_VALUE(zip_code) AS zip_code FROM patients GROUP BY patient_id) AS p JOIN geo_lookup AS g ON p.zip_code = g.zip_code WHERE state='CA';
要重写查询以使其成功,请在联接后面使用 GROUP BY 子句。不能通过聚合在删除重复项和 SELECT 子句之间进行联接。
SELECT AVG(bmi) FROM (SELECT patient_id, ANY_VALUE(bmi) as bmi, ANY_VALUE(state) as state FROM patients AS p JOIN geo_lookup AS g ON p.zip_code = g.zip_code GROUP BY patient_id) WHERE state='CA';
执行事务级查询¶
实体级差分隐私的删除重复项要求不会妨碍您执行事务级查询。然而,您必须首先将数据分组到实体级别,然后对这些组进行聚合。
例如,假设您有一个表 doctor_visits
,数据提供商已定义一个实体键 patient_id
以实现实体级隐私。一个事务级的查询可能是:“有多少次看医生不是为了常规检查?”以下是如何编写此查询的示例:
SELECT SUM(num_visits)
FROM (SELECT SUM((visit_reason<>'Regular checkup')::INT) AS num_visits
WHERE visit_reason IS NOT NULL
GROUP BY patient_id)
WHERE num_visits > 0 AND num_visits < 20;
子查询通过 patient_id
进行分组以删除数据重复项。聚合列 num_visits
捕获不是为了定期检查而去看医生的每位患者的访问次数。然后,查询在每位患者的列上再次聚合,以获取总访问次数。请注意,外部查询中的 WHERE 子句是必需的,以便 针对数据指定隐私域。
备注
虽然这不是一项要求,但在联接受受实体级差分隐私保护的表时,最佳实践是包括实体键列作为联接键的一部分(如果它不会在语义上更改查询)。有关更多信息,请参阅 在联接中使用实体键。
理解查询结果¶
对受隐私保护的表的查询不会返回聚合的确切值。差分隐私在结果中引入 噪声,使其成为实际值的近似值。返回的值与实际值相差很大,足以掩盖个人的数据是否包含在聚合中。这适用于所有查询,但返回受隐私保护的表中总行数的查询除外,例如,SELECT COUNT(*) FROM t
。
分析师需要能够确定引入结果的噪声是否降低了查询的有用性。Snowflake 使用 噪声区间 来帮助分析师解释结果。噪声区间是一个封闭的数学区间,在大多数情况下,包括聚合的实际值。查询的实际结果在噪声区间内的概率为 95%。
将以下功能添加到查询中,可允许分析师使用噪声区间来决定查询的效用:
DP_INTERVAL_LOW – 返回噪声间隔的下限。实际值很可能等于或大于这个数字。
DP_INTERVAL_HIGH – 返回噪声间隔的上限。实际值很可能等于或小于这个数字。
要使用这些函数,请在主查询中传入聚合列的别名。例如,以下查询返回 num_claims
列的总和以及该聚合的噪声区间:
SELECT SUM(num_claims) AS sum_claims,
DP_INTERVAL_LOW(sum_claims),
DP_INTERVAL_HIGH(sum_claims)
FROM t1;
dp_interval_low = f.function('dp_interval_low')
dp_interval_high = f.function('dp_interval_high')
dpdf_cohort.group_by().agg(f.sum("num_claims").alias("sum_claims"),
dp_interval_low("sum_claims"),
dp_interval_low("sum_claims")
).show()
输出可能是:
+--------------+--------------------------------+----------------------------------+
| sum_claims | dp_interval_low("sum_claims") | dp_interval_high("sum_claims") |
|--------------+--------------------------------+----------------------------------+
| 50 | 35 | 75 |
+--------------+--------------------------------+----------------------------------+
在此示例中,返回值是总和值 50。但分析师也以 95% 的确定性确定,聚合的实际值在 35 到 75 之间。
跟踪隐私预算消耗¶
您可以使用 ESTIMATE_REMAINING_DP_AGGREGATES 函数来估算在当前预算窗口内(即,直到累计隐私损失重置为 0)您还可以运行多少个查询。该估计基于聚合的数量而不是查询。例如,查询 SELECT SUM(age), COUNT(age) FROM T
包含两个聚合函数:SUM(age)
和 COUNT(age)
。
在执行 ESTIMATE_REMAINING_DP_AGGREGATES 函数时,请确保使用与执行查询相同的确切条件,例如相同的用户、角色和账户。
如果您正在运行一个使用多个表的查询,您应该每个表运行一次 ESTIMATE_REMAINING_DP_AGGREGATES,然后使用最低的 NUMBER_OF_REMAINING_DP_AGGREGATES
值作为估计的使用上限。
以下示例展示了一系列查询如何影响隐私预算限制的消耗(即查询的累积隐私损失)以及剩余聚合的估计数量。
1.初步检查
让我们看看表 my_table
上的隐私预算数字。您从未针对此表运行过任何查询。
SELECT * FROM TABLE(SNOWFLAKE.DATA_PRIVACY.ESTIMATE_REMAINING_DP_AGGREGATES('my_table'));
到目前为止没有使用预算:
+-----------------------------------+--------------+---------------+--------------+
| NUMBER_OF_REMAINING_DP_AGGREGATES | BUDGET_LIMIT | BUDGET_WINDOW | BUDGET_SPENT |
|-----------------------------------+--------------+---------------+--------------|
| 996 | 233 | WEEKLY | 0.0 |
+-----------------------------------+--------------+---------------+--------------+
2.运行查询
让我们运行一个带有一个聚合函数的查询,并再次检查我们的数字:
SELECT SUM(salary) FROM my_table;
-- results omitted ...
SELECT * FROM TABLE(SNOWFLAKE.DATA_PRIVACY.ESTIMATE_REMAINING_DP_AGGREGATES('my_table'));
剩余的总聚合调用估计减少了一次,累计隐私损失(预算消耗)增加了。
+-----------------------------------+--------------+---------------+--------------+
| NUMBER_OF_REMAINING_DP_AGGREGATES | BUDGET_LIMIT | BUDGET_WINDOW | BUDGET_SPENT |
|-----------------------------------+--------------+---------------+--------------|
| 995 | 233 | WEEKLY | 0.6 |
+-----------------------------------+--------------+---------------+--------------+
3.使用两个聚合函数运行另一个查询
SELECT SUM(age), COUNT(age) FROM my_table GROUP BY STATE;
-- results omitted ...
SELECT * FROM TABLE(SNOWFLAKE.DATA_PRIVACY.ESTIMATE_REMAINING_DP_AGGREGATES('my_table'));
预计剩余查询已减少两个。请记住,这只是一个估计。
+-----------------------------------+--------------+---------------+--------------+
| NUMBER_OF_REMAINING_DP_AGGREGATES | BUDGET_LIMIT | BUDGET_WINDOW | BUDGET_SPENT |
|-----------------------------------+--------------+---------------+--------------|
| 993 | 233 | WEEKLY | 1.8 |
+-----------------------------------+--------------+---------------+--------------+
4.重新运行查询
让我们重新运行之前的查询,以显示隐私预算始终是扣减的,即使是在相同的查询上。重复查询每次运行都会造成相同的隐私损失(也就是说,它消耗相同数量的隐私预算)。
SELECT SUM(age), COUNT(age) FROM T GROUP BY STATE;
-- results omitted ...
SELECT * FROM TABLE(SNOWFLAKE.DATA_PRIVACY.ESTIMATE_REMAINING_DP_AGGREGATES('my_table'));
查询的扣减与之前相同:1.2 单位的隐私损失。
+-----------------------------------+--------------+---------------+--------------+
| NUMBER_OF_REMAINING_DP_AGGREGATES | BUDGET_LIMIT | BUDGET_WINDOW | BUDGET_SPENT |
|-----------------------------------+--------------+---------------+--------------|
| 991 | 233 | WEEKLY | 3.0 |
+-----------------------------------+--------------+---------------+--------------+