어떻게 하면 자연스러운 한국 주소를 만들 수 있을까? 고민하다가 이것도 사람들이 많이 사용하는 주소를 기준으로 하는게 좋을 것 같았다. 그래서 인구기준으로 한국주소를 정리 했다. 행정안전부_지역별(법정동) 성별 연령별 주민등록 인구수(https://www.data.go.kr/data/15099158/fileData.do) 데이터를 통해서 법정동(법정리) 별 인구수를 확인 할 수 있다. 이에 따라서 인구수가 적은 법정동(법정리)는 제거 했다.(전체를 포함할 경우 데이터가 너무 많아진다.)
우리가 사용하는 동 주소에는 행정동과 법정동이 있다. 경우에 따라서 행정동과 법정동 이름 자체가 다른 경우도 꽤 많다. (https://www.seoul.go.kr/seoul/autonomy_sub.do 에서 추가정보를 확인 할 수 있다. ). 그러나 가짜 데이터용을 만들면서 이런 것 까지는 신경쓰기는 힘들다. 

한국에서 주소는 시도, 시군구, 읍면동, 리 로 세분화되어있다. 시도는 특별시, 광역시, 도(경기도, 강원도...) 를 포함하는 체계이고, 시군구는 과천시, 수원시 같은 시와 부여군, 예산군 같은 군과 광진구, 성북구 같은 구를 포함하는 개념으로 경우에 따라서는 성남시 수정구까지를 하나의 구로 볼 수 있다. 읍면동은 우리가 흔히 알고 있는 주소이고, 리는 읍면에서만 사용 되는 주소 체계이다. 

한국 주소 종류는 지번주소와 도로명 주소가 있다. 도로명주소 체계를 확인해보니 도로명으로  ~대로, ~로, ~길이 있고 이 곁가지 도로에 ~대로2길 ~대로2번길   ~로 2길 ~로 2번길  형태로 되어있다.(지역에 따라서 번길을 사용하는지 길을 사용하는지에가 다르다. 두 개가 번호를 붙이는 방식이 다른 방식이라고 한다. ). 그리고 경우에 따라서 ~안길을 사용하는 경우도 있지만 이를 무시했다. 

그래서 대충 아래와 같은방식으로 가짜 주소 데이터를 만들 수 있을 것 같다.
1. 인구수에 따라서 확률적으로 지번주소(address_bunji.csv 이용)를 생성한다.
2. 인구수에 따라서 적당히 동주소를 확장한다. ( ex: 구의동  =>  구의2동, 인구수가 많을 수록 많은 큰 값의 n동이 생성대도록 한다.)
3. 인구수에 따라서 적당히 번-지를 상성한다. ( 인구수가 많으면 큰 번이 생성가능하도록 하고 지는 가능한 100보다 작게 한다. 경우에 따라서는 지가 0일 경우 번만 사용되기도 한다. )
4. address_bunji.csv에 법정동코드가 있는데 이 주소와 일치하는 도로명주소(address_road.csv 이용)를 찾아서 해당 도로명주소중 랜덤하게 선택한다.
5. address_road.csv 파일에 보면 확장형태가 있는데, 이에 따라서 도로명주소에 적당히 N확장형태를 붙여서 주소를 완성한다. ( 강원도,홍천군,가일길,번길  의 경우 가일길2번길 로 확장주소를 만들 수 있다.)
6. 추가적으로 주거 형태(아파트, 빌라, 단독주택)에 따라서 건물명이 필요할 수 있다. 이 부분을 차후 더 분석해보도록 하겠다.  

 

address_bunji.csv
0.38MB
address_road.csv
0.41MB

 

여러 language 에서 DB 테스트나 unittest 를 위한 random 데이터 생성기를 지원한다. Random Data generator 라든지 fake data generator 라는 이름으로 불린다. 이러한 라이브러리는 영어권 데이터는 잘 지원하는데 반해 한국어 데이터는 조금 데이터가 부족하게 있는 경우가 많다. 그래서 한국 사람 이름을 생성하다보면 좀 어색하게 생성되거나 기초 데이터 자체가 좀 적은 경우가 있다. 내 기준에서 한국어 인명 데이터가 가장 충실한 것은 https://github.com/faker-js/faker/blob/main/src/locales/ko/name/first_name.ts  Faker 라는 js 라이브러리이다. 그런데 address 데이터는 좀 아쉬웠다. 그래서 내가 라이브러리 까지 만들것은 아니지만 여러 데이터를 수집하고 있다. 
  우선 사람 이름(성씨 제외)은 대한민국 법원, 전자가족관계등록시스템 통계서비스에서 수집할 수 있다. https://stfamily.scourt.go.kr/st/StFrrStatcsView.do?pgmId=090000000025 에서 출생신고 하는 이름현황과 https://stfamily.scourt.go.kr/st/StFrrStatcsView.do?pgmId=090000000062 에서 개명신고 이름 현황을 확인 할 수 있다. 다만 이 통계가 상위 20개 밖에 안 보여주기 한 번에 얻을 수 없고, 일일 데이터에 대해 통계를 일일이 또는 프로그래밍으로 수집해야 한다. 그리고 이 통계가 대략적인 정보 밖에 수집할 수 없어서 모든 데이터를 획득할 수는 없다. 다만 일일 데이터를 잘 수집하면 한국사람들이 많이 사용하는 이름을 확인 할 수 있다. 
 개인적으로 프로그래밍으로 출생신고 데이터는 2008 부터, 개명신고 데이터는 2012년(2008년 부터 데이터 검색이 되지만 실제 데이터는 2011년 12월 부터 데이터가 존재했다.) 까지 수집해서 그 통계를 기록했다. 3번 이상 중복해서 나타나는 이름만을 첨부해서 올린다. 누군가 fake db 를 만들 때 유용하게 사용했으면 좋겠다.

남자이름.csv
0.02MB
여자이름.csv
0.01MB

  sqlite3 에서 virtual table 을 구현한 것 중 기본 제공하는 기능이 csv 파일을 table 처럼 읽는 기능을 제공하는 csvtab이다. virtual table 을 분석하기 아주 좋은 구조라서 정리해 보았다. 해당 코드는 https://github.com/sqlite/sqlite/blob/version-3.38.5/ext/misc/csv.c  에서 볼 수 있다. 

 우선 csvtab 은 write 하는 기능이 없고, 당연히 transaction 관련 기능도 없다. 그래서 간단한 편이다. 앞에서 설명한(https://yiunsr.tistory.com/879) 함수이름에 x 접두어 대신 csvtab 접두어를 사용하고 있다. 

csvtab 을 이용하는 방법은 

CREATE VIRTUAL TABLE temp.csv USING csv(filename=FILENAME);


 이렇게 하거나 

CREATE VIRTUAL TABLE temp.csv2 USING csv(
    filename = "../http.log",
    schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
);

이렇게 schema 를 전달 할 수 있다. 또 data 인자롤 통해서 실제 csv 데이터를 넣을 수도 있다. 실제 예제를 찾지는 못했지만 아래처럼 될 것 같다.

CREATE VIRTUAL TABLE temp.csv2 USING csv(
    data = "2021-01-01,1.120.12.12,http://google.com,,chrome\n",
    schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
);



CsvReader (https://github.com/sqlite/sqlite/blob/version-3.38.5/ext/misc/csv.c#L74)
* csv 파일에 대한 handler 를 가지고 있고, 해당 파일을 읽은 데이터를 buffer 에 저장해주는 기능을 함
* csv를 파싱할 때 필요한 데이터들을 저장하고 있음. 
* nLine 은 현재 line 을 의미하는데 에러 표시를 위해 사용된다. (컴파일러 이런 것에 몇째 줄에서 오류가 있음. 이런 것 표시할 때 사용하는 line) line 은 csv 와 row 와 다르다. csv 는 column 에 " 를 이용해서 column 안에 Newline(https://ko.wikipedia.org/wiki/%EC%83%88%EC%A4%84_%EB%AC%B8%EC%9E%90) 을 가질 수 있는데 이럴 경우 csv row 는 증가하지 않지만 nLine 은 증가한다. 

CsvTable(https://github.com/sqlite/sqlite/blob/version-3.38.5/ext/misc/csv.c#L307)
* sqlite3_vtab 가 첫 멤버인데, C++의 상속의 역할을 하는 것 같음. using csv 를 사용할 때, 전달되는 paramer 인 filename, data 를 저장하고 csv 파일의 데이터 시작위치(파일 BOM, header 를 제외하고 시작하는 위치), csv 의 column 개수를 저장한다.

static char *csv_read_one_field(CsvReader *p)
* csv데이터를 파싱해서 csv column은 CsvReader.z 에 넣어두고, CsvReader.term 에 column 다음 글자(일반적으로 콤마, newline같은 csv 구분자를 저자하고 있을 것이다. )를 저장한다. 

csvtabCreate
* 내부적으로 csvtabConnect 함수를 호출함. eponymous virtual table 이 되지 않기 위해서 따로 정의해서 사용하고 있음

​csvtabConnect
* CsvTable 메모리 할당
* schema 정의가 안 된 경우 csv 파일을 읽어서 schema 를 자동으로 만들고, 전달된 경우 해당 schema 를 이용해서 sqlite3_declare_vtab 를 호출한다. schema가 정의되어 있지않으면 실제로 csv 파일을 읽거나 생성할 때 전달받은 data 을 받아서 column 개수를 확인 한 후, c1, c2, ... 이런 형태로 column 이름을 정한다. 
* SQLITE_VTAB_DIRECTONLY (trigger 와 view 을 사용할 수 없음)을 설정한다. 

csvtabDisconnect
*
csvtabConnect 에서 할당한 메모리 해제

csvtabBestIndex
* forward full table scan 만을 지원해서 적당히 cost 값을 넣는 것 같다. 어떤 연산이든 상관없이 cost 에 1000000을 넣는다. 

csvtabOpen
* cursor(CsvCursor)에서 사용한 메모리 할당, csv 파일을 open 한다. 

csvtabClose
*
csvtabOpen 에서 사용한 메모리 해제, csv 파일을 닫는다. 

csvtabFilter
* 메모리 할당한 것들에 대해 초기화, 실질적은 동작은 csvtabNext 에서 한다. full table scan 만을 지원하기 때문에 cursor를 csv 파일의 데이터 시작위치로 옮긴다.

csvtabNext
* 하나의 column 을 가져오고 csv 파일의 row의 모든 column 이 csvCursor 에 저장된다. 
* 이 과정에서 cursor의 iRowid 를 값을 증가시키고 값이 없으면 cursor의 iRowid를 -1 로 세팅한다. 
* csv_read_one_field 가 호출되는 과정에서 자동으로 다음 row 를 가져올 수 있도록 설정될 것이다. 

csvtabColumn
* csvCursor 에 저장된 column 데이터를 sqlite3_result_text 를 호출해서 sqlite engine 에 넘겨준다. 
* 실질적으로 데이터가 넘어 가는 부분이다. 

csvtabRowid
* 현재 cursor 의 iRowid 를 리턴한다. 

csvtabEof
* 현재 cursordml iRowid 가 음수이면 eof 이다. 



딱히 뭔가 실질적으로 데이터를 필터링(sql 에서 where 하는 로직때문에 불필요한 row 자체를 전달하지 않는 동작)이 없다. 느낌으로는 sqlite engine 에 다시 뭔가 row 데이터를 검증하는 로직이 있을 것 같다. 뭔가 bloom filter 처럼 아닌 row를 제외해서 csvtabNext 가 알아서 설정하면 효율적으로 동작하고, 아니더라도 sqlite 엔진이 알아서 실제 데이터를 가지고 filtering 해주는 것으로 추정된다.