Zugriffsverlauf

Unter diesem Thema werden Konzepte zum Benutzerzugriffsverlauf in Snowflake bereitgestellt.

Unter diesem Thema:

Übersicht

Der Zugriffsverlauf in Snowflake bezieht sich darauf, wann die Benutzerabfrage Daten liest und wann die SQL-Anweisung eine Datenschreiboperation wie INSERT, UPDATE und DELETE sowie Variationen des COPY-Befehls vom Quelldatenobjekt zum Zieldatenobjekt ausführt. Der Benutzerzugriffsverlauf kann durch Abfrage der Account Usage-Ansicht ACCESS_HISTORY abgerufen werden. Mithilfe der Datensätze in dieser Ansicht kann die Einhaltung gesetzlicher Bestimmungen einfacher geprüft werden, und sie bieten Einblick in beliebte und häufig abgefragte Tabellen und Spalten, denn es gibt eine direkte Verbindung zwischen dem Benutzer (d. h. Abfrageoperator), der Abfrage, der Tabelle bzw. Ansicht, der Spalte und den Daten.

Jede Zeile in der ACCESS_HISTORY-Ansicht enthält einen einzelnen Datensatz pro SQL-Anweisung. Der Datensatz enthält Informationen zu den Quellspalten, auf die die Abfrage direkt und indirekt zugegriffen hat (d. h. die zugrunde liegenden Tabellen, aus denen die Daten für die Abfrage stammen) und die projizierten Spalten, die der Benutzer im Abfrageergebnis sieht. Wenn die Abfrage nicht auf eine Spalte im Abfrageergebnis zugreift oder diese projiziert, wird diese Spalte im Datensatz der Ansicht nicht erfasst, da die Daten dieser Spalte dem Benutzer nicht zur Verfügung gestellt werden. Beispiel:

CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
SELECT c1 as vc1,
       c2 as vc2
FROM t
WHERE t.c3 > 0
;
Copy
  • Die Tabellenspalten C1 und C2 sind Quellspalten, auf die die Ansicht direkt zugreift und die in der Spalte BASE_OBJECTS_ACCESSED der ACCESS_HISTORY-Ansicht erfasst sind.

  • Die Ansichtsspalten VC1 und VC2 sind projizierte Spalten, die der Benutzer bei der Abfrage der Ansicht sieht und die in der Spalte DIRECT_OBJECTS_ACCESSED der ACCESS_HISTORY-Ansicht erfasst werden.

  • Die Tabellenspalte C3 wird verwendet, um die Zeilen zu filtern, die die Ansicht enthält. Diese Spalte wird nicht in der ACCESS_HISTORY-Ansicht erfasst.

  • Eine Abfrageansicht, z. B. SELECT * FROM v1;, erfasst VC1 und VC2 als die Spalten, auf die zugegriffen wurde.

Bemerkung

Datensätze in der Account Usage-Ansicht QUERY_HISTORY werden nicht immer in der Ansicht ACCESS_HISTORY erfasst. Die Struktur der SQL-Anweisung bestimmt, ob Snowflake Datensätze in der ACCESS_HISTORY-Ansicht erfasst.

Einzelheiten zu den Lese- und Schreiboperationen, die Snowflake in der ACCESS_HISTORY-Ansicht unterstützt, finden Sie unter Nutzungshinweise.

Verfolgen von Lese- und Schreiboperationen

Die ACCESS_HISTORY-Ansicht enthält folgende Spalten:

QUERY_ID | QUERY_START_TIME | USER_NAME | DIRECT_OBJECTS_ACCESSED | BASE_OBJECTS_ACCESSED | OBJECTS_MODIFIED | OBJECT_MODIFIED_BY_DDL | POLICIES_REFERENCED
Copy

Leseoperationen werden über die ersten fünf Spalten verfolgt, während die letzte Spalte OBJECTS_MODIFIED die Datenschreibinformationen angibt, die Snowflake-Spalten, -Tabellen und -Stagingbereiche betreffen.

Die Abfrage in Snowflake und die Methode zum Erstellen von Datenbankobjekten bestimmen die Informationen, die Snowflake in den Spalten DIRECT_OBJECTS_ACCESSED, BASE_OBJECTS_ACCESSED und OBJECTS_MODIFIED zurückgibt.

Gleiches gilt, wenn die Abfrage auf ein Objekt verweist, das durch eine Zeilenzugriffsrichtlinie geschützt ist, oder auf eine Spalte, die durch eine Maskierungsrichtlinie geschützt ist, erfasst Snowflake die Richtlinieninformationen in der Spalte POLICIES_REFERENCED.

In der Spalte OBJECT_MODIFIED_BY_DDL wird die auf einer Datenbank, einem Schema, einer Tabelle, einer Ansicht oder einer Spalte ausgeführte DDL-Operation erfasst. Zu diesen Operationen gehören auch Anweisungen, die eine Zeilenzugriffsrichtlinie auf einer Tabelle oder Ansicht, eine Maskierungsrichtlinie auf einer Spalte sowie Tag-Aktualisierungen (z. B. Setzen eines Tags, Ändern eines Tag-Werts) auf dem Objekt oder der Spalte angeben.

Weitere Informationen zu den Spalten finden Sie im Abschnitt Spalten zur ACCESS_HISTORY-Ansicht.

Lesen

Betrachten Sie das folgende Szenario, um ein besseres Verständnis der Leseabfrage und der Erfassung dieser Informationen in der ACCESS_HISTORY-Ansicht zu erhalten:

  • Eine Reihe von Objekten: base_table » view_1 » view_2 » view_3.

  • Eine Leseabfrage auf view_2, wie z. B.:

    select * from view_2;
    
    Copy

In diesem Beispiel gibt Snowflake Folgendes zurück:

  • view_2 in der Spalte DIRECT_OBJECTS_ACCESSED, weil in der Abfrage view_2 angegeben ist.

  • base_table in der Spalte BASE_OBJECTS_ACCESSED, da dies die ursprüngliche Quelle der Daten in view_2 ist.

Beachten Sie, dass view_1 und view_3 nicht in den Spalten DIRECT_OBJECTS_ACCESSED und BASE_OBJECTS_ACCESSED enthalten sind, da keine dieser Ansichten in der Abfrage enthalten war und sie nicht das Basisobjekt sind, das als Quelle für die Daten in view_2 dient.

Schreiben

Betrachten Sie das folgende Szenario, um ein besseres Verständnis der Schreiboperation und der Erfassung dieser Informationen in der ACCESS_HISTORY-Ansicht zu erhalten:

  • Eine Datenquelle: base_table

  • Erstellen Sie eine Tabelle aus der Datenquelle (d. h. CTAS):

    create table table_1 as select * from base_table;
    
    Copy

In diesem Beispiel gibt Snowflake Folgendes zurück:

  • base_table in den Spalten BASE_OBJECTS_ACCESSED und DIRECT_OBJECTS_ACCESSED, da auf die Tabelle direkt zugegriffen wurde und sie die Quelle der Daten ist.

  • table_1 in der Spalte OBJECTS_MODIFIED mit den Spalten, in die beim Erstellen der Tabelle geschrieben wurde.

Spaltenherkunft

Spaltenherkunft (d. h. der Zugriffsverlauf von Spalten) erweitert die Account Usage-Ansicht ACCESS_HISTORY umd die Angabe, wie die Daten bei einer Schreiboperation von der Quellspalte zur Zielspalte gelangen. Snowflake verfolgt die Daten aus den Quellspalten über alle nachfolgenden Tabellenobjekte, die auf Daten aus den Quellspalten verweisen (z. B. INSERT, MERGE, CTAS), vorausgesetzt, dass Objekte in der Herkunftsabfolge nicht gelöscht werden. Snowflake stellt die Spaltenherkunft bereit, indem die ACCESS_HISTORY-Ansicht um die Spalte OBJECTS_MODIFIED erweitert wird.

Weitere Informationen dazu finden Sie unter:

Richtlinienreferenzen von Maskierungs- und Zeilenzugriffsrichtlinien

Die Spalte POLICY_REFERENCED gibt das Objekt an, das eine Zeilenzugriffsrichtlinie für eine Tabelle bzw. eine Maskierungsrichtlinie für eine Spalte hat, einschließlich aller Zwischenobjekte, die entweder durch eine Zeilenzugriffsrichtlinie oder eine Maskierungsrichtlinie geschützt sind. Snowflake erfasst die Richtlinie, die für die Tabelle oder Spalte erzwungen wird.

Betrachten Sie diese Objekte:

t1 » v1 » v2

Wobei:

  • t1 ist eine Basistabelle.

  • v1 ist eine aus der Basistabelle erstellte Ansicht.

  • v2 ist eine aus v1 erstellte Ansicht.

