person_id 익명화

CDM을 이용하는 사람이 생김에 따라서 person_id를 한 번 더 익명화하기로 결심했다. 매번 CDM에 자료가 추가되는 방법이면 난수를 생성하는 방법을 고민해야 할 것이다. 하지만, 수정하면서 자료를 추가하고 있기 때문에 매번 새로운 수를 만들어도 될 것 같았다. 수십만건의 난수를 효과적으로 만들기 위하여 여러 방법을 시도해 보았다. R이라면 sample(, replace = F)로 만들면 그만인데, SQL에서는 그런 명령어는 못 찾았다. 그래서 비슷한 개념으로 만들어 쿼리를 작성해 보았다. 

CREATE TABLE mapping.person_anonymous_from AS SELECT DISTINCT person_id FROM cdm.person;
ALTER TABLE mapping.person_anonymous_from ADD COLUMN temp_join_a bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 );

우선 person 테이블에서 고유한 person_id를 추출하여 테이블을 하나 만든다. 그리고 생성된 테이블에서 bigint 속성을 가지며 추가될 때마다 1씩 커지는 temp_join_a 컬럼을 만든다.

CREATE TABLE mapping.person_anonymous_to AS (SELECT DISTINCT trunc(random() * 1e8 + 1) AS random_key FROM generate_series(1, 1e8));
ALTER TABLE mapping.person_anonymous_to ADD COLUMN temp_join_b bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 );

random() 함수는 0~1의 실수값을 생성하기 때문에 정수값으로 변환한다. 병원 환자 수를 보니 1천만개 정도 만들면 몇 년은 충분할 것 같았다. 그래서 생성된 난수에 1천만을 곱하고 1을 더한 후 정수값으로 처리한 작업을 1천만번 반복한다. 이런 방법으로 난수를 만들면 중복이 생기기 때문에 고유한 값만을 선택하여 이 값을 다른 테이블에 random_key라는 컬럼 이름으로 만든다. 이 방법은 중복되지 않는 수를 생성하는데에는 좀 멍청한 것 같다. 1억개를 만들면 60% 정도만 남는다. 그리고 마찬가지로 temp_join_b 컬럼을 만든다.

CREATE TABLE mapping.person_anonymous AS (SELECT a.person_id, b.random_key FROM mapping.person_anonymous_from AS a INNER JOIN mapping.person_anonymous_to AS b ON a.temp_join_a = b.temp_join_b);

이렇게 만들어진 random_key의 순서는 충분히 임의적이라고 할 수 있다. temp_join_a에 맞추어 INNER JOIN의 형태로 결합하여 테이블을 만든다. random_key가 person_id보다 충분히 숫자가 크기 때문에 공통인 것만 선택을 하면 된다. 이러면 충분히 식별력이 없는 새로운 person_id를 만들어 낼 수 있다.

PostgreSQL 문서에 의하면 distinct는 처음 나타나는 순서대로 정렬이 되는 것이라고 한다.