記一次在數據庫中查詢:“包含”或者“僅包含”某些商品的訂單的方法


有這樣一個需求:

  1. 從數據庫中查出包含“商品1”和“商品2”的訂單;
  2. 從數據庫中查出包含“商品1”或“商品2”的訂單;
  3. 從數據庫中查出僅包含“商品1”和“商品2”的訂單;
  4. 從數據庫中查出僅包含“商品1”或“商品2”的訂單;

這里只用“商品1”、“商品2”舉例,可以擴展到多個商品的需求。

涉及到的表大概如下圖:

該怎么做呢?以第一點需求為例,一種可行的方法是:先查出所有包含“商品1”的訂單,然后遍歷這些訂單,選出包含“商品2”的訂單,如果要查詢包含更多商品的訂單,需要進行多次遍歷,層層篩選,效率低下。況且,在實際情況下,往往需要支持分頁查詢,這種方式基本不可行,或者實現起來很復雜。

第二種方法是這樣的:假設查詢包含“商品1”、“商品2”、“商品3”的訂單,其goods_id 分別為1、2、3,sql如下:

SELECT * FROM `order` 
WHERE order_id IN ( 
SELECT tmp.order_id FROM ( SELECT order_id FROM order_item WHERE goods_id = 1 ) AS tmp 
INNER JOIN ( SELECT order_id FROM order_item WHERE goods_id = 2 ) AS t2 ON tmp.order_id = t2.order_id 
INNER JOIN ( SELECT order_id FROM order_item WHERE goods_id = 3 ) AS t3 ON tmp.order_id = t3.order_id 
)
LIMIT 0,10

如果要求僅包含的話,可以這樣寫:

SELECT * FROM `order` 
WHERE order_id IN ( 
SELECT tmp.order_id FROM ( SELECT order_id FROM order_item WHERE goods_id = 1 ) AS tmp 
INNER JOIN ( SELECT order_id FROM order_item WHERE goods_id = 2 ) AS t2 ON tmp.order_id = t2.order_id 
INNER JOIN ( SELECT order_id FROM order_item WHERE goods_id = 3 ) AS t3 ON tmp.order_id = t3.order_id 
WHERE (SELECT count(*) from order_item WHERE order_id = tmp.order_id) = 3
)
LIMIT 0,10

增加where條件,限制該訂單只有三個商品即可。

以上說明的需求點1和3的實現方式,需求點2和4可以用類似的方式實現。

這種實現方式確實能夠滿足需求,但是有一個比較嚴重的問題:當關聯的商品多了以后,多個inner join的使用,會使查詢效率非常低,尤其是訂單量大的時候,會更慢,如果再加上其他查詢條件,如:下單時間、發貨時間、訂單狀態等等各類條件以后,(此處已無法描述)。親測查詢7到8個商品,幾十萬訂單的時候,已經慢到不要不要的了。

最后一種實現方式,也即本文的重點,這種方式需要對數據結構做一小小改動,如下圖:

如圖所示,goods表和order表都增加了一個字段:bit。

先說goods表中的bit,該字段表示對商品編碼,取值為2的n次方(n>=0),假設goods表有5個商品,那么bit值依次為1、2、4、8、16、32.

再說order表中的bit,該字段表示該訂單所包含商品的bit值之和,假設某訂單包含bit值為1和2的商品,那么其bit為3.

如何實現需求呢?先說需求1,假設“商品1”的bit為1,“商品2”的bit為2,那么查詢包含“商品1”和“商品2”的訂單的sql如下:

SELECT * from `order` where bit & 3 = 3

這里的3為“商品1”和“商品2”bit之和。這個要怎么理解呢,從goods的bit說起,商品的bit用二進制表示如下圖:

通過上圖,可以想象order表中的bit的值的二進制形式,如果訂單包含商品1、2,則二進制位11;如果包含商品1、3,則二進制位101;如果包含商品1、2、3,則二進制位111,。。。商品的各種組合,其bit值相加不會產生進位,因為每個商品bit值的“1”位對應其他商品bit值都為“0”位,所以商品的各種組合對應的訂單的bit值唯一,這也是為何商品的bit要求取值為2的n次方(n>=0)。

因此,上述sql中,如果order的bit 和 所要查詢商品的bit之和按位與運算,如果結果為查詢商品的bit之和,那么說明order的bit相應位為1,訂單中有該商品。

需求2,包含“商品1”或“商品2”怎么表示?sql如下:

SELECT * from `order` where bit & 3 > 0 

意思是說,訂單bit的相應位至少有一個商品的bit即可。

需求3:僅包含“商品1”和“商品2”怎么表示?sql如下:

SELECT * from `order` where bit = 3

需求4:僅包含“商品1”或“商品2”怎么表示?這種情況下要求訂單中只有一個商品,為“商品1”或“商品2”。sql如下:

SELECT * from `order` where bit & (bit - 1) =0 and bit & 3 != 0

這里 bit & (bit -1) = 0 保證訂單中只有一個商品,bit & 3  != 0 保證訂單中的商品為“商品1”或者“商品2”。

這種方法效率很高,滿足需求的同時也簡化了sql。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM