MySQL小白入門04 - 集合運算

落木~發表於2020-12-22

1. 表的加減法

1.1 什麼是集合運算

集合在數學領域表示“各種各樣的事物的總和”, 在資料庫領域表示記錄的集合. 具體來說,表、檢視和查詢的執行結果都是記錄的集合, 其中的元素為表或者查詢結果中的每一行.

在標準 SQL 中, 分別對檢索結果使用 UNION, INTERSECT, EXCEPT 來將檢索結果進行並,交和差運算, 像UNION,INTERSECT, EXCEPT這種用來進行集合運算的運算子稱為集合運算子.

以下的文氏圖展示了幾種集合的基本運算.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-gxqFwQ7Q-1608652028913)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.01.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-c39Ybp8g-1608652028924)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.02.png)]

[圖片來源於網路]

在資料庫中, 所有的表–以及查詢結果–都可以視為集合, 因此也可以把表視為集合進行上述集合運算, 在很多時候, 這種抽象非常有助於對複雜查詢問題給出一個可行的思路.

1.2 表的加法–UNION

1.2.1 UNION

建表程式碼及資料匯入請使用第一章提供的程式碼.

接下來我們演示UNION的具體用法及查詢結果:

SELECT product_id, product_name
  FROM Product
 UNION
SELECT product_id, product_name
  FROM Product2;

上述結果包含了兩張表中的全部商品. 你會發現,這就是我們在學校學過的集合中的並集運算,通過文氏圖會看得更清晰(圖 7-1):

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rFnr0Rd8-1608652028929)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.03union.png)]

通過觀察可以發現,商品編號為“ 0001 ”~“ 0003 ”的 3 條記錄在兩個表中都存在,因此大家可能會認為結果中會出現重複的記錄,但是 UNION 等集合運算子通常都會除去重複的記錄.

上述查詢是對不同的兩張表進行求並集運算. 對於同一張表, 實際上也是可以進行求並集的.

**練習題:**假設連鎖店想要增加毛利率超過 50%或者售價低於 800 的貨物的存貨量, 請使用 UNION 對分別滿足上述兩個條件的商品的查詢結果求並集.

結果應該類似於:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-urruaikK-1608652028936)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.04result.png)]

-- 參考答案:
SELECT  product_id,product_name,product_type
       ,sale_price,purchase_price
  FROM PRODUCT 
 WHERE sale_price<800
  
 UNION
 
SELECT  product_id,product_name,product_type
       ,sale_price,purchase_price
  FROM PRODUCT 
 WHERE sale_price>1.5*purchase_price;

思考: 如果不使用 UNION 該怎麼寫查詢語句?

-- 參考答案:
SELECT  product_id,product_name,product_type
       ,sale_price,purchase_price
  FROM PRODUCT 
 WHERE sale_price < 800 
    OR sale_price > 1.5 * purchase_price;

1.2.2 UNION 與 OR 謂詞

對於上邊的練習題, 如果你已經正確地寫出來查詢, 你會發現, 使用 UNION 對兩個查詢結果取並集, 和在一個查詢中使用 WHERE 子句, 然後使用 OR 謂詞連線兩個查詢條件, 能夠得到相同的結果.

那麼是不是就沒必要引入 UNION 了呢? 當然不是這樣的. 確實, 對於同一個表的兩個不同的篩選結果集, 使用 UNION 對兩個結果集取並集, 和把兩個子查詢的篩選條件用 OR 謂詞連線, 會得到相同的結果, 但倘若要將兩個不同的表中的結果合併在一起, 就不得不使用 UNION 了.

而且, 即便是對於同一張表, 有時也會出於查詢效率方面的因素來使用 UNION.

練習題 :

分別使用 UNION 或者 OR 謂詞,找出毛利率不足 30%或毛利率未知的商品.

參考答案:

-- 使用 OR 謂詞
SELECT * 
  FROM Product 
 WHERE sale_price / purchase_price < 1.3 
    OR sale_price / purchase_price IS NULL;
-- 使用 UNION
SELECT * 
  FROM Product 
 WHERE sale_price / purchase_price < 1.3
 
 UNION
SELECT * 
  FROM Product 
 WHERE sale_price / purchase_price IS NULL;

練習題:

找出 Product 和 Product2 中售價高於 500 的商品的基本資訊.

參考答案略.

1.2.3 包含重複行的集合運算 UNION ALL

在1.1.1 中我們發現, SQL 語句的 UNION 會對兩個查詢的結果集進行合併和去重, 這種去重不僅會去掉兩個結果集相互重複的, 還會去掉一個結果集中的重複行. 但在實踐中有時候需要需要不去重的並集, 在 UNION 的結果中保留重複行的語法其實非常簡單,只需要在 UNION 後面新增 ALL 關鍵字就可以了.

例如, 想要知道 Product 和 Product2 中所包含的商品種類及每種商品的數量, 第一步,就需要將兩個表的商品種類欄位選出來, 然後使用 UNION ALL 進行不去重地合併. 接下來再對兩個表的結果按 Product_type 欄位分組計數.

-- 保留重複行
SELECT product_id, product_name
  FROM Product
 UNION ALL
SELECT product_id, product_name
  FROM Product2;

查詢結果如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-KGFjG53v-1608652028942)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.05result2.png)]

練習題:

商店決定對product表中利潤低於50%和售價低於1000的商品提價, 請使用UNION ALL 語句將分別滿足上述兩個條件的結果取並集. 查詢結果類似下表:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZzT9Jvyc-1608652028947)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.06result3.png)]

參考答案

