데이터과학 유망주의 매일 글쓰기 — 46일차
생각보다 복잡한 데이터베이스의 가족 관계
# schema, #관계, #데이터베이스, #SQLite
오늘 한일:
오늘 부터는 SQL을 탐구하는, 일주일간의 새로운 작은 여정을 시작했다. SQL은 Structured Query Language(구조화된 쿼리 언어)의 약자로써, 관계형 데이터베이스(Relational Database)에 접근할 수 있는 언어이다. 파이썬에서 Pandas를 통해 데이터를 탐구하는 방법과 매우 비슷하지만, 파이썬에서는 프로그램이 동작하는 동안의 Runtime이 끝나면 데이터가 저장되지 않는 단점이있었다. 이를 보완하고, 데이터를 장기적으로 저장하여 원할 때마다 접근할 수 있게 해주는 것이 SQL 기반 관계형 데이터베이스였다.
관계형 데이터베이스(Relational Database)의 관계
Primary Key & Foreign Key
SQL기반 관계형 데이터베이스는 생각보다 상당히 복잡한 관계를 가질 수 있는데, 데이터가 서로 primary key와 foreign key라는 것으로 연결되어 있기 때문이다. 예를 들면 아래와 같은 Schema를 가질 수 있다.
예를 들어, 위 customers라는 테이블과 employees라는 테이블 사이에는, SupportRepId와 EmployeeId라는 key가 연결된 것을 알 수 있다. 여기서 employees를 기준으로 볼 때, customer와 연결하는 primary key는 EmployeeId이며, customers에 존재하는 SupportRepId는 foreign key로 볼 수 있다. Primary Key는 다른 테이블과 연결하기 위해 employees테이블이 갖는 키이며, SupportRepId는 customers테이블이 가지고 있지 않은 key이지만, EmployeeId와 같은 값을 가지고 있다고 보기 때문에 Foreign Key인 것이다. 여기서 주의할 것은 두 테이블의 Key 이름이 다르더라도, 두 키에 해당하는 값이 모두 같기 때문에 연결이 가능하다는 것이다.
관계 종류
관계형 데이터베이스의 관계는 크게 4가지로 나뉜다. 자가 참조(self-referencing)는 한 테이블 내에서의 관계를 말하며, 이외의 세 경우는 두 개 이상의 테이블 간 관계로 볼 수 있다. 각 관계를 좀 더 명확하게 보여주기 위해 SQL대신 파이썬을 사용해 표현하였다. 주로 행(row)간의 관계를 표현하는데, 데이터의 각 행은 레코드(record)라 부른다.
- 자가 참조(self-referencing)
- 1:1 관계
- 1:N 관계
- N:N 관계
- 자가 참조(self-referencing):
테이블 내에서 관계를 정의할 필요가 있는 경우가 있다. 예를 들어 내가 속한 회사에서, 내가 보고 해야할 상사들과 나의 관계를 명확하게 할 필요가 있을 수 있다. 또한, 넷플릭스같은 인터넷 서비스에 가입할 때, 추천인을 입력하여 보너스 크레딧 등을 얻고자 하는 경우에 활용될 수 있다.
# 자가 참조의 예from pandas import DataFrameprint("넷플릭스 추천인\n")print(DataFrame({"Customers":["승태", "도일", "명한"], "reference_id":['없음', 0, 0]}))
추천인 입력의 성격상, 한 명은 각자 한 사람만 추천을 할 수 있지만, 추천인을 입력하는데 있어서는 제한이 없다. 승태를 아무도 추천인으로 입력하지 않았지만, 도일과 명한은 추천인으로 입력이 된 것을 알 수 있다. 아무래도 도일은 명한을 추천하고, 명한도 도일을 추천했으나, 승태는 아무도 추천하지 않은 것임을 알 수 있다. 이 처럼 데이터 안에서도, 서로 다른 행(row)끼리의 관계를 정의할 수 있게 하는 것이 자가 참조(self-referencing)관계이다.
2. 1:1 관계
한 테이블의 레코드와 다른 테이블의 한 레코드가 연결되어 있는 관계를 말한다. 즉 1:1로 맵핑이 되어있는 경우를 말한다.
# 1:1 관계의 예from pandas import DataFrameprint("넷플릭스 추천인 정보 1\n")print(DataFrame({"Customers":["승태", "도일", "명한"], "Job":["데이터 사이언티스트", "머신러닝 엔지니어", "딥러닝 엔지니어"], "contact_id":[1000, 1001, 1002]}))print(DataFrame({"Customers":["승태", "도일", "명한"], "reference_id":['없음', 0, 0]}))print('\n\n')print("넷플릭스 추천인 정보 2\n")print(DataFrame(index=[1000, 1001, 1002], data={"Contact":["010-1234-5678", "010-2345-6789", "011-3456-7890"]}))
첫 번째 테이블에서 contact_id의 Key가 그대로 두 번째 테이블의 index와 똑같은 값으로 맵핑되어 있는 것을 볼 수 있다. 첫 번째 테이블에서 각각의 레코드가 두 번째 테이블의 각각의 레코드에 맵핑되어 있으므로 1:1의 관계로 볼 수 있다. 여기서 첫 번째 테이블의 contact_id가 두 번째 테이블의 Foreign key 역할을 하며, 두 번재 테이블의 index는 Primary Key라고 할 수 있다.
하지만, 자세히 보면 이렇게 데이터를 관리하는 것이 상당히 비효율적임을 알 수 있다. 하나의 테이블에 전화번호 정보인 contact 정보도 같이 포함할 수 있기 때문이다. 용량이 너무 커서 같이 저장하기 힘든 경우가 아니라면, 또는 특별한 관리 목적이 있는 것이 아니라면, 굳이 두 개의 테이블을 나누어서 봐야할 필요성이 떨어진다는 것이다. 왠만하면 1:1에서는 서로 연결되어있는 Key를 기준으로(첫 번째 테이블의 contact_id와 두 번째 테이블의 index) 같이 입력해 주는 것이 좋다. 따라서 1:1 관계를 정의하는 것은, 관계형 데이터베이스를 사용하는 의의를 상대적으로 떨어뜨리기 때문에, 상당히 드문 경우라 볼 수 있다.
3. 1:N 관계
한 테이블에서 하나의 레코드가 다른 테이블에 있는 두 개 이상의 레코드에 맵핑된 경우를 말한다.
# 1:N 관계의 예from pandas import DataFrameprint("넷플릭스 추천인 정보 1\n")print(DataFrame(index=[1000, 1001, 1002], data={"Order Item":["500일의 여름", "킹덤", "데어데블"], "customer_id":[0, 0, 1]}))print('\n\n')print("넷플릭스 추천인 정보 2\n")print(DataFrame({"Customers":["승태", "도일", "명한"], "Job":["데이터 사이언티스트", "머신러닝 엔지니어", "딥러닝 엔지니어"]}))
위 예를 보면, 한 사람이 여러가지를 주문할 수 있지만, 각 주문은 한 사람에게만 맵핑될 수 있다는 것을 알 수 있다. 예를 들어, 첫 번째 테이블에서 customer_id는 두 개가 0인 것을 볼 수 있다. 이는 승태가 “500일의 여름"과 “킹덤" 두 가지의 상품을 주문했기 때문이다. 한 사람이 여러개를 주문했지만, 각 주문은 오직 한 사람에게만 지정된다. 나머지 하나인 “데어데블"은 도일이 주문한 것을 알 수 있다. 관계형 데이터베이스에서 가장 흔히 볼 수 있는 경우라 할 수 있다.
4. N:N 관계
한 테이블의 여러 레코드가 다른 테이블의 여러 레코드와 연결되는 관계를 말한다. 하나의 레코드가 여러개에 연결될 수 있고, 연결된 레코드도 역시 여러개의 다른 레코드에 연결될 수 있기 때문에, 가장 복잡한 형태라 할 수 있다.
# N:N 관계의 예from pandas import DataFrameprint("넷플릭스 추천인 정보 1\n")print(DataFrame(index=[1000, 1001, 1002], data={"Order Item":["500일의 여름", "킹덤", "데어데블"], "customer_id":[0, 0, 1]}))print('\n\n')print("넷플릭스 추천인 정보 2\n")print(DataFrame({"Customers":["승태", "도일", "명한"], "Job":["데이터 사이언티스트", "머신러닝 엔지니어", "딥러닝 엔지니어"]}))print('\n\n')print("넷플릭스 추천인 정보 3\n")print(DataFrame(data={"customer_id":[0, 0, 1, 2, 1], "product_id":[1000, 1001, 1002, 1001, 1000]}))
위 예에서는 한 사람이 여러 개의 상품을 주문할 수 있으며, 한 상품 역시 여러 사람들에게 주문 될 수 있다. 세 번째 테이블은 각 사람을 구분하는 customer_id와 각 상품을 구분하는product_id를 연결해 주는 역할이며, 이를 통해 각 사람이 어떤 상품을 주문했고, 각 상품이 어떤 사람들에게 주문되었는지 알 수 있다. 주의할 것은, 이렇게 세 번째 테이블처럼 이전 테이블과 새로운 Key를 바탕으로 테이블을 생성하게되면, 동일하게 Primary Key가 있어야 한다는 것이다. (두 번째 테이블에서의 Index와 세 번째 테이블의 customer_id, 또는 첫 번째 테이블의 index와 세 번째 테이블의 product_id)
앞으로 할일:
오늘은 관계형 데이터베이스의 다양한 관계의 종류에 대해 알아보았다. 막상 SQL언어를 배웠을 때는 어렵지 않았지만, 이를 통해 테이블 사이의 좀 더 복잡한 관계를 표현하려고 하니, 정말 쉽게 “더 어려워졌다.” 물론, 여전히 주어진 과제를 하는데는 성공하기는 했지만.
일단 SQL은 pandas에 비해 훨씬 더 언어가 직관적이다. 그래서, 오히려 언어 자체는 좀 더 쉽게 느껴졌던 것 같다. 물론, 파이썬처럼 pandas같은 놀라운 라이브러리를 지원하여 사용자가 편리하게 알아서 다 처리해주지는 않는다. 그래서, 오히려 언어를 사용하여 내가 원하는 것을 표현하는데 있어서는, 상대적으로 더 단순한 SQL이 더 어렵게 느껴진 것 같다.
오늘 SQL을 하면서, 좀 더 낮은 레벨의 언어를 배우는 게 그렇게 나쁘지는 않다는 생각이 들었다. 한편으로는, 좀 더 높은 레벨의 언어이지만, 많은 반복적인 일을 자동화 해주는 파이썬의 편리한 여러 라이브러리들이 좀 더 감사하게 느껴지기도 했다. 정말, 이런 라이브러리를 자동화하여 배포한 사람들에게 큰 존경심이 들지 않을 수 없었다. 그들 덕분에 정말 코딩을 배우고 사용하기 편해졌다.
내일까지는 SQL기반 데이터베이스를 배우고, 모래부터는 NoSQL기반 데이터베이스를 배운다고 한다. 대표적으로 MongoDB가 있는데, 참 반가운 소식이다. 내가 예전에 참여한 주말반 부트캠프에서 MongoDB를 활용하여 개인 프로젝트를 해 본 경험이 있기 때문이다. 그래서, 좀 더 기대된다. 과연 수요일에 배울 내용은 오늘과 내일 보다 어떻게 다를지.
일단, 조금 어렵게 느껴졌던 SQL과 좀 더 친해져서 다행이다. 앞으로도 친해질 수 있도록 좀 더 많은 연습을 해야겠다고 다짐해본다.
참조: