액세스 내역

이 항목에서는 Snowflake의 사용자 액세스 기록에 대한 개념을 제공합니다.

이 항목의 내용:

개요

Snowflake의 액세스 기록은 사용자 쿼리가 열 데이터를 읽을 때와 SQL 문이 원본 데이터 오브젝트에서 대상 데이터 오브젝트로 복사하는 COPY 명령의 변형과 함께 INSERT, UPDATE, DELETE와 같은 데이터 쓰기 작업을 수행할 때를 말합니다. 사용자 액세스 기록은 Account Usage ACCESS_HISTORY 뷰를 쿼리하여 찾을 수 있습니다.

ACCESS_HISTORY 뷰의 각 행에는 SQL 문마다 단 하나의 레코드가 포함됩니다. 레코드는 쿼리가 직간접적으로 액세스한 열(즉, 쿼리를 위한 데이터의 출처인 기본 테이블)을 설명합니다. 사용자(즉, 쿼리 연산자), 쿼리, 테이블 또는 뷰, 열 및 데이터 사이에는 직접 링크가 있기 때문에 이러한 레코드는 규정 준수 감사를 간소화하며 인기 있고 자주 액세스하는 테이블 및 열에 대한 정보를 제공합니다.

참고

Account Usage QUERY_HISTORY 뷰의 레코드가 항상 ACCESS_HISTORY 뷰에 기록되는 것은 아닙니다. SQL 문의 구조에 따라 Snowflake가 ACCESS_HISTORY 뷰에 항목을 기록할지 여부가 결정됩니다.

Snowflake가 ACCESS_HISTORY 뷰에서 지원하는 읽기 및 쓰기 작업에 대한 자세한 내용은 사용법 노트 뷰를 참조하십시오.

읽기 및 쓰기 작업 추적하기

ACCESS_HISTORY 뷰는 다음 열을 포함합니다.

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

마지막 열 OBJECTS_MODIFIED 가 Snowflake 열, 테이블 및 스테이지와 관련된 데이터 쓰기 정보를 지정하는 동안 처음 5개 열을 통해 읽기 작업이 추적됩니다.

Snowflake의 쿼리, 그리고 데이터베이스 오브젝트가 만들어진 방식에 따라, Snowflake가 DIRECT_OBJECTS_ACCESSED, BASE_OBJECTS_ACCESSED 및 OBJECTS_MODIFIED 열에 반환하는 정보가 결정됩니다.

마찬가지로, 쿼리가 행 액세스 정책으로 보호되는 오브젝트 또는 마스킹 정책으로 보호되는 열을 참조하는 경우 Snowflake는 정책 정보를 POLICIES_REFERENCED 열에 기록합니다.

열 세부 정보는 ACCESS_HISTORY 뷰의 섹션을 참조하십시오.

읽기

읽기 쿼리와 ACCESS_HISTORY 뷰가 이 정보를 기록하는 방법을 이해하려면 다음 시나리오를 고려하십시오.

  • 일련의 오브젝트: base_table » view_1 » view_2 » view_3.

  • 다음과 같은 view_2 에 대한 읽기 쿼리:

    select * from view_2;
    
    Copy

이 예에서 Snowflake는 다음을 반환합니다.

  • 쿼리가 view_2 를 지정하므로 DIRECT_OBJECTS_ACCESSED 열의 view_2 를 반환합니다.

  • view_2 에서 데이터의 최초 원본이므로 BASE_OBJECTS_ACCESSED 열의 base_table 을 반환합니다.

view_1view_3 은 DIRECT_OBJECTS_ACCESSED 및 BASE_OBJECTS_ACCESSED 열에 포함되지 않습니다. 이러한 뷰 중 어느 것도 쿼리에 포함되지 않았고, view_2 의 데이터에 대한 소스 역할을 하는 기본 오브젝트가 아니기 때문입니다.

쓰기

