SQL數據去重的3種方法實例詳解
1、使用distinct去重
distinct用來查詢不重復記錄的條數,用count(distinct id)來返回不重復字段的條數。用法注意:
- distinct【查詢字段】,必須放在要查詢字段的開頭,即放在第一個參數;
- 只能在SELECT 語句中使用,不能在 INSERT, DELETE, UPDATE 中使用;
- DISTINCT 表示對后面的所有參數的拼接取不重復的記錄,即查出的參數拼接每行記錄都是唯一的
- 不能與all同時使用,默認情況下,查詢時返回的就是所有的結果。
distinct支持單列、多列的去重方式。
- 作用于單列
單列去重的方式簡明易懂,即相同值只保留1個。
select distinct name from A //對A表的name去重然后顯示
- 作用于多列
多列的去重則是根據指定的去重的列信息來進行,即只有所有指定的列信息都相同,才會被認為是重復的信息。注意,distinct作用于多列的時候只在開頭加上即可,并不用每個字段都加上。distinct必須在開頭,在中間是不可以的,會報錯,`select id,distinct name from A //錯誤
select distinct id,name from A //對A表的id和name去重然后顯示
- 配合count使用
select count(distinct name) from A //對A表的不同的name進行計數
按順序去重時,order by 的列必須出現在 distinct 中
出錯代碼

改正后的代碼

討論:若不使用Distinct關鍵字,則order by后面的字段不一定要放在seletc中

2、使用group by
GROUP BY 語句根據一個或多個列對結果集進行分組。在分組的列上我們可以使用 COUNT, SUM, AVG,等函數,形式為select 重復的字段名 from 表名 group by 重復的字段名;
group by 對age查詢結果進行了分組,自動將重復的項歸結為一組。

還可以使用count函數,統(tǒng)計重復的數據有多少個

3、使用ROW_NUMBER() OVER 或 GROUP BY 和 COLLECT_SET/COLLECT_LIST
說到要去重,自然會想到 DISTINCT,但是在 Hive SQL 里,它有兩個問題:
- DISTINCT 會以 SELECT 出的全部列作為 key 進行去重。也就是說,只要有一列的數據不同,DISTINCT 就認為是不同數據而保留。
- DISTINCT 會將全部數據打到一個 reducer 上執(zhí)行,造成嚴重的數據傾斜,耗時巨大。
3.1 ROW_NUMBER() OVER
DISTINCT 的兩個問題,用 ROW_NUMBER() OVER 可解。比如,如果我們要按 key1 和 key2 兩列為 key 去重,就會寫出這樣的代碼:
WITH temp_table AS (
SELECT
key1,
key2,
[columns]...,
ROW_NUMBER() OVER (
PARTITION BY key1, key2
ORDER BY column ASC
) AS rn
FROM
table
)
SELECT
key1,
key2,
[columns]...
FROM
temp_table
WHERE
rn = 1;這樣,Hive 會按 key1 和 key2 為 key,將數據打到不同的 mapper 上,然后對 key1 和 key2 都相同的一組數據,按 column 升序排列,并最終在每組中保留排列后的第一條數據。借此就完成了按 key1 和 key2 兩列為 key 的去重任務。注意 PARTITION BY 在此起到的作用:
- 一是按 key1 和 key2 打散數據,解決上述問題 (2);
- 二是與 ORDER BY 和 rn = 1 的條件結合,按 key1 和 key2 對數據進行分組去重,解決上述問題 (1)。
但顯然,這樣做十分不優(yōu)雅(not-elegant),并且不難想見其效率比較低。
row_number() OVER (PARTITION BY
COL1ORDER BYCOL2) as num 表示根據COL1分組,在分組內部根據COL2排序,此函數計算的值num就表示每組內部排序后的順序編號(組內連續(xù)的唯一的)
3.2 GROUP BY 和 COLLECT_SET/COLLECT_LIST
ROW_NUMBER() OVER 解法的一個核心是利用 PARTITION BY 對數據按 key 分組,同樣的功能用 GROUP BY 也可以實現。但是,GROUP BY 需要與聚合函數搭配使用。我們需要考慮,什么樣的聚合函數能實現或者間接實現這樣的功能呢?不難想到有 COLLECT_SET 和 COLLECT_LIST。
SELECT key1, key2, [COLLECT_LIST(column)[1] AS column]... FROM temp_table GROUP BY key1, key2
對于 key1 和 key2 以外的列,我們用 COLLECT_LIST 將他們收集起來,然后輸出第一個收集進來的結果。這里使用 COLLECT_LIST 而非 COLLECT_SET 的原因在于 SET 內是無序的,因此你無法保證輸出的 columns 都來自同一條數據。若對于此沒有要求或限制,則可以使用 COLLECT_SET,它會更節(jié)省資源。
相比前一種辦法,由于省略了排序和(可能的)落盤動作,所以效率會高不少。但是因為(可能)不落盤,所以 COLLECT_LIST 中的數據都會緩存在內存當中。如果重復數量特別大,這種方法可能會觸發(fā) OOM。此時應考慮將數據進一步打散,然后再合并;或者干脆換用前一種辦法。
distinct與group by的去重方面的區(qū)別
distinct簡單來說就是用來去重的,而group by的設計目的則是用來聚合統(tǒng)計的,兩者在能夠實現的功能上有些相同之處,但應該仔細區(qū)分。
單純的去重操作使用distinct,速度是快于group by的。
distinct支持單列、多列的去重方式。
單列去重的方式簡明易懂,即相同值只保留1個。
多列的去重則是根據指定的去重的列信息來進行,即只有所有指定的列信息都相同,才會被認為是重復的信息。
group by使用的頻率相對較高,但正如其功能一樣,它的目的是用來進行聚合統(tǒng)計的,雖然也可能實現去重的功能,但這并不是它的長項。
區(qū)別:
1)distinct只是將重復的行從結果中出去;
group by是按指定的列分組,一般這時在select中會用到聚合函數。
2)distinct是把不同的記錄顯示出來。
group by是在查詢時先把紀錄按照類別分出來再查詢。
group by 必須在查詢結果中包含一個聚集函數,而distinct不用。
distinct和group by有啥區(qū)別,大概總結以下幾點:
distinct適合查單個字段去重,支持單列、多列的去重方式。 單列去重的方式簡明易懂,即相同值只保留1個。
多列的去重則是根據指定的去重的列信息來進行,即只有所有指定的列信息都相同,才會被認為是重復的信息。
而 group by 可以針對要查詢的全部字段中的部分字段去重,它的作用主要是:獲取數據表中以分組字段為依據的其他統(tǒng)計數據。
補充:MySQL中distinct和group by去重性能對比
前言
- MySQL:5.7.17
- 存儲引擎:InnoDB
- 實驗目的:本文主要測試在某字段有無索引、各種不同值個數情況下,記錄對此字段其使用
DISTINCT/GROUP BY去重的查詢語句執(zhí)行時間,對比兩者在不同場景下的去重性能,實驗過程中關閉MySQL查詢緩存。 - 實驗表格:
| 表名 | 記錄數 | 查詢字段有無索引 | 查詢字段不同值個數 | DISTINCT | GROUP BY |
|---|---|---|---|---|---|
| tab_1 | 100000 | N | 3 | ||
| tab_2 | 100000 | Y | 3 | ||
| tab_3 | 100000 | N | 10000 | ||
| tab_4 | 100000 | Y | 10000 |
實驗過程
1)創(chuàng)建測試表
表創(chuàng)建語句:
DROP TABLE IF EXISTS `tab_1`; CREATE TABLE `tab_1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `value` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `tab_2`; CREATE TABLE `tab_2` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `value` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `idx_value` (`value`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `tab_3`; CREATE TABLE `tab_3` LIKE `tab_1`; DROP TABLE IF EXISTS `tab_4`; CREATE TABLE `tab_4` LIKE `tab_2`;
2)生成測試數據
表數據插入過程:
DROP PROCEDURE IF EXISTS generateRandomData;
delimiter $$
-- tblName為插入表,field為插入字段,num為插入字段值上限,count為插入的記錄數
CREATE PROCEDURE generateRandomData(IN tblName VARCHAR(30),IN field VARCHAR(30),IN num INT UNSIGNED,IN count INT UNSIGNED)
BEGIN
-- 聲明循環(huán)變量
DECLARE i INT UNSIGNED DEFAULT 1;
-- 循環(huán)插入隨機整數1~num,共插入count條數據
w1:WHILE i<=count DO
set i=i+1;
set @val = FLOOR(RAND()*num+1);
set @statement = CONCAT('INSERT INTO ',tblName,'(`',field,'`) VALUES(',@val,')');
PREPARE stmt FROM @statement;
EXECUTE stmt;
END WHILE w1;
END $$
delimiter ;
調用過程隨機生成測試數據:
call generateRandomData('tab_1','value',3,100000);
INSERT INTO tab_2 SELECT * FROM tab_1;
call generateRandomData('tab_3','value',10000,100000);
INSERT INTO tab_4 SELECT * FROM tab_3;3)執(zhí)行查詢語句,記錄執(zhí)行時間
查詢語句及對應執(zhí)行時間如下:
SELECT DISTINCT(`value`) FROM tab_1; SELECT `value` FROM tab_1 GROUP BY `value`; SELECT DISTINCT(`value`) FROM tab_2; SELECT `value` FROM tab_2 GROUP BY `value`; SELECT DISTINCT(`value`) FROM tab_3; SELECT `value` FROM tab_3 GROUP BY `value`; SELECT DISTINCT(`value`) FROM tab_4; SELECT `value` FROM tab_4 GROUP BY `value`;
4)實驗結果
| 表名 | 記錄數 | 查詢字段有無索引 | 查詢字段不同值個數 | DISTINCT | GROUP BY |
|---|---|---|---|---|---|
| tab_1 | 100000 | N | 3 | 0.058s | 0.059s |
| tab_2 | 100000 | Y | 3 | 0.030s | 0.027s |
| tab_3 | 100000 | N | 10000 | 0.072s | 0.073s |
| tab_4 | 100000 | Y | 10000 | 0.047s | 0.049s |
實驗結論
MySQL 5.7.17中使用distinct和group by進行去重時,性能相差不大
使用去重distinct方法的示例詳解
一 distinct
含義:distinct用來查詢不重復記錄的條數,即distinct來返回不重復字段的條數(count(distinct id)),其原因是distinct只能返回他的目標字段,而無法返回其他字段
用法注意:
1.distinct【查詢字段】,必須放在要查詢字段的開頭,即放在第一個參數;
2.只能在SELECT 語句中使用,不能在 INSERT, DELETE, UPDATE 中使用;
3.DISTINCT 表示對后面的所有參數的拼接取 不重復的記錄,即查出的參數拼接每行記錄都是唯一的
4.不能與all同時使用,默認情況下,查詢時返回的就是所有的結果。
1.1只對一個字段查重
對一個字段查重,表示選取該字段一列不重復的數據。
示例表:psur_list

