Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
What to Upload to SlideShare
Loading in …3
×
1 of 20

[Pgday.Seoul 2021] 1. 예제로 살펴보는 포스트그레스큐엘의 독특한 SQL

0

Share

Download to read offline

[Pgday.Seoul 2021] 1. 예제로 살펴보는 포스트그레스큐엘의 독특한 SQL - 김상기

[Pgday.Seoul 2021] 1. 예제로 살펴보는 포스트그레스큐엘의 독특한 SQL

  1. 1. 예제로 살펴보는 포스트그레스큐엘의 독특한 SQL 다른 데이터베이스랑 호환되지 않아요 PgDay.Seoul 2021, 김상기
  2. 2. PgDay.Seoul 2021 Ver. 14 새 SQL ● WITH RECURSIVE ... (...) SEARCH DEPTH FIRST BY order_column SET column_name SELECT … ORDER BY column_name ● WITH RECURSIVE ... (...) CYCLE cycle_check_column SET is_cycle USING column_name SELECT … WHERE is_cycle = false ORDER BY column_name ● jsonb_data['key1']['subkey2']::int + 1 ● SELECT range_agg(range_type_column) FROM table 2
  3. 3. PgDay.Seoul 2021 Ver. 14 새 SQL - WITH RECURSIVE 3 WITH RECURSIVE t AS ( SELECT *, addrname AS conname FROM addrcodes WHERE addrid = '380704' UNION ALL SELECT a.*, a.addrname || ' ' || t.conname FROM addrcodes a, t WHERE a.addrid = t.upaddr ) SEARCH DEPTH FIRST BY addrname SET path CYCLE addrname SET is_cycle USING path2 SELECT conname FROM t WHERE is_cycle = false ORDER BY path DESC FETCH FIRST 1 ROW ONLY
  4. 4. PgDay.Seoul 2021 Ver. 14 새 SQL - jsonb Subscripting "public.t_jsonb" 테이블 필드명 | 종류 | NULL허용 | 초기값 --------------+-------+----------+-------- jsonb_column | jsonb | | postgres=# SELECT jsonb_pretty(jsonb_column) FROM t_jsonb jsonb_pretty { "key1": { "subkey1": 1, "subkey2": 3 } } 4 postgres=# SELECT jsonb_column['key1']['subkey2'] FROM t_jsonb; jsonb_column -------------- 3 postgres=# UPDATE t_jsonb SET jsonb_column['key1']['subkey2'] = to_jsonb((jsonb_column['key1']['subkey2'])::in t + 1) where jsonb_column['key1']['subkey1'] = '1'; -- 문자열은 '문자열'(X), '"문자열"'(O) postgres=# select ...; jsonb_pretty { "key1": { "subkey1": 1, "subkey2": 4 } }
  5. 5. PgDay.Seoul 2021 Ver. 14 새 SQL - multirange 자료형 1 5 이 초록 영역을 어떻게 구할 것인가? multirange! dTS *multirange
  6. 6. PgDay.Seoul 2021 Ver. 14 새 SQL - multirange 자료형 2 6 postgres=# SELECT lower(unnest), upper(unnest) - 1 FROM (SELECT unnest( '{[2021-08-01, 2021-10-01)}'::datemultirange - range_agg(daterange(startdate, enddate, '[]'))) FROM place_term WHERE startdate > '2021-08-01' AND enddate < '2021-09-30') AS t; lower | ?column? ------------+------------ 2021-08-01 | 2021-08-03 2021-09-27 | 2021-09-30 (2개 행)
  7. 7. PgDay.Seoul 2021 대한민국 구석구석 자료 소개 7 ● 한국관광공사에서 제공하는 공공 그래프 데이터베이스 자료 출처: http://data.visitkorea.or.kr/linked_open_data ● 이것을 PostgreSQL용으로 변환하고, 관계형 데이터베이스 모델로 바꿨다. ● 엔터티 관계도: https://github.com/i0seph/visitkorea_for_pg/blob/master/graph2rdbms/visitkor ea-erd.pdf ● flask 로 만든 샘플 코드는 그 위에
  8. 8. PgDay.Seoul 2021 예제 구성 - 첫화면 8 검색: 인덱스를 사용하는 %검색% 분류 검색: 계층형 쿼리와 그 외 추천 장소: 임의 뽑기 최적화 오늘 행사 중인 축제: 범위 자료형 검색
  9. 9. PgDay.Seoul 2021 예제 구성 - 설명 화면 9 분류와 지역: 계층형 쿼리 각 항목: key-value 자료 구조처리 인근 추천 장소: 위경도 인덱스 탐색 및 거리 계산
  10. 10. PgDay.Seoul 2021 SQL 1: DISTINCT ON 10 ● 1:N 관계에서 N 자료 가운데 하나만 뽑기 ○ 전통적으로 row_number() 윈도우 함수를 이용한 인라인뷰를 만들고, 그 값이 1인 것을 뽑음 SELECT …. FROM ( SELECT …, row_number() OVER (PARTITION BY … ORDER BY …) as no FROM …) AS t WHERE no = 1 ○ 이 복잡한 쿼리가 DISTINCT ON으로 해결 가능함 (sql_search_list.py) SELECT DISTINCT ON (a.place_name, a.place_id) a.place_id, a.place_name, b.imgurl FROM place a LEFT JOIN place_images b ON a.place_id = b.place_id WHERE place_name ~* to_regexp('자연 휴양림') ORDER BY a.place_name, a.place_id, b.imgurl FETCH FIRST 50 ROWS ONLY
  11. 11. PgDay.Seoul 2021 SQL 2: 문자열 검색 11 ● 전통 기법: LIKE, ILIKE ○ 인덱스를 사용하지 않아 테이블 전체 탐색을 함, WHERE place_name LIKE %자연휴양림% ○ 단어 분리 상황에서 AND 연산 중복 비용 발생, WHERE place_name LIKE %자연% AND place_name LIKE %휴양림% ● pg_trgm 확장 모듈과 정규식을 이용한 문제 풀기 (sql_search_list.py) 1. CREATE EXTENSION pg_trgm 2. CREATE INDEX place_name_i ON place USING gist_trgm_ops (place_name) 3. CREATE OR REPLACE FUNCTION to_regexp(text) RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT string_agg('(?=.*' || a || ')','') FROM UNNEST(tsvector_to_array(to_tsvector('simple', $1))) a $function$ 4. SELECT ... FROM place a LEFT JOIN place_images b ON a.place_id = b.place_id WHERE a.place_name ~* to_regexp('자연 휴양림') ...
  12. 12. PgDay.Seoul 2021 SQL 3: 배열 검색 12 ● 전통 기법: IN ○ WHERE col IN (1,2,3,4) ● pg의 또 다른 문법: ANY ○ WHERE col = ANY(ARRAY[1,2,3,4]) ○ 더 복잡한 문법을 왜 쓰지? SQLAlchemy 모듈에서 변수 바인딩이 쉬워짐 ○ sql_place_list.py 참조 SELECT addrid FROM addrcodes WHERE upaddr = ANY(:locastr) query_string으로 받은 문자열 loca 값을 loca.split() 함수로 배열 locastr 으로 바꾸고 그것을 그대로 쿼리로 넘겨 넘겨주면, SQLAlchemy 모듈이 알아서 처리해줌 ○ 대부분 응용 프로그램 pg용 DB 드라이버들이 이 배열처리를 편하게 쓸 수 있도록 각자의 방법을 제공함
  13. 13. PgDay.Seoul 2021 SQL 4: 실무 예제와 쿼리 최적화 13 SELECT DISTINCT ON (a.place_id, a.place_name) a.place_id, a.place_name, c.imgurl FROM place a, (SELECT place_id FROM place WHERE loca IN (SELECT addrid FROM addrcodes WHERE upaddr = ANY (ARRAY['380300'])) AND cate IN ( WITH RECURSIVE t AS ( SELECT * FROM tourism WHERE uptour = ANY (ARRAY['A05', 'B02']) UNION ALL SELECT a.* FROM tourism a, t WHERE a.uptour = t.tourid) SELECT tourid FROM t WHERE length(tourid) = 9) ORDER BY place_name FETCH FIRST 50 ROWS ONLY) b LEFT JOIN place_images c ON b.place_id = c.place_id WHERE a.place_id = b.place_id ORDER BY a.place_name, a.place_id, c.imgurl; ● 전라남도 곡성군 음식점과 숙박 시설 찾는 쿼리
  14. 14. PgDay.Seoul 2021 SQL 5: TABLESAMPLE - 임의 자료 탐색 14 ● 전통적인 ORDER BY random() LIMIT 10 구문은 테이블 전체를 탐색한다. ● TABLESAMPLE {SYSTEM|BERNOULLI} (퍼센트) ○ SYSTEM: 임의 블록으로 가서 그 해당 자료 수 만큼 추출 (퍼센트 값이 아주 작다면 하나의 블록 읽기만 하는데, 그 안에 비슷한 자료들이 몰려 있으면 임의 추출의 의미가 퇴색함) ○ BERNOULLI: 블록 기준으로 해당 퍼센트 확률만큼 블록을 선택하고 거기서 자료를 추출 (확률값이 낮을 수록 더 많은 블록을 뒤지게 된다. 반면 임의 추출 품질은 좋아짐) ● place_images 테이블(블록수: 약 1,400개) 대상 베르누이 샘풀링 확률 보면, ○ 1% = 18, 0.1% = 43, 0.01% = 600 정도의 블록을 읽음으로 예제 코드에서는 0.05로 설정함 ○ 이처럼 베르누이 확률을 사용할 경우는 비용과 품질 사이 적정값을 찾아야 함 SELECT DISTINCT ON (place_id) place_id, imgurl FROM ( SELECT place_id, imgurl FROM place_images TABLESAMPLE BERNOULLI (0.05) LIMIT 20 ) a ORDER BY place_id, imgurl LIMIT 10
  15. 15. PgDay.Seoul 2021 SQL 6: 범위 자료형 탐색 - 오늘 방문하면 되는 곳 15 ● 전통적인 범위, 기간 검색은 시작값(lower)과 마침값(upper)을 저장하고, >=, <, BETWEEN 연산으로 처리 함. ○ 시작날짜가 오늘보다 작거나 같고, 마침날짜가 오늘보다 크거나 같은 것 startdate <= current_date and enddate >= current_date ● 범위자료형을 쓰면 ○ 행사기간들 중에 오늘이 포함된 것 term @> current_date ● 전통적인 모델링을 바꿀 수 없다면 (sql_festa_list.py) ○ 범위자료형 변환 함수를 사용하는 함수 기반 인덱스를 만들어 쓴다 CREATE INDEX term_range_i ON place_term USING gist (daterange(startdate, enddate, '[]')); SELECT * FROM place_term WHERE daterange(startdate, enddate, '[]') @> CURRENT_DATE AND daterange(date_trunc('month' , current_timestamp)::date, (date_trunc('month' , current_timestamp) + interval '1 month')::date) @> daterange(startdate, enddate, '[]')
  16. 16. PgDay.Seoul 2021 SQL 7: key-value 자료 처리 - json 16 ● 처음부터 jsonb, json 자료형으로 DB에 넣고 꺼내기 ○ json 양식 검사 문제 ○ jsonb 인 경우 key 정렬 문제 ○ 해당 칼럼이 toast에 저장되는 문제 ● DB에는 관계형으로 저장되고, 응용프로그램에서 json으로 다루기 ○ 어디서 json으로 변환할 것인가? ■ DB측: json_build_*(), json_agg() 함수를 사용 ■ APP측: DB result row의 유연한 json 변환 작업을 제공해야함
  17. 17. PgDay.Seoul 2021 SQL 7: key-value 자료 처리 - 예제 17 ● 예제에서는 N:N 관계형 모델링 자료에 대한 처리를 다룬다. ● DB 측 ● 응용프로그램 측(python flask) SELECT b.attname, a.v FROM place_attrib a JOIN attnames b ON a.attid = b.attid WHERE place_id = 130679; attname | v ----------------------+------------------------------------ 규모 | 지상 3층 신용카드사용여부 | 불가능 전화번호 | 033-462-2303 유모차대여서비스 | 불가능 주소 | 강원도 인제군 북면 만해로 91 이용요금 | 무료 애완동물동반가능여부 | 불가능 우편번호 | 24606 이용시간 | 09:00~17:00 쉬는날 | 매주 월요일, 1월 1일, 설/추석 당일 주차시설 | 주차 가능 @app.route('/ajax/getattrib/<int:place_id>') def get_attribute(place_id): mod = __import__('sql_get_attribute') d = sql(mod.query, {'place_id': place_id}) return jsonify([dict(row) for row in d.fetchall()])
  18. 18. PgDay.Seoul 2021 SQL 8: 위경도 처리 18 ● 위경도값 처리를 위한 작업 ○ WGS84 좌표계를 사용하는 위경도값이라면, 가장 단순한 방법으로 earthdistance 확장 모듈을 사용하는 것이다. CREATE EXTENSION earthdistance CASCADE; ALTER TABLE place ADD position earth GENERATED ALWAYS AS (ll_to_earth(lat, long)) STORED; -- INSERT 작업에서는 이 position 칼럼의 값으로 default를 쓴다 CREATE INDEX CONCURRENTLY place_position_i ON place USING gist (position); -- 자료 찾기, :y1 = 현재 위도, :x1 = 현재 경도, :place_id = 현재 장소번호 -- 현재 위치에서 2Km 안에 있는 다른 장소들 찾기 SELECT place_id, round((earth_distance(position, ll_to_earth(:y1, :x1))::numeric / 1000)::numeric, 2)::float as distance FROM place WHERE earth_box(ll_to_earth(:y1, :x1), 2000) @> position AND a.place_id <> :place_id
  19. 19. PgDay.Seoul 2021 그 외 SQL 들 19 ● 형변환자: :: ● {INSERT|UPDATE|DELETE} … RETURNING … ● JOIN UPDATE: UPDATE … FROM ● JOIN DELETE: DELETE FROM … USING … ● INSERT or UPDATE: INSERT INTO … ON CONFLICT … ● 명령어 VAULES: SELECT * FROM (VALUES(1,2,3)) t ● LATERAL 예약어: SELECT … FROM a, LATERAL (SELECT * FROM b where b.col = a.col) … ● LISTEN & NOTIFY
  20. 20. PgDay.Seoul 2021 참고 자료 20 ● python flask ○ flask.palletsprojects.com ● jquery ○ jquery.com ● PostgreSQL ○ postgresql.org & postgresql.kr ● 발표를 위한 샘플 코드 ○ https://github.com/i0seph/visitkorea_for_pg ● PostgreSQL 한국 사용자 모임 ○ https://www.facebook.com/groups/postgres.kr

×