MySQL(12)---紀錄一次left join一對多關係而引發的BUG

MySQL(11)---紀錄一次left join一對多關係而引發的bug

BUG背景 咱們有一個訂單表 和 一個 物流表 它們經過 訂單ID 進行一對一的關係綁定。可是因爲物流表在保存訂單信息的時候沒有作判斷該訂單是否已經有物流信息,
這就變成同一個訂單id在物流表中存在多條數據,也就變成了原本訂單表只有100條紀錄,而left join 物流表後,所查詢的訂單數據遠遠大於100條。
總結 趁着上面這個問題,本身來複習下join語句distinct關鍵字,同時說明如何解決就算關聯是一對多,但我仍是想只顯示100條訂單數據的方法。html

1、理論

先再講下關聯表查詢的幾種表達式,網上找了一張圖,經過這張圖就能理解全部關聯查詢的含義。mysql

left join(左聯接) 返回包括左表中的全部記錄和右表中聯結字段相等的記錄 。
right join(右聯接) 返回包括右表中的全部記錄和左表中聯結字段相等的記錄。
inner join(等值鏈接) 只返回兩個表中聯結字段相等的行。sql


2、left join一對一和一對多

一、一對一關聯表查詢

業務邏輯1 有兩張表,一張商品表、一張商品訂單表回顯訂單列表的時候須要訂單表關聯商品表,以下數據庫

1)商品表編碼

DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product` (
  `product_id` char(32) NOT NULL DEFAULT '' COMMENT '主鍵ID',
  `pro_name` varchar(64) DEFAULT NULL COMMENT '商品名稱',
  `cash` double(10,2) DEFAULT '0.00' COMMENT '商品價格',
  `pro_code` varchar(32) DEFAULT NULL COMMENT '商品編號',
  PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';

INSERT INTO `t_product` (`product_id`, `pro_name`, `cash`, `pro_code`)
VALUES
    ('1','小米',888.00,'001'),
    ('2','華爲',1888.00,'002');

2) 訂單表code

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `order_id` char(32) NOT NULL DEFAULT '' COMMENT '主鍵ID',
  `product_id` char(32) DEFAULT NULL COMMENT '商品ID',
  `sale_amount` double(16,2) DEFAULT '0.00' COMMENT '訂單金額',
  `order_number` varchar(40) DEFAULT NULL COMMENT '訂單編碼',
  `status` int(2) DEFAULT '1' COMMENT '訂單狀態 0訂單無效1兌換功成二、已發貨',
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';

INSERT INTO `t_order` (`order_id`, `product_id`, `sale_amount`, `order_number`, `status`)
VALUES
    ('1','1',888.00,'001001',1),
    ('2','2',1888.00,'001002',1);

3) 關聯查詢htm

這裏須要展現訂單列表,訂單列表中固然須要展現商品信息。blog

select o.`order_id`,o.`sale_amount`,p.`pro_name` from t_order o left join t_product p on o.`product_id`=p.`product_id`;

運行結果排序

這兩張表不多是一對多的關係,由於左表關聯右表的主鍵ID,全部右表不可能出現多條紀錄。內存

二、left join有一對多關聯查詢

業務邏輯2 這裏是邏輯也是有兩張表,一張訂單表、一張物流表。訂單表和上面同樣,數據也一致。

物流表

DROP TABLE IF EXISTS `t_logistics`;
CREATE TABLE `t_logistics` (
  `logistics_id` char(32) NOT NULL DEFAULT '' COMMENT '主鍵ID',
  `order_id` char(32) DEFAULT NULL COMMENT '訂單ID',
  `logistics_company_name` varchar(32) DEFAULT NULL COMMENT '物流公司名稱',
  `courier_number` varchar(32) DEFAULT NULL COMMENT '快遞單號',
  PRIMARY KEY (`logistics_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='物流信息表';

INSERT INTO `t_logistics` (`logistics_id`, `order_id`, `logistics_company_name`, `courier_number`)
VALUES
    ('1','1','順豐','001'),
    ('2','1','順豐','002');
    ('3','2','中通','003');

