1. 程式人生 > 實用技巧 >PostgreSQL 高階SQL(二) filter子句

PostgreSQL 高階SQL(二) filter子句

本文是轉載,原文地址是:https://www.jianshu.com/p/aad5b7265674

本章所用到案例資料來自於上一章節,如果有想使用該資料的讀者可以檢視上一章節。

這一章節我們想要了解的是PG聚合操作中使用到的filter子句,這個filter子句是ANSI SQL標準中的關鍵字,並不是PG的專用SQL關鍵字。如果我們想了解中國、美國、日本、法國、德國、加拿大從1960~2018年中每隔十年的GDP總值情況,我們可能會寫出著這樣的SQL,

SELECT
	country_name,
	SUM ( CASE WHEN YEAR >= 1960 AND YEAR < 1970 THEN gdp ELSE NULL END ) AS "1960~1969",
	SUM ( CASE WHEN YEAR >= 1970 AND YEAR < 1980 THEN gdp ELSE NULL END ) AS "1970~1979",
	SUM ( CASE WHEN YEAR >= 1980 AND YEAR < 1990 THEN gdp ELSE NULL END ) AS "1980~1989",
	SUM ( CASE WHEN YEAR >= 1990 AND YEAR < 2000 THEN gdp ELSE NULL END ) AS "1990~1999",
	SUM ( CASE WHEN YEAR >= 2000 THEN gdp ELSE NULL END ) AS "2000~至今" 
FROM
	country_gdp_year_final 
WHERE
	country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' ) 
GROUP BY
	country_name;

從上圖可以看出美國的經濟體量和我們中國的經濟體量不是在一個數量級的,中國每隔十年的GDP實現一倍的增速,美國一直飛速發展時期,中國要實現美國的GDP的話粗略估計需要至少30年的時間甚至更久;

迴歸正題,我們今天的主角是filter子句,ANSI SQL加入filter關鍵詞的主要目的就是替代case when子句,簡化case when參與的聚合語句,增加可可讀性,我們用同樣的filter 子句實現上面的case when參與的聚合操作。

SELECT
	country_name,
	SUM ( gdp )		FILTER ( WHERE YEAR >= 1960 AND YEAR < 1970 ) AS "1960~1969",
	SUM ( gdp )  FILTER ( WHERE YEAR >= 1970 AND YEAR < 1980 ) AS "1970~1979",
	SUM ( gdp )  FILTER ( WHERE  YEAR >= 1980 AND YEAR < 1990 )  AS "1980~1989",
	SUM ( gdp )  FILTER ( WHERE YEAR >= 1990 AND YEAR < 2000 ) AS "1990~1999",
	SUM ( gdp )  FILTER ( WHERE YEAR >= 2000 ) AS "2000~至今" 
FROM
	country_gdp_year_final WHERE country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' ) 
GROUP BY
	country_name;

從上面的結果我們可以看出filter子句和case when 子句在聚合函式中使用是等價的,並且filter子句的可讀性更好,讓人一眼就能看出SQL的目的和作用,

下面我們看一下上面倆個語句的的執行計劃:

從上面的結果我們可以看出來倆種語句不僅結果一樣而且產生的執行計劃也是一致的,並且倆個語句值進行了一次全表掃描就計算出了結果,在平時的開發中,很多開發者為了實現相同的結果可能要進行五次全表掃描,很可能會寫出以下的相同查詢結果但是不同效能的SQL

SELECT ff.country_name,ff."1960~1969",aa."1970~1979",bb."1980~1989",cc."1990~1999",dd."2000~至今" 
FROM
	(
	SELECT country_name,SUM ( gdp ) AS "1960~1969" 
	FROM
		country_gdp_year_final ff 
		WHERE YEAR >= 1960 AND YEAR < 1970 AND country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' )  
	GROUP BY
		country_name 
	) AS ff
	LEFT JOIN (
	SELECT country_name,SUM ( gdp ) AS "1970~1979" 
	FROM
		country_gdp_year_final 
		WHERE YEAR >= 1970 AND YEAR < 1980 AND country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' )  
	GROUP BY
		country_name 
	) AS aa ON aa.country_name = ff.country_name
	LEFT JOIN (
	SELECT country_name,SUM ( gdp ) AS "1980~1989" 
	FROM
		country_gdp_year_final 
		WHERE YEAR >= 1980 AND YEAR < 1990 AND country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' ) 
	GROUP BY
		country_name 
	) AS bb ON bb.country_name = ff.country_name
	LEFT JOIN (
	SELECT country_name,SUM ( gdp ) AS "1990~1999" 
	FROM
		country_gdp_year_final 
		WHERE YEAR >= 1990 AND YEAR < 2000and country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' )  
	GROUP BY
		country_name 
	) AS cc ON cc.country_name = ff.country_name
	LEFT JOIN (
	SELECT country_name,SUM ( gdp ) AS "2000~至今" 
	FROM
		country_gdp_year_final 
	WHERE 	YEAR >= 2000 AND country_code IN ( 'CHN', 'JPN', 'USA', 'DEU', 'CAN', 'FRA' )  
	GROUP BY
	country_name 
	) AS dd ON dd.country_name = ff.country_name;

我也相信很多人開發者寫出來的SQL和上面的SQL基本差不多,這種SQL不僅很長而且很難都,更致命的是這種SQL進行了五次全表掃描,在不考慮快取命中的的情況下,這種SQL的查詢時間是上面filter和case when子句的五倍,我們可以看一下這個長SQL的查詢計劃。

從上面的查詢計劃我們可以看出來經過了五次全表掃描,五次聚合,如果這個表的資料量很大,那麼效能可想而知。

最後我想說的是filter適合所有的聚合函式,不僅僅是PG內建的的聚合函式,還支援安裝擴充套件包的聚合函式,總之filter子句非常的棒!!!