쓰기 작업과 ACCESS_HISTORY 뷰가 이 정보를 기록하는 방법을 이해하려면 다음 시나리오를 고려하십시오.

  • 데이터 원본: base_table

  • 데이터 원본에서 테이블 만들기(즉, CTAS):

    create table table_1 as select * from base_table;
    
    Copy

이 예에서 Snowflake는 다음을 반환합니다.

  • 테이블이 직접 액세스되었고 데이터의 원본이므로 BASE_OBJECTS_ACCESSED 및 DIRECT_OBJECTS_ACCESSED 열의 base_table.

  • 테이블을 만들 때 작성된 열이 있는 OBJECTS_MODIFIED 열의 table_1 을 반환합니다.

열 계보

열 계보(즉, 열에 대한 액세스 기록)는 Account Usage ACCESS_HISTORY 뷰를 확장하여 쓰기 작업에서 데이터가 원본 열에서 대상 열로 흐르는 방식을 지정합니다. Snowflake는 계보 체인의 오브젝트가 삭제되지 않는 한 원본 열의 데이터를 참조하는 모든 후속 테이블 오브젝트(예: INSERT, MERGE, CTAS)를 통해 원본 열의 데이터를 추적합니다. Snowflake는 ACCESS_HISTORY 뷰에서 OBJECTS_MODIFIED 열을 강화해 열 계보에 액세스할 수 있도록 합니다.

추가적인 세부 정보는 다음을 참조하십시오.

마스킹 및 행 액세스 정책 참조

POLICY_REFERENCED 열은 행 액세스 정책이나 마스킹 정책으로 보호되는 모든 중간 오브젝트를 포함하여 행 액세스 정책이 테이블에 설정되어 있거나 마스킹 정책이 열에 설정되어 있는 오브젝트를 지정합니다. Snowflake는 테이블 또는 열에 적용되는 정책을 기록합니다.

다음 오브젝트를 고려하십시오.

t1 » v1 » v2

여기서:

  • t1 은 기본 테이블입니다.

  • v1 은 기본 테이블에서 만든 뷰입니다.

  • v2v1 에서 만든 뷰입니다.

사용자가 v2 를 쿼리하는 경우 POLICIES_REFERENCED 열은 v2 를 보호하는 행 액세스 정책, v2 의 열을 보호하는 각 마스킹 정책 또는 적용 가능한 두 종류의 정책을 모두 기록합니다. 또한 이 열에서는 t1v1 을 보호하는 모든 마스킹 정책 또는 행 액세스 정책을 기록합니다.

이러한 레코드는 데이터 관리자가 정책으로 보호되는 오브젝트에 액세스하는 방법을 이해하는 데 도움이 될 수 있습니다.

POLICIES_REFERENCED 열은 ACCESS_HISTORY 뷰에 추가적인 이점을 제공합니다.

  • 지정된 쿼리에서 사용자가 액세스하는 정책으로 보호되는 오브젝트를 식별합니다.

  • 정책 감사 프로세스를 간소화합니다.

    ACCESS_HISTORY 뷰를 쿼리하면 사용자가 액세스하는 보호된 오브젝트와 보호된 열에 대한 정보를 얻기 위해 다른 Account Usage 뷰(예: POLICY_REFERENCESQUERY_HISTORY)에 대한 복잡한 조인이 필요하지 않습니다.

지원되는 작업

ACCESS_HISTORY 뷰가 지원하는 읽기 및 쓰기 작업에 대한 전체 설명은 ACCESS_HISTORY 뷰 의 사용법 노트 섹션을 참조하십시오.

이점

Snowflake의 액세스 기록은 읽기 및 쓰기 작업과 관련하여 다음과 같은 이점을 제공합니다.

데이터 검색

미사용 데이터를 발견하여 데이터의 보관 또는 삭제 여부를 결정합니다.

민감한 데이터의 이동 방식 추적

외부 클라우드 저장소 위치(예: Amazon S3 버킷)에서 대상 Snowflake 테이블로, 또는 그 반대로의 데이터 이동을 추적합니다.

Snowflake 테이블에서 다른 Snowflake 테이블로의 내부 데이터 이동을 추적합니다.

