差分プライバシーで保護されたデータのクエリ¶
このトピックは、アナリストが差分プライバシーで保護されたデータ(つまり、プライバシー保護されたテーブルとビュー)に対してクエリを実行し、クエリから返される結果を理解して調整するのに役立ちます。
プライバシー保護されたテーブルに対してクエリを実行するには、ユーザーはテーブルに対して SELECT 権限を持っている必要があります。
制限事項
差分プライバシーは、Snowflakeのデータ型、演算子、クエリ構文、関数のサブセットをサポートしています。クエリで使用できる SQL のリストについては、 差動プライバシー SQL リファレンス をご参照ください。
プライバシー保護されたテーブルに対するクエリは、Snowflakeがノイズの付加量を決定するために追加の計算を実行する必要があるため、時間がかかります。基本的なクエリの場合、この遅延は7秒以上となっています。以下のような複雑なクエリは、さらに時間がかかります。
多くの結合とサブクエリを含むクエリ。
結果に複数行を出力するクエリ。たとえば、 GROUP BY 句を使用して、数百または数千のグループになる場合など。
クエリの基礎¶
このセクションでは、プライバシー保護されたテーブルに対して実行した場合に成功するクエリの基本的な構成要素について説明します。説明には次が含まれます。
データの集計¶
プライバシー保護されたテーブルに対するすべてのクエリは、個々の記録を取得するのではなく、結果を集計する必要があります。最終結果が集計される限り、クエリのすべての部分で集計関数を使用する必要はありません。
COUNT 関数を除き、クエリは、その列が プライバシードメイン を持たない限り、列の集計はできません。
サポートされている集計のリストについては、 集計関数 をご参照ください。
結合の使用¶
以下のセクションでは、差分プライベートクエリで結合を使用する際のガイドラインを示します。
プライバシー保護された2つのテーブルの結合がプライバシードメインに及ぼす影響については、 プライバシードメインと結合 をご参照ください。
結合演算子¶
各結合は、単一の演算子を使用する等価結合である必要があります。たとえば、 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
トランザクションで購入された固有のアイテム。1つのトランザクションに複数の行が存在することもあります。
ベストプラクティスに従っているのであれば、データプロバイダーは、これらのテーブルがそれぞれエンティティキー customer_id
を持つようにデータを構造化しています。このデータスキーマでは、各トランザクション行は1つのトランザクションにのみ属することができ、各トランザクションは1人の顧客にのみ属することができます。この関係はデータ自体からは明らかではないので、追加情報がなければ、差分プライバシーによって追加されるノイズの量は必要以上に多くなります。
たとえ冗長であっても、結合キーの一部としてエンティティキー customer_id
を含めることで、ノイズの量を最小限に抑えることができます。たとえば、テーブル transactions
を transaction_lines
と結合する場合、通常は結合キー transaction_id
だけが必要です。しかし、 transaction_id
と customer_id
の両方で結合すると、ノイズの量は少なくなります。
データ型とプライバシードメイン¶
2つのテーブルを結合する場合、両側の結合キー列のデータ型は同一になる必要があります。差分プライバシーの場合、列のデータ型には、 プライバシードメイン を持つかどうかが含まれます。
たとえば、プライバシー保護されたテーブル transactions
と保護されていないテーブル product_lookup
があり、それらを product_id
で結合したい場合、両方のテーブルの product_id
列は同じデータ型(たとえば、文字列)でなければならず、それぞれプライバシードメインを持っていなければなりません。
この要件を満たすために、アナリストの管理者が、データプロバイダーがプライバシードメインを定義するのと同じように、プライバシードメインを定義する必要が生じる場合があります。テーブルにプライバシードメインを設定する方法については、 プライバシードメインの設定 をご参照ください。
一意性要件¶
結合はデータ行を重複させる可能性があるため、クエリ結果に付加されるノイズの量が際限なく増える可能性があります。プライバシー保護されたデータが結合で重複しないようにするため、プライバシー保護されたテーブルの結合キー(つまり、テーブルが結合される列)は、もう一方のテーブルの1つの記録にのみ一致する必要があります。つまり、プライバシー保護されたテーブルと結合する場合、反対側の結合キーは重複排除する必要があります。
重要
結合の一意性要件は、 エンティティレベルのプライバシー によって保護されているテーブルに対するクエリには必ずしも適用されません。エンティティレベルのプライバシーの場合、クエリは集計の前にエンティティキーで重複排除しなければなりません。これが結合の後で集計の前に行われる限り、結合は重複排除されたデータである必要はありません。これらの要件を満たすための詳細については、 エンティティレベルのプライバシーで保護されたデータのクエリ をご参照ください。
結合の一意性要件を満たすために、クエリは結合列のサブセットで GROUP BY を使用して重複する行を1つの結果にまとめることができます。
たとえば、 patients
テーブルが差分プライバシーで保護され、 geo_lookup
テーブルは保護されていないとしましょう。アナリストはこの2つのテーブルを zip_code
で結合し、 patients
テーブルを State
でフィルタリングしたいと考えています。プライバシー保護された 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 句の間で発生することはありません。
注釈
データプロバイダーが行レベルプライバシーを実装している場合、結合に対する重複排除要件は異なります。これらの要件の詳細については、 一意性要件 をご参照ください。
エンティティレベルのプライバシーの要件を説明するために、エンティティキー列 patient_id
を持つプライバシー保護されたテーブル patients
があると仮定しましょう。機密性のない、保護されていないテーブル 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)
という2つの集約関数があります。
ESTIMATE_REMAINING_DP_AGGREGATES 関数を実行する際には、クエリ実行時に使用している条件(たとえば、同じユーザー、ロール、アカウント)をそのまま使用するようにしてください。
複数のテーブルを使用するクエリを実行する場合は、テーブルごとに ESTIMATE_REMAINING_DP_AGGREGATES を1回実行し、 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.クエリの実行
1つの集約関数を使ったクエリを実行し、もう一度数字をチェックしてみましょう。
SELECT SUM(salary) FROM my_table;
-- results omitted ...
SELECT * FROM TABLE(SNOWFLAKE.DATA_PRIVACY.ESTIMATE_REMAINING_DP_AGGREGATES('my_table'));
残りの集計呼び出しの推定数が1つ減り、累積プライバシーロス(費やされたバジェット)が増加しました。
+-----------------------------------+--------------+---------------+--------------+
| NUMBER_OF_REMAINING_DP_AGGREGATES | BUDGET_LIMIT | BUDGET_WINDOW | BUDGET_SPENT |
|-----------------------------------+--------------+---------------+--------------|
| 995 | 233 | WEEKLY | 0.6 |
+-----------------------------------+--------------+---------------+--------------+
3.2つの集約関数を持つ別のクエリの実行
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'));
残りの推定クエリが2つ減りました。これは推定であることに留意してください。
+-----------------------------------+--------------+---------------+--------------+
| 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 |
+-----------------------------------+--------------+---------------+--------------+