SELECT * 
  FROM Product 
 WHERE sale_price < 1000
 UNION ALL
SELECT * 
  FROM Product 
 WHERE sale_price > 1.5 * purchase_price

1.2.4 [擴充套件閱讀]bag 模型與 set 模型

在高中數學課上我們就學過, 集合的一個顯著的特徵就是集合中的元素都是互異的. 當我們把資料庫中的表看作是集合的時候, 實際上存在一些問題的: 不論是有意的設計或無意的過失, 很多資料庫中的表包含了重複的行.

Bag 是和 set 類似的一種數學結構, 不一樣的地方在於: bag 裡面允許存在重複元素, 如果同一個元素被加入多次, 則袋子裡就有多個該元素.

通過上述 bag 與 set 定義之間的差別我們就發現, 使用 bag 模型來描述資料庫中的表在很多時候更加合適.

是否允許元素重複導致了 set 和 bag 的並交差等運算都存在一些區別. 以 bag 的交為例, 由於 bag 允許元素重複出現, 對於兩個 bag, 他們的並運算會按照: 1.該元素是否至少在一個 bag 裡出現過, 2.該元素在兩個 bag 中的最大出現次數 這兩個方面來進行計算. 因此對於 A = {1,1,1,2,3,5,7}, B = {1,1,2,2,4,6,8} 兩個 bag, 它們的並就等於 {1,1,1,2,2,3,4,5,6,7,8}.

1.2.5 隱式型別轉換

通常來說, 我們會把型別完全一致, 並且代表相同屬性的列使用 UNION 合併到一起顯示, 但有時候, 即使資料型別不完全相同, 也會通過隱式型別轉換來將兩個型別不同的列放在一列裡顯示, 例如字串和數值型別:

SELECT product_id, product_name, '1'
  FROM Product
 UNION
SELECT product_id, product_name,sale_price
  FROM Product2;

上述查詢能夠正確執行,得到如下結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IWLiGgCj-1608652028950)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.07result4.png)]

練習題:

使用 SYSDATE()函式可以返回當前日期時間, 是一個日期時間型別的資料, 試測試該資料型別和數值,字串等型別的相容性.

例如, 以下程式碼可以正確執行, 說明時間日期型別和字串,數值以及缺失值均能相容.

SELECT SYSDATE(), SYSDATE(), SYSDATE()
 
 UNION
 
SELECT 'chars', 123,  null

上述程式碼的查詢結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-RKnVcd94-1608652028952)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.08result5.png)]

1.3 MySQL 8.0 不支援交運算INTERSECT

集合的交, 就是兩個集合的公共部分, 由於集合元素的互異性, 集合的交只需通過文氏圖就可以很直觀地看到它的意義.

雖然集合的交運算在SQL標準中已經出現多年了, 然而很遺憾的是, 截止到 MySQL 8.0 版本, MySQL 仍然不支援 INTERSECT 操作.

SELECT product_id, product_name
  FROM Product
  
INTERSECT
SELECT product_id, product_name
  FROM Product2

錯誤程式碼:1064
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT product_id, product_name
FROM Product2

1.3.1 [擴充套件閱讀]bag 的交運算

對於兩個 bag, 他們的交運算會按照: 1.該元素是否同時屬於兩個 bag, 2.該元素在兩個 bag 中的最小出現次數這兩個方面來進行計算. 因此對於 A = {1,1,1,2,3,5,7}, B = {1,1,2,2,4,6,8} 兩個 bag, 它們的交運算結果就等於 {1,1,2}.

1.4 差集,補集與表的減法

求集合差集的減法運算和實數的減法運算有些不同, 當使用一個集合A減去另一個集合B的時候,對於只存在於集合B而不存在於集合A的元素, 採取直接忽略的策略,因此集合A和B做減法只是將集合A中也同時屬於集合B的元素減掉.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tN1IsN0a-1608652028955)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.09except.png)]

1.4.1 MySQL 8.0 還不支援 EXCEPT 運算

MySQL 8.0 還不支援 表的減法運算子 EXCEPT. 不過, 藉助第六章學過的NOT IN 謂詞, 我們同樣可以實現表的減法.

練習題**?*

找出只存在於Product表但不存在於Product2表的商品.

-- 使用 IN 子句的實現方法
SELECT * 
  FROM Product
 WHERE product_id NOT IN (SELECT product_id 
                            FROM Product2)

1.4.2 EXCEPT 與 NOT 謂詞

通過上述練習題的MySQL解法, 我們發現, 使用 NOT IN 謂詞, 基本上可以實現和SQL標準語法中的EXCEPT運算相同的效果.

練習題:

使用NOT謂詞進行集合的減法運算, 求出Product表中, 售價高於2000,但利潤低於30%的商品, 結果應該如下表所示.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-UsFUEHvy-1608652028957)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.10.png)]

參考答案:

SELECT * 
  FROM Product
 WHERE sale_price > 2000 
   AND product_id NOT IN (SELECT product_id 
                            FROM Product 
                           WHERE sale_price<1.3*purchase_price)

1.4.3 EXCEPT ALL 與bag 的差

類似於UNION ALL, EXCEPT ALL 也是按出現次數進行減法, 也是使用bag模型進行運算.

對於兩個 bag, 他們的差運算會按照:

1.該元素是否屬於作為被減數的 bag,

2.該元素在兩個 bag 中的出現次數

這兩個方面來進行計算. 只有屬於被減數的bag的元素才參與EXCEP ALL運算, 並且差bag中的次數,等於該元素在兩個bag的出現次數之差(差為零或負數則不出現). 因此對於 A = {1,1,1,2,3,5,7}, B = {1,1,2,2,4,6,8} 兩個 bag, 它們的差就等於 {1,3,5,7}.

1.4.4 INTERSECT 與 AND 謂詞

對於同一個表的兩個查詢結果而言, 他們的交INTERSECT實際上可以等價地將兩個查詢的檢索條件用AND謂詞連線來實現.

練習題:

****使用AND謂詞查詢product表中利潤率高於50%,並且售價低於1500的商品,查詢結果如下所示.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-KpB8oapx-1608652028959)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.11.png)]

參考答案

SELECT * 
  FROM Product
 WHERE sale_price > 1.5 * purchase_price 
   AND sale_price < 1500

1.5 對稱差

兩個集合A,B的對稱差是指那些僅屬於A或僅屬於B的元素構成的集合. 對稱差也是個非常基礎的運算, 例如, 兩個集合的交就可以看作是兩個集合的並去掉兩個集合的對稱差.上述方法在其他資料庫裡也可以用來簡單地實現表或查詢結果的對稱差運算: 首先使用UNION求兩個表的並集, 然後使用INTERSECT求兩個表的交集, 然後用並集減去交集, 就得到了對稱差.

但由於在MySQL 8.0 裡, 由於兩個表或查詢結果的並不能直接求出來, 因此並不適合使用上述思路來求對稱差. 好在還有差集運算可以使用. 從直觀上就能看出來, 兩個集合的對稱差等於 A-B並上B-A, 因此實踐中可以用這個思路來求對稱差.

練習題:

使用Product表和Product2表的對稱差來查詢哪些商品只在其中一張表, 結果類似於:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wUVHiqIF-1608652028960)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.12.png)]

提示: 使用 NOT IN 實現兩個表的差集.

參考答案:

-- 使用 NOT IN 實現兩個表的差集
SELECT * 
  FROM Product
 WHERE product_id NOT IN (SELECT product_id FROM Product2)
UNION
SELECT * 
  FROM Product2
 WHERE product_id NOT IN (SELECT product_id FROM Product)

1.5.1 藉助並集和差集迂迴實現交集運算 INTERSECT

通過觀察集合運算的文氏圖, 我們發現, 兩個集合的交可以看作是兩個集合的並去掉兩個集合的對稱差.

練習題:

藉助上述對稱差的實現方式, 求Product和Product2的交.

參考答案略.

2. 連結(JOIN)

前一節我們學習了 UNION和INTERSECT 等集合運算, 這些集合運算的特徵就是以行方向為單位進行操作. 通俗地說, 就是進行這些集合運算時, 會導致記錄行數的增減. 使用 UNION 會增加記錄行數,而使用 INTERSECT 或者 EXCEPT 會減少記錄行數.

但這些運算不能改變列的變化, 雖然使用函式或者 CASE表示式等列運算, 可以增加列的數量, 但仍然只能從一張表中提供的基礎資訊列中獲得一些"引申列", 本質上並不能提供更多的資訊. 如果想要從多個表獲取資訊, 例如, 如果我們想要找出某個商店裡的衣服類商品的名稱,數量及價格等資訊, 則必須分別從 ShopProduct 表和 Product 表獲取資訊.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-teshC0nX-1608652028962)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.13join.png)]

注:
截至目前,本書中出現的示例(除了關聯子查詢)基本上都是從一張表中選取資料,但實際上,期望得到的資料往往會分散在不同的表之中, 這時候就需要使用連結了.
之前在學習關聯子查詢時我們發現, 使用關聯子查詢也可以從其他表獲取資訊, 但連結更適合從多張表獲取資訊.

連結(JOIN)就是使用某種關聯條件(一般是使用相等判斷謂詞"="), 將其他表中的列新增過來, 進行“新增列”的集合運算. 可以說,連結是 SQL 查詢的核心操作, 掌握了連結, 能夠從兩張甚至多張表中獲取列, 能夠將過去使用關聯子查詢等過於複雜的查詢簡化為更加易讀的形式, 以及進行一些更加複雜的查詢.

SQL 中的連結有多種分類方法, 我們這裡使用最基礎的內連結和外連結的分類方法來分別進行講解.

2.1 內連結(INNER JOIN)

內連結的語法格式是:

-- 內連結
FROM <tb_1> INNER JOIN <tb_2> ON <condition(s)>

其中 INNER 關鍵詞表示使用了內連結, 至於內連結的涵義, 目前暫時可以不必細究.
例如, 還是剛才那個問題:

找出某個商店裡的衣服類商品的名稱,數量及價格等資訊.

我們進一步把這個問題明確化:

找出東京商店裡的衣服類商品的商品名稱,商品價格,商品種類,商品數量資訊.

2.1.1 使用內連結從兩個表獲取資訊

我們先來分別觀察所涉及的表, Product 表儲存了商品編號,商品名稱,商品種類等資訊,這個表可以提供關於衣服種類的衣服的詳細資訊, 但是不能提供商店資訊.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ivspZngT-1608652028964)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.14tb.png)]

我們接下來觀察 ShopProduct 表, 這個表裡有商店編號名稱,商店的商品編號及數量. 但要想獲取商品的種類及名稱售價等資訊,則必須藉助於 Product 表.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qJ8rDr39-1608652028966)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.15shopproduct.png)]

所以問題的關鍵是, 找出一個類似於"軸"或者"橋樑"的公共列, 將兩張表用這個列連結起來. 這就是連結運算所要作的事情.