注意 這張表數據是有問題的,由於不可能一個訂單同時有兩條物流信息,可是你不能徹底排除這條表裏存在兩條相同訂單編號,由於左表綁定的不是右表的主鍵ID,這可能就是保留物流信息的時候沒有判斷該訂單已經保存物流信息,而引發的數據重複問題。

那麼這個時候問題來了。

select o.`order_id`,o.`sale_amount`,l.`logistics_company_name` from t_order o left join t_logistics l on o.`order_id`=l.`order_id`;

運行結果

咱們發現,訂單列表已經有三條紀錄,但按照常理應該展現兩條。

注意 因此從這裏咱們能夠得知,若是你在left join 時,須要顯示的數據的左表數據不能重複時,那麼就須要 on 後面的表它們的對應關係是一對一的關係。顯然這裏對於order_id爲1所對應的物流表信息是一對多的關係。


3、如何解決一對多的問題

一對多並不必定是問題,主要仍是看錶與表之間的關係。好比:
A表是用戶表,B表是訂單表。天然也就想到了一個用戶可能屢次下單。咱們假設B表中的用戶id在A表中匹配到50個用戶id,可是這50個用戶id總訂單數是500個。這就是合理的一對多關係。

那麼若是你業務邏輯確定顯示一對一的關係,而表關係確實一對多的關係,就像上面的訂單表和物流表同樣。怎麼解決,這裏有兩種解決方案。

一、group by

關鍵點 把一對多的問題轉化成聚合查詢

select o.`order_id`,o.`sale_amount`,l.`logistics_company_name` from t_order o left join t_logistics l on o.`order_id`=l.`order_id` group by o.`order_id`;

二、distinct

select distinct o.`order_id`,o.`sale_amount`,l.`logistics_company_name` from t_order o left join t_logistics l on o.`order_id`=l.`order_id`;

它所得的的結果和上面是同樣的。

三、group by 和 distinct 比較

1)、不一樣

  • distinct須要將col列中的所有內容都存儲在一個內存中,能夠理解爲一個hash結構,key爲col的值,最後計算hash結構中有多少個key便可獲得結果。很明顯,須要將全部不一樣的值都存起來。內存消耗可能較大。
  • 而group by的方式是先將col排序。而數據庫中的group通常使用sort的方法,即數據庫會先對col進行排序。而排序的基本理論是,時間複雜爲nlogn,空間爲1。而後只要單純的計數就能夠了。優勢是空間複雜度小,缺點是要進行一次排序,執行時間會較長。

2)、使用場景

數據分佈 去重方式 緣由
離散 group distinct空間佔用較大,在時間複雜度容許的狀況下,group 能夠發揮空間複雜度優點
集中 distinct distinct空間佔用較小,能夠發揮時間複雜度優點

3)、兩個極端

  • 數據列的全部數據都同樣,即去重計數的結果爲1時,用distinct最佳。
  • 若是數據列惟一,沒有相同數值,用group 最好。

4、distinct

一、做用於單列

select distinct name from A   #name去重

二、做用於多列

select distinct name, age from A  #根據name和age兩個字段來去重的

三、COUNT統計

select count(distinct name) from A;   #表中name去重後的數目

注意: count是不能統計多個字段的,下面的SQL在SQL Server和Access中都沒法運行。

若想使用多個字段,請使用嵌套查詢,以下:

select count(*) from (select distinct name, age from A) AS B;

四、distinct必須放在開頭

select age, distinct name from A;   #會提示錯誤,由於distinct必須放在開頭

補充

一、能用inner join 儘可能用inner join。
二、重複數據多是表結構一對多形成的,這種狀況每每是有意義的,好比訂單和訂單商品明細,算總價的時候,是須要sum多個明細的。
三、若是一對多的多確實沒有意義,那就能夠考慮用group by 或者 distinct。
四、具體結構問題具體分析。


參考

一、left join百度百科

二、left join的用法

三、SQL中distinct的用法




只要本身變優秀了,其餘的事情纔會跟着好起來(少將15)
相關文章
相關標籤/搜索