Wenn der Benutzer v2 abfragt, wird in der Spalte POLICIES_REFERENCED entweder die Zeilenzugriffsrichtlinie, die v2 schützt, oder jede Maskierungsrichtlinie, die die Spalten in v2 schützt, oder es werden ggf. beide Arten von Richtlinien erfasst. Außerdem werden in dieser Spalte alle Maskierungs- oder Zeilenzugriffsrichtlinien erfasst, die t1 und v1 schützen.

Anhand dieser Datensätze können Datenverwalter nachvollziehen, wie auf ihre richtliniengeschützten Objekte zugegriffen wird.

Die Spalte POLICIES_REFERENCED bietet weitere Vorteile für die ACCESS_HISTORY-Ansicht:

  • Identifizieren der richtliniengeschützten Objekte, auf die ein Benutzer in einer bestimmten Abfrage zugreift.

  • Vereinfachen des Prüfprozesses (Auditing) von Richtlinien.

    Durch Abfragen der ACCESS_HISTORY-Ansicht sind keine komplexen Verknüpfungen mit anderen Account Usage-Ansichten (z. B. POLICY_REFERENCES und QUERY_HISTORY) mehr erforderlich, um Informationen zu den geschützten Objekten und den geschützten Spalten zu erhalten, auf die ein Benutzer Zugriff hat.

Unterstützte Operationen

Eine vollständige Beschreibung der von der ACCESS_HISTORY-Ansicht unterstützten Lese- und Schreiboperationen finden Sie im Abschnitt „Nutzungshinweise“ unter ACCESS_HISTORY-Ansicht.

Vorteile

Der Zugriffsverlauf in Snowflake bietet die folgenden Vorteile in Bezug auf Lese- und Schreiboperationen:

Datenerkennung

Erkennen von nicht verwendeten Daten, um zu entscheiden, ob die Daten archiviert oder gelöscht werden sollen.

Verfolgen der Bewegung sensibler Daten

Verfolgen Sie Datenbewegungen von einem externen Cloudspeicherort (z. B. Amazon S3-Bucket) zur Snowflake-Zieltabelle und umgekehrt.

Verfolgen Sie interne Datenbewegungen von einer Snowflake-Tabelle zu einer anderen Snowflake-Tabelle.

Nachdem Sie die Bewegung sensibler Daten verfolgt haben, wenden Sie Richtlinien (Maskierung und Zeilenzugriff) an, um die Daten zu schützen, aktualisieren Sie die Zugriffssteuerungseinstellungen, um den Zugriff auf Stagingbereich und Tabelle weiter zu regulieren, und setzen Sie Tags, um sicherzustellen, dass Stagingbereiche, Tabellen und Spalten mit sensiblen Daten für Compliance-Anforderungen nachverfolgt werden können.

Datenvalidierung

Die Genauigkeit und Integrität von Berichten, Dashboards und Datenvisualisierungsprodukten wie Diagrammen und Grafiken werden validiert, da die Daten bis zu ihrer ursprünglichen Quelle zurückverfolgt werden können.

Datenverwalter können Benutzer benachrichtigen, bevor eine bestimmte Tabelle oder Ansicht gelöscht oder geändert wird.

Compliance-Prüfung

Identifizieren Sie den Snowflake-Benutzer, der eine Schreiboperation auf einer Tabelle oder einem Stagingbereich ausgeführt hat, und wann diese Schreiboperation stattgefunden hat, um Compliance-Anforderungen wie GDPR oder CCPA zu erfüllen.

Verbesserung der allgemeinen Data Governance

Die ACCESS_HISTORY-Ansicht bietet einen Gesamtüberblick, auf welche Daten zugegriffen wurde, wann der Datenzugriff stattfand und wie die Daten, auf die zugegriffen wurde, vom Datenquellenobjekt zum Datenzielobjekt verschoben wurden.

Die Spaltenherkunft bietet folgende weitere Vorteile:

Abgeleitete Objekte schützen

Datenverwalter können auf Quellspalten mit sensiblen Daten auf einfache Weise Tags setzen, ohne dass nach dem Erstellen abgeleiteter Objekte (z. B. CTAS) zusätzlicher Aufwand erforderlich ist. Anschließend kann der Datenverwalter Tabellen, die sensible Spalten enthalten, mit einer Zeilenzugriffsrichtlinie schützen oder die sensiblen Spalten selbst entweder mit einer Maskierungsrichtlinie oder einer Tag-basierten Maskierungsrichtlinie schützen.

Kopierhäufigkeit von Spalten mit sensiblen Daten