我們來對比一下上述兩張表, 可以發現, 商品編號列是一個公共列, 因此很自然的事情就是用這個商品編號列來作為連線的“橋樑”,將Product和ShopProduct這兩張表連線起來。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MqlSWJGR-1608652028968)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.16tb73.png)]

注:
如果你使用過 excel 的 vlookup 函式, 你會發現這個函式正好也能夠實現這個功能. 實際上, 在思路上, 關聯子查詢更像是 vlookup 函式: 以表 A 為主表, 然後根據表 A 的關聯列的每一行的取值,逐個到表 B 中的關聯列中去查詢取值相等的行.
當資料量較少時, 這種方式並不會有什麼效能問題, 但資料量較大時, 這種方式將會導致較大的計算開銷: 對於外部查詢返回的每一行資料, 都會向內部的子查詢傳遞一個關聯列的值, 然後內部子查詢根據傳入的值執行一次查詢然後返回它的查詢結果. 這就使得, 例如外部主查詢的返回結果有一萬行, 那麼子查詢就會執行一萬次, 這將會帶來非常恐怖的時間消耗.

我們把上述問題進行分解:

首先, 找出每個商店的商店編號, 商店名稱, 商品編號, 商品名稱, 商品類別, 商品售價,商品數量資訊.

按照內連結的語法, 在 FROM 子句中使用 INNER JOIN 將兩張表連線起來, 併為 ON 子句指定連結條件為 ShopProduct.product_id=Product.product_id, 就得到了如下的查詢語句:

SELECT SP.shop_id
       ,SP.shop_name
       ,SP.product_id
       ,P.product_name
       ,P.product_type
       ,P.sale_price
       ,SP.quantity
  FROM ShopProduct AS SP
 INNER JOIN Product AS P
    ON SP.product_id = P.product_id;

在上述查詢中, 我們分別為兩張表指定了簡單的別名, 這種操作在使用連結時是非常常見的, 通過別名會讓我們在編寫查詢時少打很多字, 並且更重要的是, 會讓查詢語句看起來更加簡潔.
上述查詢將會得到如下的結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zW8F0xm4-1608652028969)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.17result.png)]

觀察查詢結果, 我們看到,這個結果裡的列已經包含了所有我們需要的資訊.

關於內連結,需要注意以下三點:

要點一: 進行連結時需要在 FROM 子句中使用多張表.

之前的 FROM 子句中只有一張表, 而這次我們同時使用了 ShopProduct 和 Product 兩張表,使用關鍵字 INNER JOIN 就可以將兩張表連結在一起了:

FROM ShopProduct AS SP INNER JOIN Product AS P

要點二:必須使用 ON 子句來指定連結條件.

在進行內連結時 ON 子句是必不可少的(大家可以試試去掉上述查詢的 ON 子句後會有什麼結果).

ON 子句是專門用來指定連結條件的, 我們在上述查詢的 ON 之後指定兩張表連結所使用的列以及比較條件, 基本上, 它能起到與 WHERE 相同的篩選作用, 我們會在本章的結尾部分進一步探討這個話題.

要點三: SELECT 子句中的列最好按照 表.列名 的格式來使用.

當兩張表的列除了用於關聯的列之外, 沒有名稱相同的列的時候, 也可以不寫表名, 但表名使得我們能夠在今後的任何時間閱讀查詢程式碼的時候, 都能馬上看出每一列來自於哪張表, 能夠節省我們很多時間.

但是, 如果兩張表有其他名稱相同的列, 則必須使用上述格式來選擇列名, 否則查詢語句會報錯.

我們回到上述查詢所回答的問題. 通過觀察上述查詢的結果, 我們發現, 這個結果離我們的目標: 找出東京商店的衣服類商品的基礎資訊已經很接近了. 接下來,我們只需要把這個查詢結果作為一張表, 給它增加一個 WHERE 子句來指定篩選條件.

2.1.2 結合 WHERE 子句使用內連結

如果需要在使用內連結的時候同時使用 WHERE 子句對檢索結果進行篩選, 則需要把 WHERE 子句寫在 ON 子句的後邊.

例如, 對於上述查詢問題, 我們可以在前一步查詢的基礎上, 增加 WHERE 條件.

增加 WHERE 子句的方式有好幾種,我們先從最簡單的說起.

第一種增加 WEHRE 子句的方式, 就是把上述查詢作為子查詢, 用括號封裝起來, 然後在外層查詢增加篩選條件.

SELECT *
  FROM (-- 第一步查詢的結果
        SELECT SP.shop_id
               ,SP.shop_name
               ,SP.product_id
               ,P.product_name
               ,P.product_type
               ,P.sale_price
               ,SP.quantity
          FROM ShopProduct AS SP
         INNER JOIN Product AS P
            ON SP.product_id = P.product_id) AS STEP1
 WHERE shop_name = '東京'
   AND product_type = '衣服' ;

還記得我們學習子查詢時的認識嗎? 子查詢的結果其實也是一張表,只不過是一張虛擬的表, 它並不真實存在於資料庫中, 只是資料庫中其他表經過篩選,聚合等查詢操作後得到的一個"檢視".
這種寫法能很清晰地分辨出每一個操作步驟, 在我們還不十分熟悉 SQL 查詢每一個子句的執行順序的時候可以幫到我們.

但實際上, 如果我們熟知 WHERE 子句將在 FROM 子句之後執行, 也就是說, 在做完 INNER JOIN … ON 得到一個新表後, 才會執行 WHERE 子句, 那麼就得到標準的寫法:

