# 1201資料庫(實習課) ## HINT 執行順序 `5:Select` `1:From` `2:Where` `3:Group` `4:Having` `6:Order By` --- ## 期中講解 ### 1. OrderDetails中包含了每筆訂單的資訊,其中ProductID為該訂單訂購的物品<br>請統計OrderDetails每個ProductID的訂購總數(COUNT),以及該ProductID最高的UnitPrice(MAX_PRICE) 語法執行後必須要有ProductID, COUNT, MAX_PRICE 1. 先觀察資料表 ![](https://i.imgur.com/RYR13W3.png) 2. 因為是要針對ProductID做統計,可以先對ProductID進行排序 ```typescript=sql SELECT * FROM OrderDetails ORDER BY ProductID ``` ![](https://i.imgur.com/K1GJAa6.png) 3. 先檢視需求,需求為每個ProductID的訂購總數,以及最高的UnitPrice 4. 其中OrderID的數量即每個ProductID的訂購總數,也可以看到UnitPrice 5. 使用==COUNT()==計算筆數,==MAX()==得到最高價 6. 因為要得到的數值為根據ProductID做分類(彙總函式幾乎都要用到GROUP BY),所以語法則如下 ```typescript=SQL SELECT ProductID, COUNT(ProductID) [COUNT], MAX(UnitPrice) MAX_PRICE FROM OrderDetails GROUP BY ProductID ``` #### GROUP BY 1.假設有一份資料表如下 | OrderID | ProductID | UnitPrice | | ------- | --------- | --------- | | 10628 | 1 | 18 | | 10689 | 1 | 17 | | 10918 | 1 | 19 | | 10866 | 2 | 19 | | 10856 | 2 | 21 | | 10741 | 2 | 10 | 2.如果對ProductID做group by,可以想像成表會被整理成下列的樣子 | OrderID | ProductID | UnitPrice | | ----------------------- |:---------:| ---------------- | | 10628<br>10689<br>10918 | 1 | 18 <br>17<br>19 | | OrderID | ProductID | UnitPrice | | ----------------------- |:---------:|:--------------- | | 10866<br>10856<br>10741 | 2 | 19 <br>21<br>10 | ##### <font color="red">實際上並不會被處理成這樣,這是幫助理解所製作的</font> 3. 所以在select時可以針對ProductID直接選取,但是其他欄位選取會出現錯誤 ![](https://i.imgur.com/tnzLLSh.png) ### 2. 有兩個table,分別稱為A、B<br>A用來記錄每筆訂單的資訊,B用來將A中各筆訂單所購買的細項拆開逐筆紀錄<br>如A紀錄了一筆 | 流水號 | 訂購單號 | 訂購者 | 訂購者位置 | 電話 | | ------ | -------- | ------ | ---------- |:-----:| | 1 | 00001 | 大熊 | 淡江大學 | 09123 | ### B則記錄了每個單號的購買細項 | 流水號 | 訂購單號 | 訂購項目 | 單價 | 數量 | | ------ | -------- | -------- | ----- | ---- | | 1 | 00001 | 電腦 | 25000 | 1 | | 2 | 00001 | 筆電 | 22000 | 2 | | 3 | 00001 | 音響 | 3000 | 1 | ### 2-1 請設計一段語法可以將A、B兩張table關聯性結合 1. 一般來說,一個訂購系統(如MOMO),會將訂購訂單跟明細區分,如上A及B(經過正規化)<br>因此要對不同的表格進行鏈結,需要透過兩張表格的primary key跟foreign key 2. 表A及B的primary key都是流水號,foreign key都是訂購單號(這些都得自己判斷) ![](https://i.imgur.com/weAbfKQ.png) 3. 因此則利用inner join的方式將兩張表作結合 ```typescript=sql SELECT * FROM A AS A INNER JOIN B AS B ON A.訂購單號 = B.訂購單號 ``` ### 2-2 請統計B表中各個訂購單號中,每個訂購項目的價錢總和(TOTAL),其中單價超過20000的商品須打85折 1. 了解需求,需要得到訂購單號中每個項目的金額總和 2. 單價超過20000要打折 3. 金額總和包刮了被打折後的金額,因此要先處理打折的問題 ```typescript=sql SELECT (CASE WHEN 單價>20000 THEN 單價*0.85 ELSE 單價 END) AS 打折後單價, * FROM B ``` 4. 上方指令已經針對打折問題進行處理,所以可以在處理原先的大問題,把價錢做總和並得到下方的資料表(執行上方語法後得到的結果) | 打折後單價 | 流水號 | 訂購單號 | 訂購項目 | 單價 | 數量 | | ---------- | ------ | -------- | -------- | ----- | ---- | | 20000 | 1 | 00001 | 電腦 | 25000 | 1 | | 17600 | 2 | 00001 | 筆電 | 22000 | 2 | | 3000 | 3 | 00001 | 音響 | 3000 | 1 | 5. 3.中的語法所選取的資料可以是做一個資料表使用(衍生資料表) ```typescript=sql SELECT 訂購單號, SUM(打折後單價*數量) TOTAL FROM (SELECT (CASE WHEN 單價>20000 THEN 單價*0.85 ELSE 單價 END) AS 打折後單價, * FROM B) AS B GROUP BY 訂購單號 ``` ### 3. OrderDetails中包含了每筆訂單的資訊,其中ProductID為該訂單訂購的物品<br>先利用ProductID作分組後,根據每個ProductID的UnitPrice作排名(PRICE_RANK),排名不得重複 #### e.g. ProductID 1 有兩筆訂單,其中UnitPrice分別為 3、5;ProductID 2有三筆訂單,其中UnitPrice分別為7、2、3,則結果要呈現如下 | ProductID | UnitPrice | PRICE_RANK | | --------- | --------- | ---------- | | 1 | 3 | 1 | | 1 | 5 | 2 | | 2 | 2 | 1 | | 2 | 3 | 2 | | 2 | 7 | 3 | 1. 了解需求,幫每一個ProductID中的UnitPrice做排序 2. 觀察原始資料 ```typescript=sql SELECT ProductID, UnitPrice FROM OrderDetails ORDER BY ProductID, UnitPrice ``` ![](https://i.imgur.com/iy2e7M9.png) 3. 雖然可以透過ORDER得到排序結果,但需要加上額外的排名欄位 4. 排名不得重複->ROW_NUMBER()排序 5. 由於1.,先用partition幫ProductID分割,根據order幫UnitPrice做排序,但因為partition過後,會隨著ProductID有一棟跟著重新做編號,進而得到下方結果(<font color="red">實際上並不會被處理成這樣,這是幫助理解所製作的</font>) | ProductID | UnitPrice | | --------- |:----------------- | | 1 | 18 <br>18<br>18 | | 2 | 19 <br>19<br>19 | | 3 | 10<br>10<br>10 | | 4 | 17.6 <br>22<br>22 | 6. 再針對裡面的UnitPrice做order排序 ```typescript=SQL SELECT ProductID, ROW_NUMBER() OVER(PARTITION BY ProductID ORDER BY UnitPrice) PRICE_RANK, UnitPrice FROM OrderDetails ``` 7. partition by跟group by很類似,都有分群的作用,一般在做資料彙總我會用group,排序用partition,當然partition也可以做到group的結果 8. 細節可以參考 https://learnsql.com/blog/difference-between-group-by-partition-by/ ### 4. OrderDetails中包含了每筆訂單的資訊,其中ProductID為該訂單訂購的物品<br>Orders紀錄各筆訂單的序號以及訂購者的ID<br>Customers紀錄每個訂購者的資訊 ### 4-1 找出每一個訂購者的總消費金額,再幫每一位訂購者的總消費金額作排序(需重複且連續)<br>(需要JOIN三個TABLE,可參考1110資料庫第三題) 1. 先觀察每個資料表的資料 **OrderDetails** ![](https://i.imgur.com/CG4pmhh.png) **Orders** ![](https://i.imgur.com/EJcinYX.png) **Customers** ![](https://i.imgur.com/Sor6ARE.png) 2. 三個資料表有著這樣的關係 ![](https://i.imgur.com/t8lYkuY.png) 3. 先將三個表格用inner join合併成一張表 ```typescript=SQL SELECT * FROM Orders AS O INNER JOIN OrderDetails AS OD ON O.OrderID = OD.OrderID INNER JOIN Customers AS C ON O.CustomerID = C.CustomerID ``` ![](https://i.imgur.com/LIXEmwD.png) 4. 依照需求,先幫所有訂購者的消費作加總 ![](https://i.imgur.com/7OmUXhg.png) 5. 依照上圖已經被合併的表單(先依照CustomerID做排序),觀察CustomerID有幾筆購買筆數 6. 因為要計算每個CustomerID的總購買金額(group by),因此需要用 ==SUM()== 來做加總 ```typescript=SQL SELECT O.CustomerID, O.CompanyName, SUM(OD.UnitPrice*OD.Qty) TOTAL FROM Orders AS O INNER JOIN OrderDetails AS OD ON O.OrderID = OD.OrderID INNER JOIN Customers AS C ON O.CustomerID = C.CustomerID GROUP BY O.CustomerID, O.CompanyName ``` 7. 可以想像group by後資料表會變成如下(部分示範) **Before group**(by CustomerID) | CustomerID | Name | ProductID | UnitPrice | | ---------- | ---- | --------- | --------- | | 1 | A | 28 | 45.6 | | 1 | A | 39 | 18 | | 1 | A | 46 | 12 | | 1 | A | 63 | 43.9 | | 2 | B | 11 | 21 | | 2 | B | 13 | 6 | **After group**(by CustomerID)(示意圖) | CustomerID | Name | ProductID | UnitPrice | | ---------- | ---------------- |:---------------------- | ------------------------- | | 1 | A<br>A<br>A<br>A | 28 <br>39<br>46<br>63 | 45.6 <br>18<br>12<br>43.9 | | 2 | B <br>B | 11 <br>13 | 21 <br>6 | **After group**(by CustomerID and Name)<font color="red">(這樣才能select Name)</font>(示意圖) | CustomerID | Name | ProductID | UnitPrice | | ---------- | ---- |:---------------------- |:------------------------- | | 1 | A | 28 <br>39<br>46<br>63 | 45.6 <br>18<br>12<br>43.9 | | 2 | B | 11 <br>13 | 21 <br>6 | 8. 得到加總過後的資料表 ```typescript=SQL SELECT O.CustomerID, SUM(OD.UnitPrice*OD.Qty) AS TOTAL FROM Orders AS O INNER JOIN OrderDetails AS OD ON O.OrderID = OD.OrderID INNER JOIN Customers AS C ON O.CustomerID = C.CustomerID GROUP BY O.CustomerID ``` | CustomerID | Name | UnitPrice | | ---------- | ---- |:--------- | | 1 | A | 119.5 | | 2 | B | 27 | 9. 要在為每個CustomerID添加排序,因此將8.得到的結果視為一張表在進行操作(衍生資料表) 10. 排名需重複且連續->==DENSE_RANK()== ```typescript=SQL SELECT DENSE_RANK() OVER(ORDER BY TOTAL) AS BUY_RANK,* FROM ( 8.的語法 ) AS DT ``` ### 4-2 將(1)中的語法轉成使用Common Table Expressions ```typescript=sql WITH CTE AS( 8.的語法 ) SELECT DENSE_RANK() OVER(ORDER BY TOTAL) AS BUY_RANK,* FROM CTE; ``` --- ## 課堂講解 ### 建立資料庫 ```typescript=SQL CREATE DATABSE DB_NAME ``` #### 檢查資料庫是否存在 ```typescript=sql IF DB_ID('TSQL2012') IS NOT NULL DROP DATABASE DB_NAME; ``` --- ### 建立資料表 ```typescript=sql CREATE TABLE CUSTOMER( CUST_ID INT NOT NULL , CUST_IDENT CHAR(10) NOT NULL , LAST_NAME NVARCHAR(255) NOT NULL, FIRST_NAME NVARCHAR(255), AGE SMALLINT, CONSTRAINT PK_EmployeeSSSs PRIMARY KEY(CUST_ID,CUST_IDENT) ); ``` CHAR型態儲存使用固定長度 VARCHAR型態儲存可變的長度,根據INPUT長度決定空間大小 開頭+N,代表使用Unicode儲存 ### 練習 1 #### 新增一個資料表,欄位如下 | 學生學號 | 學生姓名 | 系級 | 選修科目 | | --------- | -------- | ------ | -------- | | 405410530 | 李白 | 資工8A | 資料庫 | --- ### 新增資料 ```typescript=SQL INSERT INTO CUSTOMER(CUST_ID,CUST_IDENT,LAST_NAME,FIRST_NAME,AGE) VALUES(1,'A12345678','王','大槌','28'); ``` ### 練習 2 #### 新增以下四筆資料到資料表內 | 學生學號 | 學生姓名 | 系級 | 選修科目 | | --------- | -------- | ------ | -------- | | 405410530 | 李白 | 資工8A | 資料庫 | | 405411362 | 王大槌 | 資工9A | 資料庫 | | 405411462 | 李勤 | 資工8C | 資料庫 | | 405411158 | 陳水餃 | 資工4C | 資料庫 | --- ### 更新資料 ```typescript=SQL UPDATE CUSTOMER SET AGE = 29 WHERE CUST_ID = 1; ``` ### 練習 3 #### 將李白的系級改成資工9A,王大槌的系級改成資工10A --- ### 刪除資料 ```typescript=SQL DELETE FROM CUSTOMER WHERE CUST_ID = 1; ``` ### 練習 4 #### 將姓李的資料移除 HINT. 使用SUBSTRING(欄位,1,1)