Datenschutzbeauftragte können schnell die Objektanzahl (z. B. 1 Tabelle, 2 Ansichten) einer Spalte ermitteln, die sensible Daten enthält. Mit dem Wissen, wie oft eine Spalte mit sensiblen Daten in einem Tabellenobjekt vorkommt, können die Datenschutzbeauftragten nachweisen, wie Standards für die Einhaltung von Vorschriften, wie z. B. der Datenschutz-Grundverordnung (DGVO) in der Europäischen Union, erfüllt werden.

Ursachenanalyse

Die Spaltenherkunft bietet einen Mechanismus zur Rückverfolgung von Daten bis zu ihrer Quelle, was dazu beitragen kann, Fehlerpunkte zu identifizieren, die auf eine schlechte Datenqualität zurückzuführen sind, und die Anzahl der zu analysierenden Spalten während der Problembehandlung zu reduzieren.

Abfragen der ACCESS_HISTORY-Ansicht

Die folgenden Abschnitte enthalten Beispielabfragen für die ACCESS_HISTORY-Ansicht.

Beachten Sie, dass einige der Beispielabfragen nach der Spalte QUERY_START_TIME filtern, um die Abfrageleistung zu erhöhen. Eine weitere Möglichkeit, die Leistung zu erhöhen, ist die Einengung des Zeitbereichs, auf dem die Abfrage ausgeführt wird.

Beispiele: Leseabfragen

In den folgenden Unterabschnitten wird beschrieben, wie die ACCESS_HISTORY-Ansicht für Leseoperationen in den folgenden Anwendungsfälle abgefragt werden kann:

  • Abrufen des Zugriffsverlaufs für einen bestimmten Benutzer.

  • Vereinfachen der Compliance-Prüfungen für den Zugriff auf sensible Daten in den letzten 30 Tagen auf Basis von object_id (z. B. einer Tabellen-ID), um die folgenden Fragen zu beantworten:

    • Wer hat auf die Daten zugegriffen?

    • Wann wurde auf die Daten zugegriffen?

    • Auf welche Spalten wurde zugegriffen?

Benutzerzugriffsverlauf zurückgeben

Gibt den Benutzerzugriffsverlauf zurück, sortiert nach Benutzer und Startzeit der Abfrage, beginnend mit dem letzten Zugriff.

SELECT user_name
       , query_id
       , query_start_time
       , direct_objects_accessed
       , base_objects_accessed
FROM access_history
ORDER BY 1, 3 desc
;
Copy

Compliance-Prüfungen vereinfachen

Die folgenden Beispiele helfen, die Einhaltung gesetzlicher Bestimmungen zu vereinfachen:

  • Fügen Sie den object_id-Wert hinzu, um festzustellen, wer in den letzten 30 Tagen auf eine sensible Tabelle zugegriffen hat:

    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())
    ;
    
    Copy
  • Bestimmen Sie anhand des object_id-Wertes von 32998411400350, wann der Zugriff in den letzten 30 Tagen erfolgte:

    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())
    ;
    
    Copy
  • Bestimmen Sie anhand des object_id-Wertes von 32998411400350, auf welche Spalten in den letzten 30 Tagen zugegriffen wurde:

    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'
    ;
    
    Copy

Beispiele: Schreiboperationen

In den folgenden Unterabschnitten wird beschrieben, wie die ACCESS_HISTORY-Ansicht für Schreiboperationen in den folgenden Anwendungsfälle abgefragt werden kann:

  • Laden von Daten aus einem Stagingbereich in eine Tabelle

  • Entladen von Daten aus einer Tabelle in einen Stagingbereich

  • Verwenden des PUT-Befehls zum Hochladen einer lokale Datei in einen Stagingbereich

  • Verwenden des GET-Befehls zum Abrufen von Datendateien aus einem Stagingbereich in ein lokales Verzeichnis

  • Verfolgen der Bewegung sensibler Stagingbereichsdaten

Daten aus einem Stagingbereich in eine Tabelle laden

Laden Sie eine Menge von Werten aus einer Datendatei in einen externen Cloudspeicher in Spalten einer Zieltabelle.

copy into table1(col1, col2)
from (select t.$1, t.$2 from @mystage1/data1.csv.gz);
Copy

In den Spalten DIRECT_OBJECTS_ACCESSED und BASE_OBJECTS_ACCESSED wird angegeben, dass auf einen externen benannten Stagingbereich zugegriffen wurde:

{
  "objectDomain": STAGE
  "objectName": "mystage1",
  "objectId": 1,
  "stageKind": "External Named"
}
Copy