SELECT  SP.shop_id
       ,SP.shop_name
       ,SP.product_id
       ,P.product_name
       ,P.product_type
       ,P.sale_price
       ,SP.quantity
  FROM ShopProduct AS SP
 INNER JOIN Product AS P
    ON SP.product_id = P.product_id
 WHERE SP.shop_name = '東京'
   AND P.product_type = '衣服' ;

我們首先給出上述查詢的執行順序:

FROM 子句->WHERE 子句->SELECT 子句

也就是說, 兩張表是先按照連結列進行了連結, 得到了一張新表, 然後 WHERE 子句對這張新表的行按照兩個條件進行了篩選, 最後, SELECT 子句選出了那些我們需要的列.

此外, 一種不是很常見的做法是,還可以將 WHERE 子句中的條件直接新增在 ON 子句中, 這時候 ON 子句後最好用括號將連結條件和篩選條件括起來.

SELECT SP.shop_id
       ,SP.shop_name
       ,SP.product_id
       ,P.product_name
       ,P.product_type
       ,P.sale_price
       ,SP.quantity
  FROM ShopProduct AS SP
 INNER JOIN Product AS P
    ON (SP.product_id = P.product_id
   AND SP.shop_name = '東京'
   AND P.product_type = '衣服') ;

但上述這種把篩選條件和連結條件都放在 ON 子句的寫法, 不是太容易閱讀, 不建議大家使用.
另外, 先連結再篩選的標準寫法的執行順序是, 兩張完整的表做了連結之後再做篩選,如果要連結多張表, 或者需要做的篩選比較複雜時, 在寫 SQL 查詢時會感覺比較吃力. 在結合 WHERE 子句使用內連結的時候, 我們也可以更改任務順序, 並採用任務分解的方法,先分別在兩個表使用 WHERE 進行篩選,然後把上述兩個子查詢連結起來.

SELECT SP.shop_id
       ,SP.shop_name
       ,SP.product_id
       ,P.product_name
       ,P.product_type
       ,P.sale_price
       ,SP.quantity
  FROM (-- 子查詢 1:從 ShopProduct 表篩選出東京商店的資訊
        SELECT *
          FROM ShopProduct
         WHERE shop_name = '東京' ) AS SP
 INNER JOIN -- 子查詢 2:從 Product 表篩選出衣服類商品的資訊
   (SELECT *
      FROM Product
     WHERE product_type = '衣服') AS P
    ON SP.product_id = P.product_id;

先分別在兩張表裡做篩選, 把複雜的篩選條件按表分拆, 然後把篩選結果(作為表)連線起來, 避免了寫複雜的篩選條件, 因此這種看似複雜的寫法, 實際上整體的邏輯反而非常清晰. 在寫查詢的過程中, 首先要按照最便於自己理解的方式來寫, 先把問題解決了, 再思考優化的問題.
練習題:

找出每個商店裡的衣服類商品的名稱及價格等資訊. 希望得到如下結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-OP5m0eKa-1608652028971)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.18result2.png)]

-- 參考答案 1--不使用子查詢
SELECT  SP.shop_id,SP.shop_name,SP.product_id 
       ,P.product_name, P.product_type, P.purchase_price
  FROM shopproduct  AS SP 
 INNER JOIN Product AS P 
    ON SP.product_id = P.product_id
 WHERE P.product_type = '衣服';
-- 參考答案 2--使用子查詢
SELECT  SP.shop_id, SP.shop_name, SP.product_id
       ,P.product_name, P.product_type, P.purchase_price
  FROM shopproduct AS SP 
INNER JOIN --從 Product 表找出衣服類商品的資訊
  (SELECT product_id, product_name, product_type, purchase_price
     FROM Product	
    WHERE product_type = '衣服')AS P 
   ON SP.product_id = P.product_id;

上述第二種寫法雖然包含了子查詢, 並且程式碼行數更多, 但由於每一層的目的很明確, 更適於閱讀, 並且在外連結的情形下, 還能避免錯誤使用 WHERE 子句導致外連結失效的問題, 相關示例見後文中的"結合 WHERE 子句使用外連結"章節.
練習題:

分別使用連結兩個子查詢和不使用子查詢的方式, 找出東京商店裡, 售價低於 2000 的商品資訊,希望得到如下結果.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CdTZ6Q2b-1608652028973)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.19result3.png)]

-- 參考答案
-- 不使用子查詢
SELECT SP.*, P.*
  FROM ShopProduct AS SP 
 INNER JOIN Product AS P 
    ON SP.product_id = P.product_id
 WHERE shop_id = '000A'
   AND sale_price < 2000;

2.1.3 結合 GROUP BY 子句使用內連結

結合 GROUP BY 子句使用內連結, 需要根據分組列位於哪個表區別對待.

最簡單的情形, 是在內連結之前就使用 GROUP BY 子句.

但是如果分組列和被聚合的列不在同一張表, 且二者都未被用於連結兩張表, 則只能先連結, 再聚合.

練習題:

每個商店中, 售價最高的商品的售價分別是多少?

-- 參考答案
SELECT SP.shop_id
      ,SP.shop_name
      ,MAX(P.sale_price) AS max_price
  FROMshopproduct AS SP
 INNER JOINproduct AS P
    ON SP.product_id = P.product_id
 GROUP BY SP.shop_id,SP.shop_name

**思考題:**上述查詢得到了每個商品售價最高的商品, 但並不知道售價最高的商品是哪一個.如何獲取每個商店裡售價最高的商品的名稱和售價?

注: 這道題的一個簡易的方式是使用下一章的視窗函式. 當然, 也可以使用其他我們已經學過的知識來實現, 例如, 在找出每個商店售價最高商品的價格後, 使用這個價格再與 Product 列進行連結, 但這種做法在價格不唯一時會出現問題.