민감한 데이터의 이동을 추적한 후, 데이터를 보호하기 위한 정책(마스킹행 액세스)을 적용하고, 액세스 제어 설정 업데이트하여 스테이지와 테이블에 대한 액세스를 추가로 규제하고, 민감한 데이터가 있는 스테이지, 테이블, 열이 규정 준수 요구 사항에 맞는지 추적할 수 있도록 태그 를 설정합니다.

데이터 유효성 검사

데이터를 원본과 비교해 추적할 수 있으므로 보고서, 대시보드 그리고 차트 및 그래프와 같은 데이터 시각화 제품의 정확성과 무결성이 검증됩니다.

데이터 관리자가 사용자에게 알린 후 주어진 테이블 또는 뷰를 삭제하거나 변경할 수도 있습니다.

규정 준수 감사

테이블 또는 스테이지에서 쓰기 작업을 수행한 Snowflake 사용자와 GDPRCCPA 와 같은 규정 준수 규제를 충족하려고 쓰기 작업이 발생한 시간을 식별합니다.

전반적인 데이터 거버넌스 강화

ACCESS_HISTORY 뷰에서는 액세스한 데이터, 데이터 액세스가 발생한 시간, 액세스한 데이터가 데이터 원본 오브젝트에서 데이터 대상 오브젝트로 이동한 방법을 통합적으로 볼 수 있습니다.

열 계보는 다음과 같은 추가적인 이점을 제공합니다.

파생 오브젝트 보호

데이터 관리자는 파생 오브젝트(예: CTAS)를 만든 후 추가 작업을 수행할 필요 없이 민감한 원본 열을 손쉽게 태그 지정 할 수 있습니다. 그 뒤에, 데이터 관리자는 행 액세스 정책 을 사용하여 민감한 열이 포함된 테이블을 보호하거나 마스킹 정책 또는 태그 기반 마스킹 정책 을 사용하여 민감한 열 자체를 보호할 수 있습니다.

민감한 열 복사 빈도

데이터 개인정보 보호 담당자는 민감한 데이터가 포함된 열의 오브젝트 수(예: 테이블 1개, 뷰 2개)를 재빨리 확인할 수 있습니다. 민감한 데이터가 있는 열이 테이블 오브젝트에 나타나는 횟수를 알면 데이터 개인정보 보호 담당자가 규정 준수 표준(예: 유럽 연합의 일반 데이터 보호 규정(GDPR) 표준을 충족하는 표준)을 충족하는 방법을 입증할 수 있습니다.

근본 원인 분석

열 계보는 데이터를 해당 원본까지 추적하는 메커니즘을 제공하므로, 데이터 품질이 좋지 않아 발생하는 오류 지점을 정확히 찾아내고 문제 해결 프로세스 중에 분석할 열의 개수를 줄이는 데 도움이 될 수 있습니다.

ACCESS_HISTORY 뷰 쿼리하기

다음 섹션에서는 ACCESS_HISTORY 뷰에 대한 쿼리 예시를 제공합니다.

예시 쿼리 중 일부는 쿼리 성능을 향상하기 위해 QUERY_START_TIME 열을 필터링함에 유의하십시오. 성능을 향상하기 위한 다른 옵션은 더 좁은 시간 범위에 대하여 쿼리를 수행하는 것입니다.

예: 쿼리 읽기

아래 하위 섹션에서는 다음 사용 사례에 대한 읽기 작업의 ACCESS_HISTORY 뷰를 쿼리하는 방법을 자세히 설명합니다.

  • 특정 사용자에 대한 액세스 기록을 얻습니다.

  • object_id (예: 테이블 ID)를 기반으로 지난 30일 동안의 민감한 데이터 액세스에 대한 규정 준수 감사를 촉진하여 다음 질문에 답합니다.

    • 누가 데이터에 액세스했습니까?

    • 언제 데이터에 액세스했습니까?

    • 어떤 열에 액세스했습니까?

사용자 액세스 기록 반환하기