In der Spalte OBJECTS_MODIFIED wird angegeben, dass Daten in zwei Spalten der Tabelle geschrieben wurden:

{
  "columns": [
     {
       "columnName": "col1",
       "columnId": 1
     },
     {
       "columnName": "col2",
       "columnId": 2
     }
  ],
  "objectId": 1,
  "objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
  "objectDomain": TABLE
}
Copy

Daten aus einer Tabelle in einen Stagingbereich entladen

Entladen Sie eine Menge von Werten aus einer Snowflake-Tabelle in den Cloudspeicher.

copy into @mystage1/data1.csv
from table1;
Copy

In den Spalten DIRECT_OBJECTS_ACCESSED und BASE_OBJECTS_ACCESSED werden die Tabellenspalten angegeben, auf die zugegriffen wurde:

{
  "objectDomain": TABLE
  "objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
  "objectId": 123,
  "columns": [
     {
       "columnName": "col1",
       "columnId": 1
     },
     {
       "columnName": "col2",
       "columnId": 2
     }
  ]
}
Copy

In der Spalte OBJECTS_MODIFIED wird der Stagingbereich angegeben, in den die abgerufenen Daten geschrieben wurden:

{
  "objectId": 1,
  "objectName": "mystage1",
  "objectDomain": STAGE,
  "stageKind": "External Named"
}
Copy

PUT-Befehl zum Hochladen einer lokalen Datei in einen Stagingbereich verwenden

Kopieren Sie eine Datendatei in einen internen (d. h. Snowflake) Stagingbereich.

put file:///tmp/data/mydata.csv @my_int_stage;
Copy

In den Spalten DIRECT_OBJECTS_ACCESSED und BASE_OBJECTS_ACCESSED wird der lokale Pfad zu der Datei angegeben, auf die zugegriffen wurde:

{
  "location": "file:///tmp/data/mydata.csv"
}
Copy

In der Spalte OBJECTS_MODIFIED wird der Stagingbereich angegeben, in den die abgerufenen Daten geschrieben wurden:

{
  "objectId": 1,
  "objectName": "my_int_stage",
  "objectDomain": STAGE,
  "stageKind": "Internal Named"
}
Copy

GET-Befehl zum Abrufen von Datendateien aus einem Stagingbereich in ein lokales Verzeichnis verwenden

Rufen Sie eine Datendatei aus einem internen Stagingbereich in ein Verzeichnis auf dem lokalen Rechner ab.

get @%mytable file:///tmp/data/;
Copy

In den Spalten DIRECT_OBJECTS_ACCESSED und BASE_OBJECTS_ACCESSED werden der Stagingbereich und das lokale Verzeichnis angegeben, auf die zugegriffen wurde:

{
  "objectDomain": Stage
  "objectName": "mytable",
  "objectId": 1,
  "stageKind": "Table"
}
Copy

In der Spalte OBJECTS_MODIFIED wird das Verzeichnis angegeben, in das die abgerufenen Daten geschrieben wurden:

{
  "location": "file:///tmp/data/"
}
Copy

Bewegung sensibler Stagingbereichsdaten verfolgen

Verfolgen Sie sensible Stagingbereichsdaten, während diese eine Serie von Abfragen in chronologischer Reihenfolge durchlaufen.

Führen Sie die folgenden Abfragen aus. Beachten Sie, dass fünf der Anweisungen auf Stagingbereiche zugreifen. Wenn Sie also die ACCESS_HISTORY-Ansicht für den Stagingbereichszugriff abfragen, sollte das Resultset fünf Zeilen enthalten.

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;
Copy

Wobei:

  • T1, T2T7 geben die Namen der Tabellen an.

  • S1 und S2 geben die Namen der Stagingbereiche an.

Führen Sie eine Abfrage des Zugriffsverlaufs aus, um den Zugriff auf Stagingbereich S1 zu ermitteln.

Die Daten der Spalten DIRECT_OBJECTS_ACCESSED, BASE_OBJECTS_ACCESSED und OBJECTS_MODIFIED sind in der folgenden Tabelle aufgeführt.

DIRECT_OBJECTS_ACCESSED

BASE_OBJECTS_ACCESSED

OBJECTS_MODIFIED

[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68613,
        "columnName": "ID"
      },
      {
        "columnId": 68612,
        "columnName": "NAME"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66568,
    "objectName": "TEST_DB.TEST_SCHEMA.T2"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 118,
    "objectName": "TEST_DB.TEST_SCHEMA.S2",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68614,
        "columnName": "CUSTOMER_INFO"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66570,
    "objectName": "TEST_DB.TEST_SCHEMA.T3"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68615,
        "columnName": "NAME"
      },
      {
        "columnId": 68616,
        "columnName": "ID"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66572,
    "objectName": "TEST_DB.TEST_SCHEMA.T4"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68618,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66574,
    "objectName": "TEST_DB.TEST_SCHEMA.T7"
  }
]
Copy