**練習題:**每類商品中售價最高的商品都在哪些商店有售?

2.1.4 自連結(SELF JOIN)

之前的內連結, 連結的都是不一樣的兩個表. 但實際上一張表也可以與自身作連結, 這種連線稱之為自連結. 需要注意, 自連結並不是區分於內連結和外連結的第三種連結, 自連結可以是外連結也可以是內連結, 它是不同於內連結外連結的另一個連結的分類方法.

2.1.5 內連結與關聯子查詢

回憶第五章第三節關聯子查詢中的問題: 找出每個商品種類當中售價高於該類商品的平均售價的商品.當時我們是使用關聯子查詢來實現的.

SELECT product_type, product_name, sale_price
  FROM product AS P1
 WHERE sale_price > (SELECT AVG(sale_price)
                       FROM product AS P2
                      WHERE P1.product_type = P2.product_type
                      GROUP BY product_type);

使用內連結同樣可以解決這個問題:
首先, 使用 GROUP BY 按商品類別分類計算每類商品的平均價格.

SELECT  product_type
       ,AVG(sale_price) AS avg_price 
  FROM Product 
 GROUP BY product_type;

接下來, 將上述查詢與表 Product 按照 product_type (商品種類)進行內連結.

SELECT  P1.product_id
       ,P1.product_name
       ,P1.product_type
       ,P1.sale_price
       ,P2.avg_price
  FROM Product AS P1 
 INNER JOIN 
   (SELECT product_type,AVG(sale_price) AS avg_price 
      FROM Product 
     GROUP BY product_type) AS P2 
    ON P1.product_type = P2.product_type;

最後, 增加 WHERE 子句, 找出那些售價高於該類商品平均價格的商品.完整的程式碼如下:

SELECT  P1.product_id
       ,P1.product_name
       ,P1.product_type
       ,P1.sale_price
       ,P2.avg_price
  FROM Product AS P1
 INNER JOIN 
   (SELECT product_type,AVG(sale_price) AS avg_price 
      FROM Product 
     GROUP BY product_type) AS P2 
    ON P1.product_type = P2.product_type
 WHERE P1.sale_price > P2.avg_price;

僅僅從程式碼量上來看, 上述方法似乎比關聯子查詢更加複雜, 但這並不意味著這些程式碼更難理解. 通過上述分析, 很容易發現上述程式碼的邏輯實際上更符合我們的思路, 因此儘管看起來複雜, 但思路實際上更加清晰.
作為對比, 試分析如下程式碼:

SELECT  P1.product_id
       ,P1.product_name
       ,P1.product_type
       ,P1.sale_price
       ,AVG(P2.sale_price) AS avg_price
  FROM product AS P1
 INNER JOIN product AS P2
    ON P1.product_type=P2.product_type
 WHERE P1.sale_price > P2.sale_price
 GROUP BY P1.product_id,P1.product_name,P1.product_type,P1.sale_price,P2.product_type

雖然去掉了子查詢,查詢語句的層次更少, 而且程式碼行數似乎更少, 但實際上這個方法可能更加難以寫出來. 在實踐中, 一定要按照易於讓自己理解的思路去分層次寫程式碼, 而不要花費很長世間寫出一個效率可能更高但自己和他人理解起來難度更高的程式碼.
練習題:

1.分別使用內連結和關聯子查詢每一類商品中售價最高的商品.

2.1.6 自然連結(NATURAL JOIN)

自然連結並不是區別於內連結和外連結的第三種連結, 它其實是內連結的一種特例–當兩個表進行自然連結時, 會按照兩個表中都包含的列名來進行等值內連結, 此時無需使用 ON 來指定連線條件.

SELECT *  FROM shopproduct NATURAL JOIN Product

上述查詢得到的結果, 會把兩個表的公共列(這裡是 product_id, 可以有多個公共列)放在第一列, 然後按照兩個表的順序和表中列的順序, 將兩個表中的其他列都羅列出來.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-j1yDY8uo-1608652028975)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.20.png)]

練習題:

試寫出與上述自然連結等價的內連結.

-- 參考答案
SELECT  SP.product_id,SP.shop_id,SP.shop_name,SP.quantity
       ,P.product_name,P.product_type,P.sale_price
       ,P.purchase_price,P.regist_date  
  FROM shopproduct AS SP 
 INNER JOIN Product AS P 
    ON SP.product_id = P.product_id

使用自然連結還可以求出兩張表或子查詢的公共部分, 例如教材中 7-1 選取表中公共部分–INTERSECT 一節中的問題: 求表 Product 和表 Product2 中的公共部分, 也可以用自然連結來實現:

SELECT * FROM Product NATURAL JOIN Product2

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Qb96p107-1608652028978)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.21.png)]

這個結果和書上給的結果並不一致, 少了運動 T 恤, 這是由於運動 T 恤的 regist_date 欄位為空, 在進行自然連結時, 來自於 Product 和 Product2 的運動 T 恤這一行資料在進行比較時, 實際上是在逐欄位進行等值連結, 回憶我們在 6.2ISNULL,IS NOT NULL 這一節學到的缺失值的比較方法就可得知, 兩個缺失值用等號進行比較, 結果不為真. 而連結只會返回對連結條件返回為真的那些行.

如果我們將查詢語句進行修改:

SELECT * 
  FROM (SELECT product_id, product_name
          FROM Product ) AS A 
NATURAL JOIN 
   (SELECT product_id, product_name 
      FROM Product2) AS B;