가장 최근 액세스부터 시작하여 사용자 및 쿼리 시작 시간별로 정렬된 사용자 액세스 기록을 반환합니다.

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

규정 준수 감사 촉진하기

다음 예는 규정 준수 감사를 용이하게 하는 데 도움이 됩니다.

  • 다음과 같이 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())
    ;
    
    Copy
  • 다음과 같이 object_id32998411400350 을 사용하여, 지난 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())
    ;
    
    Copy
  • object_id32998411400350 을 사용하여, 지난 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'
    ;
    
    Copy

예: 쓰기 작업

아래 하위 섹션에서는 다음 사용 사례에 대한 쓰기 작업의 ACCESS_HISTORY 뷰를 쿼리하는 방법을 자세히 설명합니다.

  • 스테이지에서 테이블로 데이터를 로딩합니다.

  • 테이블에서 스테이지로 데이터를 언로딩합니다.

  • PUT 명령을 사용하여 로컬 파일을 스테이지에 업로드합니다.

  • GET 명령을 사용하여 스테이지에서 로컬 디렉터리로 데이터 파일을 불러옵니다.

  • 민감한 스테이지 데이터 이동 추적.

스테이지에서 테이블로 데이터 로딩하기

외부 클라우드 저장소의 데이터 파일에서 대상 테이블의 열로 값 세트를 로딩합니다.

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

DIRECT_OBJECTS_ACCESSED 및 BASE_OBJECTS_ACCESSED 열은 외부의 명명된 스테이지가 액세스되었음을 지정합니다.

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

OBJECTS_MODIFIED 열은 데이터가 테이블의 두 열에 기록되었음을 지정합니다.

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

테이블에서 스테이지로 데이터 언로딩하기

Snowflake 테이블에서 클라우드 저장소로 값 세트를 언로딩합니다.

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

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
     }
  ]
}
Copy

OBJECTS_MODIFIED 열은 액세스된 데이터가 기록된 스테이지를 지정합니다.

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

PUT 명령을 사용하여 로컬 파일을 스테이지에 업로드하기

데이터 파일을 내부(즉, Snowflake) 스테이지에 복사합니다.

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

DIRECT_OBJECTS_ACCESSED 및 BASE_OBJECTS_ACCESSED 열은 액세스된 파일의 로컬 경로를 지정합니다.

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

OBJECTS_MODIFIED 열은 액세스된 데이터가 기록된 스테이지를 지정합니다.

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

GET 명령을 사용하여 스테이지에서 로컬 디렉터리로 데이터 파일 불러오기

내부 스테이지에서 로컬 컴퓨터의 디렉터리로 데이터 파일을 불러옵니다.

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

DIRECT_OBJECTS_ACCESSED 및 BASE_OBJECTS_ACCESSED 열은 액세스된 스테이지 및 로컬 디렉터리를 지정합니다.

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

OBJECTS_MODIFIED 열은 액세스된 데이터가 기록된 디렉터리를 지정합니다.

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

민감한 스테이지 데이터 이동 추적하기

시간순으로 실행되는 일련의 쿼리를 통해 민감한 스테이지 데이터의 이동을 추적합니다.

다음 쿼리를 실행합니다. 이들 문 중 5개가 스테이지 데이터에 액세스합니다. 따라서 스테이지 액세스를 위해 ACCESS_HISTORY 뷰를 쿼리할 때 결과 세트에 5개의 행이 포함되어야 합니다.

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

여기서:

  • T1, T2T7 은 테이블의 이름을 지정합니다.

  • S1S2 는 스테이지의 이름을 지정합니다.

액세스 기록을 쿼리하여 스테이지 S1 에 대한 액세스를 확인합니다.

DIRECT_OBJECTS_ACCESSED, BASE_OBJECTS_ACCESSED, OBJECTS_MODIFIED 열의 데이터는 다음 테이블에 나와 있습니다.

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

쿼리 예제에 대해 다음 사항에 유의하십시오.

  • 재귀 일반 테이블 식 을 사용합니다.

  • USING 절 대신 JOIN 구문을 사용합니다.

    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

