SQL(8)(writing subqueries, window functions)
WRITING SUBQUERIES
하위쿼리는 쿼리내의 여러 위치에서 사용할 수 있지만 from문으로 시작하는 것이 가장 쉽다.
1 2 3 4 5 6 7 | SELECT sub.* FROM ( SELECT * FROM tutorial.sf_crime_incidents_2014_01 WHERE day_of_week = 'Friday' ) sub WHERE sub.resolution = 'NONE' | cs |
1 2 3 | SELECT * FROM tutorial.sf_crime_incidents_2014_01 WHERE day_of_week = 'Friday' | cs |
연습문제
모든 Warrant Arrests를 선택하는 쿼리를 작성하고 해결되지 않은 사건만 표시하는 외부쿼리로 래핑
1 2 3 4 5 6 7 | SELECT sub.* FROM ( SELECT * FROM tutorial.sf_crime_incidents_2014_01 WHERE descript = 'WARRANT ARREST' ) sub WHERE sub.resolution = 'NONE' | cs |
하위 쿼리를 사용하여 여러 단계로 집계
1 2 3 4 5 6 7 8 9 10 11 12 | SELECT LEFT(sub.date, 2) AS cleaned_month, sub.day_of_week, AVG(sub.incidents) AS average_incidents FROM ( SELECT day_of_week, date, COUNT(incidnt_num) AS incidents FROM tutorial.sf_crime_incidents_2014_01 GROUP BY cleaned_month,sub.day_of_week ) sub GROUP BY cleaned_month,sub.day_of_week ORDER BY cleaned_month,sub.day_of_week | cs |
일반적으로 내부 쿼리를 먼저 작성하고 결과가 이해 될 때까지 수정 한 다음 외부 쿼리로 이동하는 것이 가장 쉽다.
연습문제
각 범주에 대한 월 평균 사건 수를 표시하는 쿼리
1 2 3 4 5 6 7 8 9 10 | SELECT sub.category, AVG(sub.incidents) AS avg_incidents_per_month FROM ( SELECT EXTRACT('month' FROM cleaned_date) AS month, category, COUNT(1) AS incidents FROM tutorial.sf_crime_incidents_cleandate GROUP BY sub.category,avg_incidents_per_month ) sub GROUP BY sub.category | cs |
조건부 논리의 하위 쿼리
1 2 3 4 5 | SELECT * FROM tutorial.sf_crime_incidents_2014_01 WHERE Date = (SELECT MIN(date) FROM tutorial.sf_crime_incidents_2014_01 ) | cs |
위 쿼리는 하위 쿼리의 결과가 하나의 셀이기 때문에 작동한다.
in 내부 쿼리에 여러 결과가 포함된 경우 작동하는 조건부 논리 유형은 다음과 같다.
1 2 3 4 5 6 7 | SELECT * FROM tutorial.sf_crime_incidents_2014_01 WHERE Date IN (SELECT date FROM tutorial.sf_crime_incidents_2014_01 ORDER BY date LIMIT 5 ) | cs |
하위 쿼리 결합
조인에서 쿼리를 필터링 할 수 있다. where절에서 필터링하는 것이 아니라 외부 쿼리와 동일한 테이블을 조회하는 하위 쿼리를 조인하는 것이 일반적이다. 다음 쿼리는 이전 예제와 동일한 결과를 생성한다.
1 2 3 4 5 6 7 8 | SELECT * FROM tutorial.sf_crime_incidents_2014_01 incidents JOIN ( SELECT date FROM tutorial.sf_crime_incidents_2014_01 ORDER BY date LIMIT 5 ) sub ON incidents.date = sub.date | cs |
1 2 3 4 5 6 7 8 9 10 | SELECT incidents.*, sub.incidents AS incidents_that_day FROM tutorial.sf_crime_incidents_2014_01 incidents JOIN ( SELECT date, COUNT(incidnt_num) AS incidents FROM tutorial.sf_crime_incidents_2014_01 GROUP BY 1 ) sub ON incidents.date = sub.date ORDER BY sub.incidents DESC, time | cs |
위의 쿼리는 지정된 날짜에 보고 된 사고 수에 따라 모든 결과의 순위를 지정한다. 내부 쿼리에서 매일 총 인시던트 수를 집계한 다음 해당 값을 사용하여 외부 쿼리를 정렬하여 이를 수행한다.
연습문제
보고된 사고가 가장 적은 세 가지 범주의 모든 행을 표시하는 쿼리
1 2 3 4 5 6 7 8 9 10 11 12 | SELECT incidents.*, sub.count AS total_incidents_in_category FROM tutorial.sf_crime_incidents_2014_01 incidents JOIN ( SELECT category, COUNT(*) AS count FROM tutorial.sf_crime_incidents_2014_01 GROUP BY 1 ORDER BY 2 LIMIT 3 ) sub ON sub.category = incidents.category | cs |
하위 쿼리는 쿼리 성능을 향상시키는데 매우 유용할 수 있다.
1 2 3 4 | SELECT COUNT(*) FROM tutorial.crunchbase_acquisitions acquisitions FULL JOIN tutorial.crunchbase_investments investments ON acquisitions.acquired_month = investments.funded_month | cs |
7,414 * 83,893
두 테이블을 개별적으로 집계한 다음 이를 결합하여 훨씬 더 작은 데이터 세트에 걸쳐 카운틀를 수행하면 이 문제를 더 효율적으로 해결할 수 있다. 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | SELECT COALESCE(acquisitions.month, investments.month) AS month, acquisitions.companies_acquired, investments.companies_rec_investment FROM ( SELECT acquired_month AS month, COUNT(DISTINCT company_permalink) AS companies_acquired FROM tutorial.crunchbase_acquisitions GROUP BY 1 ) acquisitions FULL JOIN ( SELECT funded_month AS month, COUNT(DISTINCT company_permalink) AS companies_rec_investment FROM tutorial.crunchbase_investments GROUP BY 1 )investments ON acquisitions.month = investments.month ORDER BY 1 DESC | cs |
연습문제
2012년 1분기부터 분기별로 설립되고 인수된 회사 수를 계산하는 쿼리를 작성. 두 개의 개별 쿼리에서 집계를 만든 다음 조인.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | SELECT COALESCE(companies.quarter, acquisitions.quarter) AS quarter, companies.companies_founded, acquisitions.companies_acquired FROM ( SELECT founded_quarter AS quarter, COUNT(permalink) AS companies_founded FROM tutorial.crunchbase_companies WHERE founded_year >= 2012 GROUP BY 1 ) companies LEFT JOIN ( SELECT acquired_quarter AS quarter, COUNT(DISTINCT company_permalink) AS companies_acquired FROM tutorial.crunchbase_acquisitions WHERE acquired_year >= 2012 GROUP BY 1 ) acquisitions ON companies.quarter = acquisitions.quarter ORDER BY 1 | cs |
하위쿼리 및 UNION
1 2 3 4 5 6 7 | SELECT * FROM tutorial.crunchbase_investments_part1 UNION ALL SELECT * FROM tutorial.crunchbase_investments_part2 | cs |
1 2 3 4 5 6 7 8 9 10 | SELECT COUNT(*) AS total_rows FROM ( SELECT * FROM tutorial.crunchbase_investments_part1 UNION ALL SELECT * FROM tutorial.crunchbase_investments_part2 ) sub | cs |
연습문제
위와 결합된 데이터 세트에서 투자자가 수행한 총 투자수를 기준으로 순위를 매기는 쿼리
1 2 3 4 5 6 7 8 9 10 11 12 13 | SELECT investor_name, COUNT(*) AS investments FROM ( SELECT * FROM tutorial.crunchbase_investments_part1 UNION ALL SELECT * FROM tutorial.crunchbase_investments_part2 ) sub GROUP BY 1 ORDER BY 2 DESC | cs |
아직 운영중인 회사만 제외하고 이전 문제와 동일한 작업을 수행하는 쿼리를 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | SELECT investments.investor_name, COUNT(investments.*) AS investments FROM tutorial.crunchbase_companies companies JOIN ( SELECT * FROM tutorial.crunchbase_investments_part1 UNION ALL SELECT * FROM tutorial.crunchbase_investments_part2 ) investments ON investments.company_permalink = companies.permalink WHERE companies.status = 'operating' GROUP BY 1 ORDER BY 2 DESC | cs |
WINDOW FUNCTIONS
1 2 3 | SELECT duration_seconds, SUM(duration_seconds) OVER (ORDER BY start_time) AS running_total FROM tutorial.dc_bikeshare_q1_2012 | cs |
1 2 3 4 5 6 7 | SELECT start_terminal, duration_seconds, SUM(duration_seconds) OVER (PARTITION BY start_terminal ORDER BY start_time) AS running_total FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' | cs |
연습문제
각 라이드의 지속 시간을 각 start_terminal에서 라이더가 누적한 총 시간의 백분율로 표시하는 쿼리
1 2 3 4 5 6 7 | SELECT start_terminal, duration_seconds, SUM(duration_seconds) OVER (PARTITION BY start_terminal) AS start_terminal_sum, (duration_seconds/SUM(duration_seconds) OVER (PARTITION BY start_terminal))*100 AS pct_of_total_time FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' ORDER BY 1, 4 DESC | cs |
SUM, COUNT, AVG
1 2 3 4 5 6 7 8 9 10 11 12 13 | SELECT start_terminal, duration_seconds, SUM(duration_seconds) OVER (PARTITION BY start_terminal ORDER BY start_time) AS running_total, COUNT(duration_seconds) OVER (PARTITION BY start_terminal ORDER BY start_time) AS running_count, AVG(duration_seconds) OVER (PARTITION BY start_terminal ORDER BY start_time) AS running_avg FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' | cs |
연습문제
자전거 타기 지속 시간의 누적합계를 표시하지만 위 처럼 그룹화하고 지속시간이 내림차순으로 정렬하는 쿼리
1 2 3 4 5 6 7 | SELECT end_terminal, duration_seconds, SUM(duration_seconds) OVER (PARTITION BY end_terminal ORDER BY duration_seconds DESC) AS running_total FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' | cs |
ROW_NUMBER()
주어진 행의 번호를 표시, 시작은 1
1 2 3 4 5 6 7 | SELECT start_terminal, start_time, duration_seconds, ROW_NUMBER() OVER (ORDER BY start_time) AS row_number FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' | cs |
partition by절을 사용하면 각 파티션별로 카운트를 할 수 있다.
1 2 3 4 5 6 7 8 | SELECT start_terminal, start_time, duration_seconds, ROW_NUMBER() OVER (PARTITION BY start_terminal ORDER BY start_time) AS row_number FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' | cs |
RANK(), DENSE_RANK()
rank는 동일 순위가 부여되고 순위를 건너 뛰지만 dense_rank는 순위를 건너 뛰지 않는다.
1 2 3 4 5 6 7 | SELECT start_terminal, duration_seconds, RANK() OVER (PARTITION BY start_terminal ORDER BY start_time) AS rank FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' | cs |
연습문제
각 출발 터미널에서 가장 긴 라이드 5개를 터미널별로 정렬하고 각 터미널 내에서 가장 긴 라이드에서 최단 라이드까지 표시하는 쿼리. 2012년 1월 8일 이전에 발생한 라읻드로 제한.
1 2 3 4 5 6 7 8 9 10 | SELECT * FROM ( SELECT start_terminal, start_time, duration_seconds AS trip_time, RANK() OVER (PARTITION BY start_terminal ORDER BY duration_seconds DESC) AS rank FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' ) sub WHERE sub.rank <= 5 | cs |
NTILE
주어진 행이 속하는 백분위 수를 식별 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | SELECT start_terminal, duration_seconds, NTILE(4) OVER (PARTITION BY start_terminal ORDER BY duration_seconds) AS quartile, NTILE(5) OVER (PARTITION BY start_terminal ORDER BY duration_seconds) AS quintile, NTILE(100) OVER (PARTITION BY start_terminal ORDER BY duration_seconds) AS percentile FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' ORDER BY start_terminal, duration_seconds | cs |
위 쿼리의 결과를 보면 percentile열이 예상 한대로 정확하게 계산되지 않는다. 100개 이상의 레코드가 있기 때문이다.
연습문제
여행 기간과 해당 기간이 속하는 백분위 수만 표시하는 쿼리
1 2 3 4 5 6 | SELECT duration_seconds, NTILE(100) OVER (ORDER BY duration_seconds) AS percentile FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' ORDER BY 1 DESC | cs |
LAG, LEAD
가져올 열과 가져오기를 수행할 행수를 입력하기만 하면된다.
행간의 차이를 계산할 때 유용하다.
1 2 3 4 5 6 7 8 | SELECT start_terminal, duration_seconds, duration_seconds -LAG(duration_seconds, 1) OVER (PARTITION BY start_terminal ORDER BY duration_seconds) AS difference FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' ORDER BY start_terminal, duration_seconds | cs |
가져올 이전 행이 없는 경우 null이 입력된다. 외부 쿼리로 null을 제거할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | SELECT * FROM ( SELECT start_terminal, duration_seconds, duration_seconds -LAG(duration_seconds, 1) OVER (PARTITION BY start_terminal ORDER BY duration_seconds) AS difference FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' ORDER BY start_terminal, duration_seconds ) sub WHERE sub.difference IS NOT NULL | cs |
다음과 같이 명칭을 작성할 수 있다.
1 2 3 4 5 6 7 8 9 10 | SELECT start_terminal, duration_seconds, NTILE(4) OVER ntile_window AS quartile, NTILE(5) OVER ntile_window AS quintile, NTILE(100) OVER ntile_window AS percentile FROM tutorial.dc_bikeshare_q1_2012 WHERE start_time < '2012-01-08' WINDOW ntile_window AS (PARTITION BY start_terminal ORDER BY duration_seconds) ORDER BY start_terminal, duration_seconds | cs |




















댓글
댓글 쓰기