PLAN_NUMBER字段去重,語句:
SELECT DISTINCT PLAN_NUMBER FROM psur_list;
結果如下:

1.2多個字段去重
對多個字段去重,表示選取多個字段拼接的一條記錄,不重復的所有記錄
示例表:psur_list

PLAN_NUMBER和PRODUCT_NAME字段去重,語句:
SELECT DISTINCT PLAN_NUMBER,PRODUCT_NAME FROM psur_list;
結果如下:

期望結果:只對第一個參數PLAN_NUMBER取唯一值
解決辦法一:使用group_concat 函數
語句:
SELECT GROUP_CONCAT(DISTINCT PLAN_NUMBER) AS PLAN_NUMBER,PRODUCT_NAMEFROM psur_list GROUP BY PLAN_NUMBER
解決辦法二:使用group by
語句:
SELECT PLAN_NUMBER,PRODUCT_NAME FROM psur_list GROUP BY PLAN_NUMBER
結果如下:

1.3針對null處理
distinct不會過濾掉null值,返回結果包含null值
表psur_list如下:

對COUNTRY字段去重,語句:
SELECT DISTINCT COUNTRY FROM psur_list
結果如下:

1.4與distinctrow同義
語句:
SELECT DISTINCTROW COUNTRY FROM psur_list
結果如下:

二 聚合函數中使用distinct
在聚合函數中DISTINCT 一般跟 COUNT 結合使用。count()會過濾掉null項
語句:
SELECT COUNT(DISTINCT COUNTRY) FROM psur_list
結果如下:【實際包含null項有4個記錄,執(zhí)行語句后過濾null項,計算為3】

總結
到此這篇關于SQL數據去重的3種方法的文章就介紹到這了,更多相關SQL數據去重方法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MySQL復制出錯 Last_SQL_Errno:1146的解決方法
這篇文章主要介紹了MySQL復制出錯 Last_SQL_Errno:1146的解決方法,需要的朋友可以參考下2016-07-07