이 쿼리는 스테이지 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

테이블

[《CONTENT》]

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

TEST_DB.TEST_SCHEMA.S2

118

스테이지

[]

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

TEST_DB.TEST_SCHEMA.T2

66568

테이블

[《NAME》,》ID》]

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

TEST_DB.TEST_SCHEMA.T4

66572

테이블

[《ID》,》NAME》]

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

TEST_DB.TEST_SCHEMA.T3

66570

테이블

[《CUSTOMER_INFO》]

예: 열 계보

다음 예제에서는 ACCESS_HISTORY 뷰를 쿼리하고 FLATTEN 함수를 사용하여 OBJECTS_MODIFIED 열을 평면화합니다.

대표적인 예로, Snowflake 계정에서 다음 SQL 쿼리를 실행하여 아래 테이블을 생성하며, 여기서 번호가 매겨진 주석은 다음을 나타냅니다.

  • // 1: directSources 필드와 대상 열 사이의 매핑을 가져옵니다.

  • // 2: baseSources 필드와 대상 열 사이의 매핑을 가져옵니다.

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

반환 결과:

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

예: 행 액세스 정책 참조 추적

행 액세스 정책이 중복 없이 테이블, 뷰 또는 구체화된 뷰에 설정된 경우 다음과 같이 각 인스턴스의 행을 반환합니다.

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

예: 마스킹 정책 참조 추적

마스킹 정책이 중복 없이 열을 보호하는 경우 각 인스턴스의 행을 반환합니다. 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
;
Copy

예: UDF

다음 UDF 예에서는 Account Usage ACCESS_HISTORY 뷰가 기록하는 방식을 보여줍니다.

  • get_product 라는 UDF 호출.

  • get_product 함수를 mydb.tables.t1 이라는 테이블로 호출한 결과물을 삽입.

  • 공유 UDF.

UDF 호출

두 숫자의 곱을 계산해 그 값이 mydb.udfs 라는 스키마에 저장되어 있다고 가정하는 다음 SQL UDF를 생각해 보십시오.

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

get_product 를 직접 호출 하면 DIRECT_OBJECTS_ACCESSED 열에 UDF 세부 정보가 기록됩니다.

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

이 예는 이 항목의 저장 프로시저 호출하기 와 유사합니다.

INSERT DML을 포함한 UDF

mydb.tables.t1 이라는 테이블에서 1과 2라는 열을 업데이트하려면 다음 INSERT 문을 사용해 보십시오.

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

ACCESS_HISTORY 뷰는 get_product 함수를 다음에 기록합니다.

  • DIRECT_OBJECTS_ACCESSED 함수(SQL 문에 함수 이름이 명시적으로 이름이 지정되므로)

  • directSources 배열의 OBJECTS_MODIFIED 열(함수가 열에 삽입되는 값의 소스이므로)

마찬가지로, t1 테이블은 다음과 같은 동일한 열에 기록됩니다.

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

공유 UDF

공유 UDF는 직접 또는 간접적으로 참조할 수 있습니다.

  • 직접 참조는 이 항목의 UDF를 명시적으로 호출하기 와 동일하지만, UDF가 BASE_OBJECTS_ACCESSED 및 DIRECT_OBJECTS_ACCESSED 열에 모두 기록됩니다.

  • 간접 참조의 예는 뷰를 생성하는 UDF를 호출하는 것입니다.

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

    BASE_OBJECTS_ACCESSED 열에는 UDF와 테이블이 기록됩니다.

    DIRECT_OBJECTS_ACCESSED 열에는 뷰가 기록됩니다.

예: 저장 프로시저 호출하기

다음 저장 프로시저가 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);
$$
;
Copy

my_procedure 를 직접 호출 하면 다음과 같이 DIRECT_OBJECTS_ACCESSED 및 BASE_OBJECTS_ACCESSED 열에 모두 프로시저 세부 정보가 기록됩니다.

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

이 예는 이 항목의 UDF 호출하기 와 유사합니다.