那就可以得到正確的結果了:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-uCgNPXDV-1608652028979)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.22.png)]

2.1.7 使用連結求交集

我們在上一節表的加減法裡知道, MySQL 8.0 裡沒有交集運算, 我們當時是通過並集和差集來實現求交集的. 現在學了連結, 讓我們試試使用連結來實現求交集的運算.

練習題: 使用內連結求 Product 表和 Product2 表的交集.

SELECT P1.*
  FROM Product AS P1
 INNER JOIN Product2 AS P2
    ON (P1.product_id  = P2.product_id
   AND P1.product_name = P2.product_name
   AND P1.product_type = P2.product_type
   AND P1.sale_price   = P2.sale_price
   AND P1.regist_date  = P2.regist_date)

得到如下結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-GkBsYOpP-1608652028981)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.23.png)]

注意上述結果和 P230 的結果並不一致–少了 product_id='0001’這一行, 觀察源表資料可發現, 少的這行資料的 regist_date 為缺失值, 回憶第六章講到的 IS NULL 謂詞, 我們得知, 這是由於缺失值是不能用等號進行比較導致的.

如果我們僅僅用 product_id 來進行連結:

SELECT P1.*
  FROM Product AS P1
 INNER JOIN Product2 AS P2
    ON P1.product_id = P2.product_id

查詢結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-RiFG1Ym7-1608652028983)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.24.png)]

這次就一致了.

2.2 外連結(OUTER JOIN)

內連結會丟棄兩張表中不滿足 ON 條件的行,和內連結相對的就是外連結. 外連結會根據外連結的種類有選擇地保留無法匹配到的行.

按照保留的行位於哪張表,外連結有三種形式: 左連結, 右連結和全外連結.

左連結會儲存左表中無法按照 ON 子句匹配到的行, 此時對應右表的行均為缺失值; 右連結則會儲存右表中無法按照 ON 子句匹配到的行, 此時對應左表的行均為缺失值; 而全外連結則會同時儲存兩個表中無法按照 ON子句匹配到的行, 相應的另一張表中的行用缺失值填充.

三種外連結的對應語法分別為:

-- 左連結     
FROM <tb_1> LEFT  OUTER JOIN <tb_2> ON <condition(s)>
-- 右連結     
FROM <tb_1> RIGHT OUTER JOIN <tb_2> ON <condition(s)>
-- 全外連結
FROM <tb_1> FULL  OUTER JOIN <tb_2> ON <condition(s)>

2.2.1 左連結與右連結

由於連結時可以交換左表和右表的位置, 因此左連結和右連結並沒有本質區別.接下來我們先以左連結為例進行學習. 所有的內容在調換兩個表的前後位置, 並將左連結改為右連結之後, 都能得到相同的結果. 稍後再介紹全外連結的概念.

2.2.2 使用左連結從兩個表獲取資訊

如果你仔細觀察過將 ShopProduct 和 Product 進行內連結前後的結果的話, 你就會發現, Product 表中有兩種商品並未在內連結的結果裡, 就是說, 這兩種商品並未在任何商店有售(這通常意味著比較重要的業務資訊, 例如, 這兩種商品在所有商店都處於缺貨狀態, 需要及時補貨). 現在, 讓我們先把之前內連結的 SELECT 語句轉換為左連結試試看吧.

練習題: 統計每種商品分別在哪些商店有售, 需要包括那些在每個商店都沒貨的商品.

使用左連結的程式碼如下(注意區別於書上的右連結):

SELECT SP.shop_id
       ,SP.shop_name
       ,SP.product_id
       ,P.product_name
       ,P.sale_price
  FROM Product AS P
  LEFT OUTER JOIN ShopProduct AS SP
    ON SP.product_id = P.product_id;

上述查詢得到的檢索結果如下(由於並未使用 ORDER BY 子句指定順序,你執行上述程式碼得到的結果可能順序與下圖不同):

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cJPdJfgw-1608652028984)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.25.png)]

我們觀察上述結果可以發現, 有兩種商品: 高壓鍋和圓珠筆, 在所有商店都沒有銷售. 由於我們在 SELECT 子句選擇列的顯示順序以及未對結果進行排序的原因, 這個事實需要你仔細地進行觀察.

●外連結要點 1: 選取出單張表中全部的資訊

與內連結的結果相比,不同點顯而易見,那就是結果的行數不一樣.內連結的結果中有 13 條記錄,而外連結的結果中有 15 條記錄,增加的 2 條記錄到底是什麼呢?這正是外連結的關鍵點. 多出的 2 條記錄是高壓鍋和圓珠筆,這 2 條記錄在 ShopProduct 表中並不存在,也就是說,這 2 種商品在任何商店中都沒有銷售.由於內連結只能選取出同時存在於兩張表中的資料,因此只在 Product 表中存在的 2 種商品並沒有出現在結果之中.相反,對於外連結來說,只要資料存在於某一張表當中,就能夠讀取出來.在實際的業務中,例如想要生成固定行數的單據時,就需要使用外連結.如果使用內連結的話,根據 SELECT 語句執行時商店庫存狀況的不同,結果的行數也會發生改變,生成的單據的版式也會受到影響,而使用外連結能夠得到固定行數的結果.雖說如此,那些表中不存在的資訊我們還是無法得到,結果中高壓鍋和圓珠筆的商店編號和商店名稱都是 NULL (具體資訊大家都不知道,真是無可奈何).外連結名稱的由來也跟 NULL 有關,即“結果中包含原表中不存在(在原表之外)的資訊”.相反,只包含表內資訊的連結也就被稱為內連結了.

