Successfully reported this slideshow.
Your SlideShare is downloading. ×

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

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 20 Ad
Advertisement

More Related Content

Slideshows for you (20)

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

Advertisement

More from PgDay.Seoul (20)

Recently uploaded (20)

Advertisement

[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

×