Beachten Sie bei diesem Abfragebeispiel Folgendes:

  • Verwendet einen rekursiven allgemeinen Tabellenausdruck.

  • Verwendet ein JOIN-Konstrukt anstelle einer USING-Klausel.

    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;
    
    Copy

Die Abfrage liefert das folgende Resultset in Bezug auf die Datenbewegung in Stagingbereich S1:

PATH

TARGET_NAME

TARGET_ID

TARGET_DOMAIN

TARGET_COLUMNS

TEST_DB.TEST_SCHEMA.S1–>TEST_DB.TEST_SCHEMA.T1

TEST_DB.TEST_SCHEMA.T1

66564

Tabelle

[„CONTENT“]

TEST_DB.TEST_SCHEMA.S1–>TEST_DB.TEST_SCHEMA.T1–>TEST_DB.TEST_SCHEMA.S2

TEST_DB.TEST_SCHEMA.S2

118

Stagingbereich

[]

TEST_DB.TEST_SCHEMA.S1–>TEST_DB.TEST_SCHEMA.T1–>TEST_DB.TEST_SCHEMA.T2

TEST_DB.TEST_SCHEMA.T2

66568

Tabelle

[„NAME“,“ID“]

TEST_DB.TEST_SCHEMA.S1–>TEST_DB.TEST_SCHEMA.T1–>TEST_DB.TEST_SCHEMA.T4

TEST_DB.TEST_SCHEMA.T4

66572

Tabelle

[„ID“,“NAME“]

TEST_DB.TEST_SCHEMA.S1–>TEST_DB.TEST_SCHEMA.T3

TEST_DB.TEST_SCHEMA.T3

66570

Tabelle

[„CUSTOMER_INFO“]

Beispiel: Spaltenherkunft

Im folgenden Beispiel wird die ACCESS_HISTORY-Ansicht abgefragt, und die Funktion FLATTEN wird verwendet, um die Spalte OBJECTS_MODIFIED zu vereinfachen.

Führen Sie als repräsentatives Beispiel die folgende SQL-Abfrage in Ihrem Snowflake-Konto aus, um die folgende Tabelle zu erstellen, in der die nummerierten Kommentare Folgendes angeben:

  • // 1: Ermittelt die Zuordnung zwischen dem Feld directSources und der Zielspalte.

  • // 2: Ermittelt die Zuordnung zwischen dem Feld baseSources und der Zielspalte.

// 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
;
Copy

Rückgabewerte:

SOURCE_OBJECT_ID

SOURCE_OBJECT_NAME

SOURCE_COLUMN_NAME

SOURCE_COLUMN_TYPE

TARGET_OBJECT_NAME

TARGET_COLUMN_NAME

1

D.S.T0

NAME

BASE

D.S.T1

NAME

2

D.S.V1

NAME

DIRECT

D.S.T1

NAME

Beispiel: Richtlinienreferenzen von Zeilenzugriffsrichtlinien verfolgen

Gibt eine Zeile für jede Instanz zurück, wenn eine Zeilenzugriffsrichtlinie für eine Tabelle, Ansicht oder materialisierte Ansicht ohne Duplikate festgelegt ist:

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
;
Copy

Beispiel: Richtlinienreferenzen von Maskierungsrichtlinien verfolgen

Gibt eine Zeile für jede Instanz zurück, wenn eine Maskierungsrichtlinie eine Spalte ohne Duplikate schützt. Beachten Sie, dass eine zusätzliche Vereinfachung (Flattening) erforderlich ist, da die POLICIES_REFERENCED-Spalten die Maskierungsrichtlinie einer Spalte eine Ebene tiefer als die Zeilenzugriffsrichtlinie einer Tabelle angeben:

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
;
Copy

Beispiele: UDFs

Die folgenden UDF-Beispiele zeigen, wie die Account Usage-Ansicht ACCESS_HISTORY Daten erfasst:

  • Aufrufen einer UDF namens get_product.

  • Einfügen des Produkts aus dem Aufruf der Funktion get_product in eine Tabelle namens mydb.tables.t1.

  • Freigeben von UDFs.

Aufrufen einer UDF

