MySQL中distinct語句去查詢重復(fù)記錄及相關(guān)的性能討論
在 MySQL 查詢中,可能會包含重復(fù)值。這并不成問題,不過,有時(shí)您也許希望僅僅列出不同(distinct)的值。
關(guān)鍵詞 DISTINCT 用于返回唯一不同的值,就是去重啦。用法也很簡單:
SELECT DISTINCT * FROM tableName
DISTINCT 這個(gè)關(guān)鍵字來過濾掉多余的重復(fù)記錄只保留一條。
另外,如果要對某個(gè)字段去重,可以試下:
SELECT *, COUNT(DISTINCT nowamagic) FROM table GROUP BY nowamagic
這個(gè)用法,MySQL的版本不能太低。
在編寫查詢之前,我們甚至應(yīng)該對過濾條件進(jìn)行排序,真正高效的條件(可能有多個(gè),涉到同的表)是查詢的主要驅(qū)動(dòng)力,低效條件只起輔助作用。那么定義高效過濾條件的準(zhǔn)則是什呢?首先,要看過濾條件能否盡快減少必須處理的數(shù)據(jù)量。所以,我們必須倍加關(guān)注條件的寫方式。
假設(shè)有四個(gè)表: customers 、 orders 、 orderdetail 、 articles ,現(xiàn)在假設(shè) SQL 要處理的問題是:找出最近六個(gè)月內(nèi)居住在 Gotham 市、訂購了蝙蝠車的所有客戶。當(dāng)然,編寫這個(gè)查詢有多種方法, ANSI SQL 的推崇者可能寫出下列語句:
select distinct c.custname from customers c join orders o on o.custid = c.custid join orderdetail od on od.ordid = o.ordid join articles a on a.artid = od.artid where c.city = 'GOTHAM' and a.artname = 'BATMOBILE' and o.ordered >= somefunc
其中, somefunc 是個(gè)函數(shù),返回距今六個(gè)月前的具體日期。注意上面用了 distinct ,因?yàn)榭紤]到某個(gè)客戶可以是大買家,最近訂購了好幾臺蝙蝠車。
暫不考慮優(yōu)化器將如何改寫此查詢,我們先看一下這段代碼的含義。首先,來自 customers 表的數(shù)據(jù)應(yīng)只保留城市名為 Gotham 的記錄。接著,搜索 orders 表,這意味著 custid 字段最好有索引,否則只有通過排序、合并或掃描 orders 表建立一個(gè)哈希表才能保證查詢速度。對 orders 表 ,還要針對訂單日期進(jìn)行過濾:如果優(yōu)化器比較聰明,它會在連接( join )前先過濾掉一些數(shù)據(jù),從而減少后面要處理的數(shù)據(jù)量;不太聰明的優(yōu)化器則可能會先做連接,再作過濾,這時(shí)在連接中指定過濾條件利于提高性能,例如:
join orders o on o.custid = c.custid and a.ordered >= somefunc
注意,如果是:
left outer join orders o on o.custid = c.custid and a.ordered >= somefunc
此處關(guān)于left表的篩選條件將失效,因?yàn)槭亲笸膺B接,左表的所有列都將出現(xiàn)在這次連接結(jié)果集中)。
即使過濾條件與連接( join )無關(guān),優(yōu)化器也會受到過濾條件的影響。例如,若 orderdetail 的主鍵為( ordid, artid ),即 ordid 為索引的第一個(gè)屬性,那么我們可以利用索引找到與訂單相關(guān)的記錄。但如果主鍵是( artid, ordid )就太不幸了(注意,就關(guān)系理論而言 ,無論哪個(gè)版本都是完全一樣),此時(shí)的訪問效率比( ordid, artid )作為索引時(shí)要差,甚至一些數(shù)據(jù)庫產(chǎn)品無法使用該索引(注 3 ),唯一的希望就是在ordid 上加獨(dú)立索引了。
連接了表 orderdetail 和 orders 之后,來看 articles 表,這不會有問題,因?yàn)楸?order 包括 artid 字段。最后,檢查 articles 中的值是否為 Batmobile 。查詢就這樣結(jié)束了,因?yàn)橛昧?distinct ,通過層層篩選的客戶名還必須要排序,以剔除重復(fù)項(xiàng)目。
避免在最高層使用 distinct 應(yīng)該是一條基本規(guī)則 。原因在于,即使我們遺漏了連接的某個(gè)條件, distinct 也會使查詢 " 看似正確 " 地執(zhí)行 —— 無可否認(rèn),發(fā)現(xiàn)重復(fù)數(shù)據(jù)容易,發(fā)現(xiàn)數(shù)據(jù)不準(zhǔn)確很難,所以避免在最高層使用 distinct 應(yīng)該是一條基本規(guī)則。
發(fā)現(xiàn)結(jié)果不正確更難,例如,如果恰巧有多位客戶都叫 " Wayne " , distinct 不但會剔除由同個(gè)客戶的多張訂單產(chǎn)生的重復(fù)項(xiàng)目,也會剔除由名字相同的不同客戶產(chǎn)生的重復(fù)項(xiàng)目。事實(shí)上,應(yīng)該同時(shí)返回具唯一性的客戶 ID 和客戶名,以保證得到蝙蝠車買家的完整清單。
要擺脫 distinct ,可考慮以下思路:客戶在 Gohtam 市,而且滿足存在性測試,即在最近六個(gè)月訂購過蝙蝠車。注意,多數(shù)(但非全部) SQL 方言支持以下語法:
select c.custname from customers c where c.city = 'GOTHAM' and exists (select null from orders o, orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid and od.ordid = o.ordid and o.custid = c.custid and o.ordered >= somefunc )
上例的存在性測試,同一個(gè)名字可能出現(xiàn)多次,但每個(gè)客戶只出現(xiàn)一次,不管他有多少訂單。有人認(rèn)為我對 ANSI SQL 語法的挑剔有點(diǎn)苛刻(指 " 蝙蝠車買主 " 的例子),因?yàn)樯厦娲a中customers 表的地位并沒有降低。其實(shí),關(guān)鍵區(qū)別在于,新查詢中 customers 表是查詢結(jié)果的唯一來源(嵌套的子查詢會負(fù)責(zé)找出客戶子集),而先前的查詢卻用了 join 。
這個(gè)嵌套的子查詢與外層的 select 關(guān)系十分密切。如代碼第 11 行所示(粗體部分),子查詢參照了外層查詢的當(dāng)前記錄,因此,內(nèi)層子查詢就是所謂的關(guān)聯(lián)子查詢( correlated subquery )。
此類子查詢有個(gè)弱點(diǎn),它無法在確定當(dāng)前客戶之前執(zhí)行。如果優(yōu)化器不改寫此查詢,就必須先找出每個(gè)客戶,然后逐一檢查是否滿足存在性測試,當(dāng)來自 Gotham 市的客戶非常少時(shí)執(zhí)行效率倒是很高,否則情況會很糟(此時(shí),優(yōu)秀的優(yōu)化器應(yīng)嘗試其他執(zhí)行查詢的方式)。
select custname from customers where city = 'GOTHAM' and custid in (select o.custid from orders o, orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid and od.ordid = o.ordid and o.ordered >= somefunc)
在這個(gè)例子中,內(nèi)層查詢不再依賴外層查詢,它已變成了非關(guān)聯(lián)子查詢( uncorrelated subquery ),只須執(zhí)行一次。很顯然,這段代碼采用了原有的執(zhí)行流程。在本節(jié)的前一個(gè)例子 中 ,必須先搜尋符合地點(diǎn)條件的客戶(如均來自 GOTHAM ),接著依次檢查各個(gè)訂單。而現(xiàn)在,訂購了蝙蝠車的客戶,可以通過內(nèi)層查詢獲得。
不過,如果更仔細(xì)地分析一下,前后兩個(gè)版本的代碼還有些更微妙的差異。含關(guān)聯(lián)子查詢的代碼中,至關(guān)重要的是 orders 表中的 custid 字段要有索引,而這對另一段代碼并不重要,因?yàn)檫@時(shí)要用到的索引(如果有的話)是表 customers 的主鍵索引。
你或許注意到,新版的查詢中執(zhí)行了隱式的 distinct 。的確,由于連接操作,子查詢可能會返回有關(guān)一個(gè)客戶的多條記錄。但重復(fù)項(xiàng)目不會有影響,因?yàn)?in 條件只檢查該項(xiàng)目是否出現(xiàn)在子查詢返回的列表中,且 in 不在乎某值在列表中出現(xiàn)了一次還是一百次。但為了一致性,作為整體,應(yīng)該對子查詢和主查詢應(yīng)用相同的規(guī)則,也就是在子查詢中也加入存在性測試:
select custname from customers where city = 'GOTHAM' and custid in (select o.custid from orders o where o.ordered >= somefunc and exists (select null from orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid and od.ordid = o.ordid))
或者
select custname from customers where city = 'GOTHAM' and custid in (select custid from orders where ordered >= somefunc and ordid in (select od.ordid from orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid)
盡管嵌套變得更深、也更難懂了,但子查詢內(nèi)應(yīng)選擇 exists 還是 in 的選擇規(guī)則相同:此選擇取決于日期與商品條件的有效性。除非過去六個(gè)月的生意非常清淡,否則商品名稱應(yīng)為最有效的過濾條件,因此子查詢中用 in 比 exists 好,這是因?yàn)?,先找出所有蝙蝠車的訂單、再檢查銷售是否發(fā)生在最近六個(gè)月,比反過來操作要快。如果表 orderdetail 的 artid 字段有索引,這個(gè)方法會更快,否則,這個(gè)聰明巧妙的舉措就會黯然失色。
每當(dāng)對大量記錄做存在性檢查時(shí),選擇 in 還是 exists 須斟酌。
利于多數(shù) SQL 方言,非關(guān)聯(lián)子查詢可以被改寫成 from 子句中的內(nèi)嵌視圖。然而,一定要記住的是, in 會隱式地剔除重復(fù)項(xiàng)目,當(dāng)子查詢改寫為 from 子句中的內(nèi)嵌視圖時(shí),必須要顯式地消除重復(fù)項(xiàng)目。例如:
select custname from customers where city = 'GOTHAM' and custid in (select o.custid from orders o, (select distinct od.ordid from orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid) x where o.ordered >= somefunc and x.ordid = o.ordid)
總結(jié):保證 SQL 語句返回正確結(jié)果,只是建立最佳 SQL 語句的第一步。
相關(guān)文章
MySQL?數(shù)據(jù)備份和數(shù)據(jù)恢復(fù)的實(shí)現(xiàn)
數(shù)據(jù)恢復(fù)的過程包括將備份文件導(dǎo)入到數(shù)據(jù)庫中、重建索引、應(yīng)用日志等,本文主要介紹了MySQL數(shù)據(jù)備份和數(shù)據(jù)恢復(fù)的實(shí)現(xiàn),感興趣的可以了解一下2023-08-08MySQL如何通過Navicat實(shí)現(xiàn)遠(yuǎn)程連接
這篇文章主要介紹了MySQL如何通過Navicat實(shí)現(xiàn)遠(yuǎn)程連接,幫助大家更好的理解和使用MySQL數(shù)據(jù)庫,感興趣的朋友可以了解下2020-09-09CentOS 7搭建多實(shí)例MySQL8的詳細(xì)教程(想要幾個(gè)搞幾個(gè))
這篇文章主要介紹了CentOS 7搭建多實(shí)例MySQL8的詳細(xì)教程(想要幾個(gè)搞幾個(gè)),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-059種 MySQL數(shù)據(jù)庫優(yōu)化的技巧
這篇文章小編主要給大家介紹的是 MySQL數(shù)據(jù)庫優(yōu)化的正確姿勢,九種方法呢?。?!需要的小伙伴趕快收藏起來吧2021-09-09一文搞定MySQL binlog/redolog/undolog區(qū)別
這篇文章主要介紹了一文搞定MySQL binlog/redolog/undolog區(qū)別,作為開發(fā),我們重點(diǎn)需要關(guān)注的是二進(jìn)制日志(binlog)和事務(wù)日志(包括redo log和undo log),本文接下來會詳細(xì)介紹這三種日志,需要的朋友可以參考下2023-04-04MySQL無法啟動(dòng)、無法停止解決方法(安全設(shè)置后容易出現(xiàn))
最近在Win2003上的MySQL出現(xiàn)過多次正常運(yùn)行時(shí)無法連接數(shù)據(jù)庫故障,根本原因就是因?yàn)榘踩O(shè)置以后容易出現(xiàn)的問題,其實(shí)很簡單的解決2012-03-03