****外連結要點 2:使用 LEFT、RIGHT 來指定主表.

外連結還有一點非常重要,那就是要把哪張表作為主表.最終的結果中會包含主表內所有的資料.指定主表的關鍵字是 LEFT 和 RIGHT.顧名思義,使用 LEFT 時 FROM 子句中寫在左側的表是主表,使用 RIGHT 時右側的表是主表.程式碼清單 7-11 中使用了 RIGHT ,因此,右側的表,也就是 Product 表是主表.我們還可以像程式碼清單 7-12 這樣進行改寫,意思完全相同.這樣你可能會困惑,到底應該使用 LEFT 還是 RIGHT?其實它們的功能沒有任何區別,使用哪一個都可以.通常使用 LEFT 的情況會多一些,但也並沒有非使用這個不可的理由,使用 RIGHT 也沒有問題.

通過交換兩個表的順序, 同時將 LEFT 更換為 RIGHT(如果原先是 RIGHT,則更換為 LEFT), 兩種方式會到完全相同的結果.

2.2.3 結合 WHERE 子句使用左連結

上一小節我們學到了外連結的基礎用法, 並且在上一節也學習了結合WHERE子句使用內連結的方法, 但在結合WHERE子句使用外連結時, 由於外連結的結果很可能與內連結的結果不一樣, 會包含那些主表中無法匹配到的行, 並用缺失值填寫另一表中的列, 由於這些行的存在, 因此在外連結時使用WHERE子句, 情況會有些不一樣. 我們來看一個例子:

練習題:

使用外連結從ShopProduct表和Product表中找出那些在某個商店庫存少於50的商品及對應的商店.希望得到如下結果.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oUTVNhZc-1608652028986)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.26.png)]

注意高壓鍋和圓珠筆兩種商品在所有商店都無貨, 所以也應該包括在內.

按照"結合WHERE子句使用內連結"的思路, 我們很自然會寫出如下程式碼

SELECT P.product_id
       ,P.product_name
       ,P.sale_price
       ,SP.shop_id
       ,SP.shop_name
       ,SP.quantity
  FROM Product AS P
  LEFT OUTER JOIN ShopProduct AS SP
    ON SP.product_id = P.product_id
 WHERE quantity< 50

然而不幸的是, 得到的卻是如下的結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-uxY5hi5G-1608652028989)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.27.png)]

觀察發現, 少了在所有商店都無貨的高壓鍋和圓珠筆. 聰明的你可能很容易想到,在WHERE子句中增加 quantity IS NULL 的條件, 然而在真實的查詢環境中, 由於資料量大且資料質量並非如系統說明和我們設想的那樣"乾淨", 我們並不能很容易地意識到缺失值等問題資料的存在, 因此,還是讓我們想一下如何改寫我們的查詢以使得它能夠適應更復雜的真實資料的情形吧.

聯絡到我們已經掌握了的SQL查詢的執行順序(FROM->WHERE->SELECT),我們發現, 問題可能出在篩選條件上, 因為在進行完外連結後才會執行WHERE子句, 因此那些主表中無法被匹配到的行就被WHERE條件篩選掉了.

明白了這一點, 我們就可以試著把WHERE子句挪到外連結之前進行: 先寫個子查詢,用來從ShopProduct表中篩選quantity<50的商品, 然後再把這個子查詢和主表連結起來.

我們把上述思路寫成SQL查詢語句:

SELECT P.product_id
      ,P.product_name
      ,P.sale_price
       ,SP.shop_id
      ,SP.shop_name
      ,SP.quantity 
  FROM Product AS P
  LEFT OUTER JOIN-- 先篩選quantity<50的商品
   (SELECT *
      FROM ShopProduct
     WHERE quantity < 50 ) AS SP
    ON SP.product_id = P.product_id

得到的結果如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-30vjjmvz-1608652028990)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.28.png)]

4.2.2.4 在 MySQL 中實現全外連結

有了對左連結和右連結的瞭解, 就不難理解全外連結的含義了. 全外連結本質上就是對左表和右表的所有行都予以保留, 能用 ON 關聯到的就把左表和右表的內容在一行內顯示, 不能被關聯到的就分別顯示, 然後把多餘的列用缺失值填充.

遺憾的是, MySQL8.0 目前還不支援全外連結, 不過我們可以對左連結和右連結的結果進行 UNION 來實現全外連結.

4.2.3 多表連結

通常連結只涉及 2 張表,但有時也會出現必須同時連結 3 張以上的表的情況, 原則上連結表的數量並沒有限制.

4.2.3.1 多表進行內連結

首先建立一個用於三表連結的表 InventoryProduct.首先我們建立一張用來管理庫存商品的表, 假設商品都儲存在 P001 和 P002 這 2 個倉庫之中.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-z9ZvdQ1w-1608652028992)(https://github.com/datawhalechina/team-learning-sql/blob/main/img/ch04/ch04.29.png)]

建表語句如下:

CREATE TABLE InventoryProduct
( inventory_id       CHAR(4) NOT NULL,
product_id         CHAR(4) NOT NULL,
inventory_quantity INTEGER NOT NULL,
PRIMARY KEY (inventory_id, product_id));

然後插入一些資料:

--- DML:插入資料
START TRANSACTION;
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0001', 0);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0002', 120);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0003', 200);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0004', 3);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0005', 0);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0006', 99);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0007', 999);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0008', 200);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0001', 10);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0002', 25);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0003', 34);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0004', 19);
INSERT INTO InventoryProduct (inventory_id, product_id, inventory_quantity)
VALUES ('P00

相關文章