Angenommen, die folgende SQL-UDF berechnet das Produkt zweier Zahlen und speichert das Ergebnis im Schema namens mydb.udfs:

CREATE FUNCTION MYDB.UDFS.GET_PRODUCT(num1 number, num2 number)
RETURNS number
AS
$$
    NUM1 * NUM2
$$
;
Copy

Das direkte Aufrufen von get_product führt zum Erfassen der UDF-Details in der Spalte DIRECT_OBJECTS_ACCESSED:

[
  {
    "objectDomain": "FUNCTION",
    "objectName": "MYDB.UDFS.GET_PRODUCT",
    "objectId": "2",
    "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)",
    "dataType": "NUMBER(38,0)"
  }
]
Copy

Dieses Beispiel ist analog zum Aufrufen einer gespeicherten Prozedur (unter diesem Thema).

UDF mit INSERT DML

Betrachten Sie die folgende INSERT-Anweisung, mit der die Spalten 1 und 2 in der Tabelle mydb.tables.t1 aktualisiert werden:

insert into t1(product)
select get_product(c1, c2) from mydb.tables.t1;
Copy

Die ACCESS_HISTORY-Ansicht erfasst die get_product-Funktion in folgenden Spalten:

  • Spalte DIRECT_OBJECTS_ACCESSED, da die Funktion in der SQL-Anweisung explizit genannt wird

  • Spalte OBJECTS_MODIFIED im Array directSources, da die Funktion die Quelle für die Werte ist, die in die Spalten eingefügt werden.

Außerdem wird auch Tabelle t1 in denselben Spalten erfasst:

DIRECT_OBJECTS_ACCESSED

OBJECTS_MODIFIED

[
  {
    "objectDomain": "FUNCTION",
    "objectName": "MYDB.UDFS.GET_PRODUCT",
    "objectId": "2",
    "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)",
    "dataType": "NUMBER(38,0)"
  },
  {
    "objectDomain": "TABLE",
    "objectName": "MYDB.TABLES.T1",
    "objectId": 1,
    "columns":
    [
      {
        "columnName": "c1",
        "columnId": 1
      },
      {
        "columnName": "c2",
        "columnId": 2
      }
    ]
  }
]
Copy
 [
   {
     "objectDomain": "TABLE",
     "objectName": "MYDB.TABLES.T1",
     "objectId": 2,
     "columns":
     [
       {
         "columnId": "product",
         "columnName": "201",
         "directSourceColumns":
         [
           {
             "objectDomain": "Table",
             "objectName": "MYDB.TABLES.T1",
             "objectId": "1",
             "columnName": "c1"
           },
           {
             "objectDomain": "Table",
             "objectName": "MYDB.TABLES.T1",
             "objectId": "1",
             "columnName": "c2"
           },
           {
             "objectDomain": "FUNCTION",
             "objectName": "MYDB.UDFS.GET_PRODUCT",
             "objectId": "2",
             "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)",
             "dataType": "NUMBER(38,0)"
           }
         ],
         "baseSourceColumns":[]
       }
     ]
   }
]
Copy

Freigegebene UDFs

Freigegebene UDFs können direkt oder indirekt referenziert werden:

  • Eine direkte Referenz ist dasselbe wie das explizite Aufrufen der UDF (unter diesem Thema), führt aber dazu, dass die UDF sowohl in der Spalte BASE_OBJECTS_ACCESSED als auch in der Spalte DIRECT_OBJECTS_ACCESSED erfasst wird.

  • Ein Beispiel für einen indirekten Verweis ist das Aufrufen der UDF zum Erstellen einer Ansicht:

    create view v as
    select get_product(c1, c2) as vc from t;
    
    Copy

    In der Spalte BASE_OBJECTS_ACCESSED werden die UDF und die Tabelle erfasst.

    In der Spalte DIRECT_OBJECTS_ACCESSED wird die Ansicht erfasst.

Beispiel: Aufrufen einer gespeicherten Prozedur

Angenommen die folgende gespeicherte Prozedur ist im Schema namens mydb.procedures gespeichert:

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);
$$
;
Copy

Der direkte Aufruf von my_procedure führt dazu, dass die Details der Prozedur sowohl in der Spalte DIRECT_OBJECTS_ACCESSED als auch in der Spalte BASE_OBJECTS_ACCESSED wie folgt erfasst werden:

[
  {
    "objectDomain": "PROCEDURE",
    "objectName": "MYDB.PROCEDURES.GET_ID_VALUE",
    "argumentSignature": "(NAME STRING)",
    "dataType": "STRING"
  }
]
Copy

