今天在工作中寫項目的時候,遇到了一個讓我感到幾乎無解的問題,在轉換了思路后,想出了一個折中的解決方案,記錄如下。
其實,問題的場景,非常簡單:

就是需要查詢出上圖的數據,紅框是從 項目產品表 中查詢的2個字段,綠框是從與項目產品表關聯的 文章表 中查詢出的1個字段。我希望實現的效果是,獲取到項目產品對應的文章提交人數,即該項目產品,有多少人提交了文章。看似很簡單啊,于是我開始擼 SQL 語句了。
先寫個雛形
既然在查詢項目產品表的時候,希望多查詢1列數據,而此列數據是從其他關聯表獲取的,所以基本實現方式,是使用子查詢。
SELECT s.id, s.name, (SELECT COUNT(*) FROM art_subject_article WHERE subject_id = s.id) AS article_num
FROM crm_subject s
ORDER BY article_num DESC;
獲得結果如下:

這個 SQL 語句,查詢出了項目產品所對應的文章數,下面基于它再做個優化調整,把查詢到的文章數量 article_num 變為提交文章的用戶數量 member_num。
再優化一下,意外發生了
現在不是直接從文章表中,獲取文章數量了,而是需要先根據文章表中的用戶ID進行分組,獲得分組數據之后,再通過 count(*) 聚合函數,拿到用戶數量。于是繼續調整 SQL 如下:
SELECT s.id, s.name, (SELECT count(*) FROM (SELECT mg_userid FROM art_subject_article WHERE subject_id = s.id GROUP BY mg_userid) t) AS member_num
FROM crm_subject s
ORDER BY member_num DESC;
但是,運行卻報錯了:

報錯信息說:s.id 字段找不到。這是一個嵌套的子查詢,在嵌套的最內層的子查詢中,關聯外部表的字段,是無法關聯的。雖然我沒找根據,但通過報錯信息,也能大致看出一二。而且,在 DataGrip 中,把鼠標放到 s.id 上面時,也會出現一個提示:

雖然這個提示,我也不甚明了,但是感覺上,好像就是在告訴我,你無法關聯到外部表的字段。
好像無解了,轉變思路,柳暗花明
上面的 SQL 語句,看起來是如此的完美,可是就是有問題、不成立,咋辦?
突然,靈機一動,想到一個方案,姑且一試。既然在嵌套的最內層的子查詢中,做 WHERE subject_id = s.id 與主表的字段關聯行不通,那么,就不在內層的子查詢中做關聯,把它提到外層的子查詢中去,不就行的通了嘛。于是,改造 SQL 如下:
SELECT s.id, s.name, (SELECT count(*) FROM (SELECT subject_id, mg_userid FROM art_subject_article GROUP BY subject_id, mg_userid) t WHERE t.subject_id = s.id) AS member_num
FROM crm_subject s
ORDER BY member_num DESC;
主要關注子查詢這里的改造,我們可以把這里的子查詢做個分解。
首先,可以把子查詢看成這樣:(SELECT count(*) FROM t WHERE t.subject_id = s.id) AS member_num,把它理解成從 t 表中查詢與主表的項目產品有關的記錄數量。
然后,我們再把 t 表看成 (SELECT subject_id, mg_userid FROM art_subject_article GROUP BY subject_id, mg_userid) t,代表從文章表中查詢出每個產品對應的用戶ID。
最后把2個子查詢,整合起來,就實現了查詢項目產品表中,每個產品所對應的提交了文章的用戶數量。
有沒有更好的解決方案
這個折中的方案,雖然可以解決我的問題,但是,我依然想知道,有沒有更好的、更標準的最佳實踐。
并且此方案,也有3點不足:
- 改進前我們是對文章表做項目產品關聯查詢后再分組,改進后是對文章表做全表掃描后的分組,效率較低,在大數據下的表現不好。
- 優化方案是基于兩層嵌套的子查詢進行的,假如需要三層嵌套的子查詢,此方案估計又失效了。
- 此優化方案較為局限,不具有普適性,不能很好的適用于各種業務場景。
所以,我將我遇到的這個問題,和解決方案分享在此,希望能幫助到有緣人,同時,也期望各位大神能夠不吝賜教,分享一下最佳實踐。