Dieses Beispiel ist analog zum Aufrufen einer UDF (unter diesem Thema).

Beispiele: Verfolgen von Objekten, die durch eine DDL-Operation geändert wurden

Tag mit Schlüssel ALLOWED_VALUES erstellen

So erstellen Sie das Tag:

create tag governance.tags.pii allowed_values 'sensitive','public';
Copy

Spaltenwert:

{
  "objectDomain": "TAG",
  "objectName": "governance.tags.pii",
  "objectId": "1",
  "operationType": "CREATE",
  "properties": {
    "allowedValues": {
      "sensitive": {
        "subOperationType": "ADD"
      },
      "public": {
        "subOperationType": "ADD"
      }
    }
  }
}
Copy

Bemerkung

Wenn Sie beim Erstellen des Tags keine zulässigen Werte angeben, ist das Feld properties ein leeres Array (d. h. {}).

Tabelle mit Tag und Maskierungsrichtlinie erstellen

So erstellen Sie die Tabelle mit einer Maskierungsrichtlinie auf der Spalte, einem Tag auf der Spalte und einem Tag auf der Tabelle:

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');
Copy

Spaltenwert:

{
  "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
            }
          }
        }
      }
    }
  }
}
Copy

Maskierungsrichtlinie auf ein Tag setzen

So legen Sie eine Maskierungsrichtlinie auf dem Tag fest (d. h. Tag-basiertes Maskieren):

alter tag governance.tags.pii set masking policy governance.policies.email_mask;
Copy

Spaltenwert:

{
  "objectDomain": "TAG",
  "objectName": "governance.tags.pii",
  "objectId": "1",
  "operationType": "ALTER",
  "properties": {
    "maskingPolicies": {
      "governance.policies.email_mask": {
        "subOperationType": "ADD",
        "objectId": {
          "value": 2
        }
      }
    }
  }
}
Copy

Tabelle austauschen

So tauschen Sie die Tabelle mit dem Namen t2 gegen die Tabelle mit dem Namen t3 aus:

alter table governance.tables.t2 swap with governance.tables.t3;
Copy

Beachten Sie die beiden unterschiedlichen Datensätze in der Ansicht.

Datensatz 1:

{
  "objectDomain": "Table",
  "objectId": 0,
  "objectName": "GOVERNANCE.TABLES.T2",
  "operationType": "ALTER",
  "properties": {
    "swapTargetDomain": {
      "value": "Table"
    },
    "swapTargetId": {
      "value": 0
    },
    "swapTargetName": {
      "value": "GOVERNANCE.TABLES.T3"
    }
  }
}
Copy

Datensatz 2:

{
  "objectDomain": "Table",
  "objectId": 0,
  "objectName": "GOVERNANCE.TABLES.T3",
  "operationType": "ALTER",
  "properties": {
    "swapTargetDomain": {
      "value": "Table"
    },
    "swapTargetId": {
      "value": 0
    },
    "swapTargetName": {
      "value": "GOVERNANCE.TABLES.T2"
    }
  }
}
Copy

Maskierungsrichtlinie löschen

So löschen Sie die Maskierungsrichtlinie:

drop masking policy governance.policies.email_mask;
Copy

Spaltenwert:

{
  "objectDomain" : "MASKING_POLICY",
  "objectName": "governance.policies.email_mask",
  "objectId" : "1",
  "operationType": "DROP",
  "properties" : {}
}
Copy

Bemerkung

Der Spaltenwert ist repräsentativ und gilt für eine DROP-Operation auf einem Tag und einer Zeilenzugriffsrichtlinie.

Das Feld properties ist ein leeres Array. Es enthält keine Informationen zu der Richtlinie vor der DROP-Operation.

Tag-Referenzen auf einer Spalte verfolgen

Fragen Sie die Spalte OBJECT_MODIFIED_BY_DDL ab, um zu überwachen, wie ein Tag auf eine Spalte gesetzt ist.

Als Administrator der Tabelle können Sie ein Tag auf eine Spalte setzen, das Tag wieder entfernen und das Tag mit einem anderen Zeichenfolgenwert aktualisieren:

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';
Copy

Ändern Sie als Data Engineer den Tag-Wert:

alter table hr.tables.empl_info
  alter column email set tag governance.tags.data_category = 'public';
Copy

Fragen Sie die ACCESS_HISTORY-Ansicht ab, um die Änderungen zu überwachen:

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;
Copy

Rückgabewerte:

+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
| 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    |
+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+