# SQL 喔天 超複雜的東東 >Thx ChatGPT~ [TOC] ## 新增資料 ![螢幕擷取畫面 2025-02-25 103957](https://hackmd.io/_uploads/B19jTo9c1e.png=50%) 在這即可輸入你要的行列,每行的名稱以及類型「ID,int,不允許Null」 ![螢幕擷取畫面 2025-02-25 104128](https://hackmd.io/_uploads/By91Co99kx.png) 記得要儲存以及重跑一下資料庫,就可以看到加入的資料 ![螢幕擷取畫面 2025-02-25 104551](https://hackmd.io/_uploads/H1Jb1hc5Je.png) 選取前1000、200資料列都可以手動加入資料 ![螢幕擷取畫面 2025-02-25 104743](https://hackmd.io/_uploads/rJMPy2c5ye.png) 常見權限問題: ---- 右鍵資料庫 屬性 > 檔案 > 擁有者 > 新增目前使用者 ![image](https://hackmd.io/_uploads/S1oS_9lZex.png) ## 建立資料庫 也可以使用新增查詢使用程式語言來新增資料庫 1. 開啟 SSMS → 新建查詢 1. 建立資料庫 → CREATE DATABASE MyStore; 1. 切換資料庫 → USE MyStore; 1. 建立資料表 → CREATE TABLE Customers (...); 1. 插入資料 → INSERT INTO Customers (...) VALUES (...); 1. 查詢資料 → SELECT * FROM Customers; ```sql= CREATE DATABASE MyDatabase; ``` ### 使用資料庫 ```sql= USE MyDatabase; ``` ## 建立資料表 ```sql= CREATE TABLE Users ( UserID INT PRIMARY KEY IDENTITY(1,1), -- 自增主鍵 UserName NVARCHAR(50) NOT NULL, -- 使用 NVARCHAR 支援 Unicode Age INT CHECK (Age >= 0), -- 年齡必須大於等於 0 Email NVARCHAR(100) UNIQUE, -- Email 欄位唯一 CreatedAt DATETIME DEFAULT GETDATE() -- 預設值為當前時間 ); ``` ### 設定主鍵 主鍵的特點: * 唯一性(Unique):主鍵欄位內的值不能重複。 * 非空性(Not NULL):主鍵欄位的值不能為空(NULL)。 * 一個表只能有一個主鍵,但可以由 單個欄位或多個欄位組合 而成。 同一筆訂單 (OrderID) 不能有 重複的產品 (Product)。 ```sql= CREATE TABLE Orders ( OrderID INT, CustomerName NVARCHAR(50), Product NVARCHAR(100), PRIMARY KEY (OrderID, Product) -- 設定 OrderID 和 Product 為「複合主鍵」 ); ``` ### 設定外鍵 🔹 透過「外鍵(FOREIGN KEY)」建立連結 🔹 假設我們有兩張表: * Customers(客戶表) * Orders(訂單表) * Orders.CustomerID 參考 Customers.CustomerID,建立連結 ```sql= CREATE TABLE Customers ( CustomerID INT PRIMARY KEY, -- 主鍵 Name VARCHAR(50) NOT NULL ); CREATE TABLE Orders ( OrderID INT PRIMARY KEY, -- 訂單的主鍵 CustomerID INT, -- 這是外鍵 OrderAmount DECIMAL(10,2), -- 這條語法建立「連結」: FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID) ); ``` ✅ 這樣 Orders 的 CustomerID 一定要對應 Customers 的 CustomerID,不能輸入不存在的客戶! #### 💡💡連結的其他功能 當在進行 刪除或更新時 ,與你連結的那個資料也會一起進行 | 設定 | 效果 | |--------------------------|------------------------------------------------------------| | ON DELETE CASCADE | 如果刪除 Customers 裡的 CustomerID = 1,則 Orders 裡所有 CustomerID = 1 的訂單也會被刪除。 | | ON DELETE SET NULL | 如果刪除 Customers.CustomerID = 1,則 Orders.CustomerID 會變成 NULL。 | | ON UPDATE CASCADE | 如果 Customers.CustomerID = 1 改成 2,則 Orders.CustomerID = 1 也會自動變 2。 | 📌 這樣就能確保數據的正確性,避免孤立的資料! ### 主鍵 和 外鍵 * 主鍵 (PRIMARY KEY) 🔹 主鍵 是 表中的唯一識別碼,用來唯一標識每一筆資料。 🔹 一個表只能有一個主鍵,但主鍵可以包含多個欄位(複合鍵)。 🔹 主鍵的值不能重複,也不能是 NULL! 💡 範例:建立 Customers 表並設定 CustomerID 為主鍵 ```sql= CREATE TABLE Customers ( CustomerID INT PRIMARY KEY, -- 主鍵 Name VARCHAR(50) NOT NULL, Email VARCHAR(100) UNIQUE ); ``` ✅ CustomerID 是主鍵,代表每個客戶的唯一編號,不可重複或為 NULL。 * 外鍵 (FOREIGN KEY) 🔹 外鍵 是 參考另一個表的主鍵,**建立兩個表之間的關聯**。 🔹 外鍵的值必須來自於它所參考的主鍵,這樣才能確保數據的正確性! 🔹 外鍵可以有多個(同一個表內可以有多個外鍵)。 💡 範例:建立 Orders 表,並用 CustomerID 作為外鍵 ```sql= CREATE TABLE Orders ( OrderID INT PRIMARY KEY, -- 訂單主鍵 CustomerID INT, -- 這是外鍵 OrderAmount DECIMAL(10,2), -- 設定 `CustomerID` 為外鍵,參考 `Customers` 表的 `CustomerID` FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID) ); ``` ✅ 這樣 Orders 表中的 CustomerID 一定要對應 Customers 表的 CustomerID,否則 SQL Server 會拒絕插入數據! 💡外鍵的作用 🔹 確保資料的完整性 * 避免無效的資料寫入 🔹 建立關聯 * 讓不同表之間能夠關聯起來 🔹 防止刪除或更新錯誤 * 可以設定刪除或更新時的行為 ### 主鍵 和 外鍵 的關係 | 名稱 | 說明 | 限制 | |--------------------|----------------------------------------|------------------------------------------| | 主鍵 (PRIMARY KEY) | 唯一標識表中的每一行數據 | 不能重複,不能為 NULL | | 外鍵 (FOREIGN KEY) | 參考另一個表的 PRIMARY KEY,用來建立關聯 | 必須是另一個表的 PRIMARY KEY 中的值,或者為 NULL(除非 NOT NULL 限制) | ### 📌 總結 | 功能 | PRIMARY KEY(主鍵) | FOREIGN KEY(外鍵) | |--------------------|--------------------------------------------------|-------------------------------------------------| | 作用 | 唯一識別該表內的每一筆數據 | 參考另一張表的 PRIMARY KEY,建立關聯 | | 是否唯一? | ✅ 是,不能重複 | ❌ 不是,可以重複(但必須是另一表的 PRIMARY KEY 值) | | 是否可為 NULL? | ❌ 不可以 | ✅ 可以(除非 NOT NULL 限制) | | 一張表可以有幾個? | 只能有一個 | 可以有多個 | | 常見用途 | 客戶編號 (CustomerID)、訂單編號 (OrderID) | 訂單的 CustomerID 參考 Customers 表 | ## 連結資料 目前看來,我常與C#進行連結,這其中大概就只有那一長串的不一樣了 以下幾種都可以連結到的 * Windows 驗證:後面為=True; * SQL Server 驗證:其中會有User Id=****;Password=****或者底部為.mdf;如此 範例: ```csharp= // 使用 Windows 驗證(Trusted Connection) string connectionString = "Server=localhost;Database=YourDatabase;Trusted_Connection=True;"; // 使用 SQL Server 驗證(需要帳號密碼) string connectionString = "Server=localhost;Database=YourDatabase;User Id=sa;Password=YourPassword;"; ``` ### 連結測試 C#測試方法 ```csharp= string connectionString = "超長一串ㄟ"; using (SqlConnection conn = new SqlConnection(connectionString)) { try { conn.Open(); MessageBox.Show("✅ 成功連接到 SQL Server!"); } catch (Exception ex) { MessageBox.Show($"❌ 連線失敗:{ex.Message}"); } } ``` ### SQL server 在新增查詢裡撰寫:USE MyDatabase(資料表名稱); SQL 觸發器範例 ```sql= USE MyDatabase; -- 確保操作的資料庫是正確的 CREATE TRIGGER trg_AfterInsertOrder ON Orders AFTER INSERT AS BEGIN PRINT '新的訂單已插入!'; -- 觸發時顯示訊息 END; ``` ✅ 當 Orders 表新增資料時,這個觸發器就會自動執行! ## 獲取 SQL 語法 1. 開啟 SQL Server Management Studio (SSMS),並連接到你的 SQL Server。 1. 在 「物件總管」(Object Explorer) 中,找到你的 資料庫 (Database),然後展開它。 1. 在 「資料表 (Tables)」 裡找到你要查詢的表,例如 test3。 1. 右鍵點擊資料表,選擇 「Script Table as」,然後選擇你需要的 SQL 語法: * SELECT To → 產生 SELECT 查詢語法 * INSERT To → 產生 INSERT INTO 語法 * UPDATE To → 產生 UPDATE 語法 * DELETE To → 產生 DELETE 語法 5. 選擇 To New Query Editor Window,SSMS 會自動開啟一個新視窗,並顯示對應的 SQL 語法。 ![image](https://hackmd.io/_uploads/rkp0M42c1e.png) 我建議第一個用到 新增查詢編輯器視窗 ,剩下的用到 剪貼簿 ,用貼上的發法集中在一個 查詢編輯器視窗 ### 範例 假設你選擇 INSERT To,SSMS 可能會產生: ```sql= INSERT INTO [dbo].[test3] ([ID], [Name], [Age]) VALUES (<ID, int,>, <Name, nvarchar(50),>, <Age, int,>) ``` 你可以根據需求修改,例如: ```sql= INSERT INTO [dbo].[test3] ([ID], [Name], [Age]) VALUES (1, 'Anna', 25) ``` ## 創建資料語法 * NEWID() 隨機生成一個ID:96833a93-1924-4cd2-93af-0e7354723d79 * SYSDATETIMEOFFSET() 生成一個時間:2022-11-11 08:20:15 -07:00 ## 插入(新增)資料 ```sql= INSERT INTO Users (UserName, Age, Email) VALUES ('小明', 25, 'xiaoming@example.com'); ``` ### 插入多筆資料 ```sql= INSERT INTO Users (UserName, Age, Email) VALUES ('小華', 30, 'xiaohua@example.com'), ('小美', 28, 'xiaomei@example.com'); ``` ### 關聯性的插入資料 這樣可以確保 TicketID 和 MuseumID 在相應的表中已經存在,避免外鍵錯誤。 ```sql= INSERT INTO AdmissionRecord (ID, TicketID, MuseumID, RecordTime, IsRejected) VALUES (NEWID(), (SELECT TOP 1 TicketID FROM Tickets), (SELECT TOP 1 MuseumID FROM Museums), SYSDATETIMEOFFSET(), 0); ``` ## 查詢資料 ### 查詢所有欄位 ```sql= SELECT * FROM Users; ``` ### 查詢特定欄位 ```sql= SELECT UserName, Age FROM Users; ``` ### 查詢多種條件 📌 AND 與 OR 的區別 AND:兩個條件都必須滿足,才會返回結果。 OR:只要其中一個條件成立,就會返回結果。 普通時候可以加括號。AND 和 OR 混用,就需要加括號 ```sql SELECT * FROM Users WHERE UserName = 'Anna' AND Password = '12345'; ``` ### 只查詢 UserName 和 Password ```sql SELECT UserName, Password FROM Users; ``` ### WHERE 使用條件查詢 ```sql= SELECT * FROM Users WHERE Age > 25; ``` ### 使用 LIKE 模糊查詢 ```sql= SELECT * FROM Users WHERE UserName LIKE '小%'; -- 找出名字以「小」開頭的使用者 ``` ### JOIN 和 ON [SQL server:JOIN_ON](/mgbIC7QqSh2G9l3p5yq8Dw)這裡有一些說明 查詢 Orders(訂單表)和 Users(使用者表)中的資料,根據 UserID 這個欄位來進行關聯。 ```sql= SELECT * FROM Orders JOIN Users ON Orders.UserID = Users.UserID; ``` 🔹 JOIN:用來合併多張表 🔹 ON:指定關聯條件 ### 特定條件搜尋 ---- ✅ 正確寫法 — 使用 JOIN: 假設你想要從 BatteryChargeRecord 中查出 ExpectedMaxCapacity,前提是這筆資料的 VehicleID 和 Vehicle 資料表中的 ID 對應。 你應該使用 INNER JOIN,例如: ```SQL= SELECT B.[ExpectedMaxCapacity] FROM [Zyberion_Full].[dbo].[BatteryChargeRecord] AS B JOIN [Zyberion_Full].[dbo].[Vehicle] AS V ON B.[VehicleID] = V.[ID] ``` 這樣就可以正確地將兩張表結合起來,並且查出符合條件的 ExpectedMaxCapacity。 ❓延伸補充:你可能想要加條件(例如特定車輛名稱) 如果你想查某輛特定車的資料,可以再加條件,例如: ```sql= SELECT B.[ExpectedMaxCapacity] FROM [Zyberion_Full].[dbo].[BatteryChargeRecord] AS B JOIN [Zyberion_Full].[dbo].[Vehicle] AS V ON B.[VehicleID] = V.[ID] WHERE V.[Name] = 'Z-Car-01' ``` ### 排序 ASC、DESC 💡使用 SQL 查詢排序非主鍵欄位時,其他欄位的資料也會「一併」隨之排序。 ORDER BY 用來對查詢結果進行排序,可以 升序(遞增)或 降序(遞減)排列。 在 ORDER BY 裡不能使用 AND,因為 AND 是條件運算符(通常用在 WHERE 裡),而 ORDER BY 應該用 ,(逗號)來排序多個欄位。 ```sql= SELECT * FROM Users ORDER BY Age DESC; -- 依照年齡遞減排序 ``` 🔹 ORDER BY:排序查詢結果 🔹 ASC(預設):遞增(小→大) 🔹 DESC:遞減(大→小) ### ORDER BY 列出相同的資料 ```sql= SELECT [Orders].[OrderID] ,[Users].[Name] ,[Orders].[Product] FROM [dbo].[Users] JOIN [dbo].[Orders] ON [Orders].[UserID] = [Users].[UserID] ORDER BY [Users].[Name]; -- 讓相同 Name 排在一起 ``` ### ROW_NUMBER() 多少到多少 ROW_NUMBER() 會根據 ORDER BY Id 重新排序,然後分配行號。 可以篩選特定行號,例如: ```sql= SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY Id) AS RowNum FROM Users ) AS TempTable WHERE RowNum > 5 AND RowNum <= 10; -- 取得第 6 到 10 筆資料 ``` ### GROUP BY計算使用 在 SQL 裡面使用 聚合函數(像是 MAX()、MIN()、AVG()、SUM()、COUNT() 等),就必須使用 GROUP BY #### 基本語法 ```sql= SELECT 分組欄位, 聚合函數(...) FROM 資料表 GROUP BY 分組欄位 ``` #### 範例說明 假設你有一張 BatteryChargeRecord 表如下: | VehicleID | ExpectedMaxCapacity | | --------- | ------------------- | | V001 | 98 | | V001 | 95 | | V002 | 92 | | V002 | 96 | 你想要查出每台車目前曾出現過的 最大容量: ```sql= SELECT VehicleID, MAX(ExpectedMaxCapacity) AS MaxCap FROM BatteryChargeRecord GROUP BY VehicleID ``` 結果會是: | VehicleID | MaxCap | | --------- | ------ | | V001 | 98 | | V002 | 96 | #### 更多用法 | 函數 | 用法 | 說明 | | ---------- | ---- | -------- | | `COUNT(*)` | 計算筆數 | 每一組的資料筆數 | | `SUM(欄位)` | 加總 | 每一組的總和 | | `AVG(欄位)` | 平均值 | 每一組的平均值 | | `MAX(欄位)` | 最大值 | 每一組的最大值 | | `MIN(欄位)` | 最小值 | 每一組的最小值 | ### ROWNUM 前幾筆資料 * ROWNUM 是 Oracle 專有 的行號 (Row Number) 欄位。 * 它會在 查詢結果返回時 依序從 1 開始自動編號。 * 無法在 ORDER BY 之後使用,因為行號是在排序前就分配好的! ```csharp= SELECT ROWNUM, Users.* FROM Users WHERE ROWNUM <= 5; -- 取前 5 筆資料 ``` ### CASE 自訂排序規則 CASE 讓 VIP 使用者排在最前面 ```sql= SELECT * FROM Users ORDER BY CASE WHEN UserType = 'VIP' THEN 1 WHEN UserType = 'Regular' THEN 2 ELSE 3 END, Age ASC; ``` 1. UserType = 'VIP' 的排最前 1. UserType = 'Regular' 的排第二 1. 其他使用者排最後 1. 同類型內,再按 Age 遞增排序 ---- 方法二: 如果資料=這個條件,就顯示如何 ```sql= CASE EngineType WHEN '1' THEN 'gasoline or hybrid' ELSE 'pure electric' END AS EngineTypeDisplay ➜ 加了一個轉換後的新欄位叫 EngineTypeDisplay CASE BrandCode WHEN 'L7' THEN 'V' ELSE 'X' END AS Category ➜ 同樣新增了 Category 欄位 ``` ### 隨機排序 Users 表的資料將 隨機排列(每次查詢結果都不同) ```sql= SELECT * FROM Users ORDER BY NEWID(); ``` ### TOP 前幾筆資料 但 TOP 不能用於 分頁查詢,所以 SQL Server 2012 之後推薦使用 OFFSET FETCH。 ```sql= SELECT TOP 5 * FROM Users ORDER BY Age DESC; ``` ### 後幾筆資料 SQL Server 沒有直接提供 OFFSET 來取得最後幾筆,但我們可以透過 ORDER BY + DESC + FETCH 來達成。 | 方法 | 方式 | 優點 | 缺點 | |-------------------------------|----------------------------|-------------------------|--------------------------| | ORDER BY DESC + FETCH | 先倒序,再取前 N 筆 | 簡單直覺 | 需要再反向排序 | | COUNT(*) + OFFSET | 計算總筆數,然後 OFFSET | 不依賴 ID | 需要多一次查詢 | | TOP + ORDER BY DESC | 直接取最後 N 筆 | 執行效能好 | 需要子查詢來調整順序 | ### 分頁查詢 ```sql= SELECT * FROM Users ORDER BY Id ASC OFFSET 10 ROWS -- 跳過前 10 筆 FETCH NEXT 5 ROWS ONLY; -- 取出 5 筆 ``` * OFFSET 10 ROWS → 跳過前 10 筆資料 * FETCH NEXT 5 ROWS ONLY → 取 5 筆資料 | 關鍵字 | 作用 | |----------------------------|---------------------------| | OFFSET X ROWS | 跳過 X 筆資料,X 為要跳過的行數 | | FETCH NEXT Y ROWS ONLY | 取得 Y 筆資料,Y 為要取出的行數 | ### 最後一筆資料(最大 Id 的資料) 🔹SELECT TOP 1 *:取 最上面 1 筆資料 排序方式 | SQL 語法 | Entity Framework 語法 | 結果 ----------------------|------------------------|-------------------------------|---------------- 升序(順序排列) | ORDER BY Id ASC | OrderBy(u => u.Id) | Id 小 → 大 降序(最新資料在前) | ORDER BY Id DESC | OrderByDescending(u => u.Id) | Id 大 → 小 ```sql SELECT TOP 1 * FROM Users ORDER BY Id DESC ``` * 獲取 最後插入的 Id IDENT_CURRENT('Users') 會回傳 最後一筆 Id,不受 SESSION 限制 ```sql SELECT IDENT_CURRENT('Users') ``` ## EXISTS 資料有無存在(最佳方法) ```csharp= string query = "SELECT CASE WHEN EXISTS (SELECT 1 FROM Users WHERE UserName = @UserName) THEN 1 ELSE 0 END"; using (SqlCommand cmd = new SqlCommand(query, conn)) { cmd.Parameters.AddWithValue("@UserName", userNameToCheck); int exists = (int)cmd.ExecuteScalar(); // 執行查詢並取得結果 if (exists == 1) Console.WriteLine("資料存在"); else Console.WriteLine("資料不存在"); } ``` 📌 優點: ✅ EXISTS 會在找到符合條件的第一筆資料後就停止,不會繼續搜尋,效能較好。 ✅ 適合用於 條件判斷(例如 IF、CASE)。 ## COUNT(*) 資料有無存在 ```csharp= string query = "SELECT COUNT(*) FROM Users WHERE UserName = @UserName"; SqlCommand cmd = new SqlCommand(query, conn); cmd.Parameters.AddWithValue("@UserName", "Anna"); int count = (int)cmd.ExecuteScalar(); if (count > 0) Console.WriteLine("資料存在"); else Console.WriteLine("資料不存在"); ``` 📌 回傳值: * 0:代表資料 不存在 * 1 或以上:代表資料 存在 --- ✅優點: 可計算符合條件的筆數,適合用來檢查 多筆記錄 是否存在。 ❌缺點: 即使找到第一筆,仍然會繼續搜尋,可能影響效能。 ## 更新資料(修改資料) ⚠️⚠️不能直接修改 PRIMARY KEY,但可以間接修改! 因為 PRIMARY KEY 是唯一識別一筆資料的欄位,不能有重複值,直接 UPDATE 可能會影響關聯性。 如果真的要修改主鍵,通常有兩種方法: * 刪除舊的主鍵,然後新增新值(適用於沒有外鍵關聯的情況) * 關閉外鍵約束(Foreign Key Constraint),修改後再開啟(適用於有關聯的情況) ```sql= UPDATE Users SET Age = 26 WHERE UserName = '小明'; ``` ### 修改所有資料 (⚠️ 謹慎使用!) ```sql= UPDATE Users SET Age = 25; ``` ⚠️🚨 這會將 Users 表的 Age 欄位全部改為 25! 一定要加上 WHERE 限制影響範圍。 ### UPDATE 搭配 CASE 條件 ```sql= UPDATE Users SET Age = CASE WHEN UserID = 1 THEN 30 WHEN UserID = 2 THEN 25 ELSE Age -- 其他的保持不變 END; ``` * UserID = 1 的 Age 改為 30 * UserID = 2 的 Age 改為 25 * 其他 UserID 的 Age 不變 ### 搭配 JOIN(更新關聯表) ```sql= UPDATE Users SET Users.Age = Orders.NewAge FROM Users JOIN Orders ON Users.UserID = Orders.UserID WHERE Orders.OrderID = 1001; ``` * 找到 Orders 表中 OrderID = 1001 的 UserID * 用 Orders 表的 NewAge 更新 Users 表的 Age ### 搭配 EXISTS 只更新存在的資料 只更新「有下過訂單」的使用者,把 Status 改為 'Active' ```sql= UPDATE Users SET Status = 'Active' WHERE EXISTS ( SELECT 1 FROM Orders WHERE Orders.UserID = Users.UserID ); ``` * 只顯示 有下過訂單 (Orders 表有對應 UserID) 的 Users * EXISTS 只要有回傳結果就會是 TRUE,不管 SELECT 選的是什麼(SELECT 1 只是代表查詢有結果) ### 限制更新筆數 只更新 前 5 筆 的 Users,把 Age 改成 30。 ```sql= UPDATE TOP (5) Users SET Age = 30; ``` ### ⚠️常見錯誤預防 忘記加 WHERE → 所有資料都會被更新 * 用 SELECT 先測試 先確認影響的資料,再執行 UPDATE。 ```sql= SELECT * FROM Users WHERE UserID = 1; ``` * 更新後還原 如果沒有 BACKUP,可以透過 TRANSACTION 來保護 ```sql= BEGIN TRANSACTION; UPDATE Users SET Age = 30 WHERE UserID = 1; ROLLBACK; -- 取消變更 ``` 如果確定沒問題: ```sql= COMMIT; -- 確認變更 ``` ## DELETE 刪除資料 ```sql= DELETE FROM Users WHERE UserName = '小華'; ``` ### FROM 刪除所有資料(但不刪表) ```sql= DELETE FROM Users; ``` ### TRUNCATE 刪除並重置 ID(清空表) ```sql= TRUNCATE TABLE Users; ``` ### DROP 刪除表 ```sql= DROP TABLE Users; ``` ### 刪除指定的那一行 刪除與該 Id 同一行的 Name 和 Password 不影響其他相同 Name 和 Password 的行 ```sql DELETE FROM Users WHERE Id = @Id AND Name = (SELECT Name FROM Users WHERE Id = @Id) AND Password = (SELECT Password FROM Users WHERE Id = @Id); ``` ## 索引(Index) 索引(Index)是什麼? * 沒有索引:每次查詢時,資料庫需要 掃描整個表(Full Table Scan),效率較低。 * 有索引:資料庫會 快速定位到對應的資料行,查詢速度提升。 ### 索引設定: ```sql= CREATE INDEX idx_users_age ON Users(Age); ``` Users 表的 Age 欄位上建立索引、索引名稱為 idx_users_age、加快以 Age 為條件的查詢 ---- #### ❌沒有索引時 假設 Users 表有 100 萬筆 用戶資料: ```sql= SELECT * FROM Users WHERE Age = 25; ``` 1. 從第一筆資料開始 逐行掃描(Full Table Scan) 1. 檢查 Age 是否等於 25 1. 若找到匹配的資料,才返回結果 這種查詢方式 速度較慢,尤其當資料量很大時,效能會大幅降低。 #### ✅ 有索引後 當我們執行: ```sql= CREATE INDEX idx_users_age ON Users(Age); ``` * SQL Server 會建立一個索引表(像目錄一樣) * 這樣查詢 Age = 25 時,資料庫可以 直接找到符合條件的資料,而不是從頭掃描整個表,大幅提升查詢效率。 再執行: ```sql= SELECT * FROM Users WHERE Age = 25; ``` 資料庫會: 1. 先檢查索引 idx_users_age 1. 直接找到 Age = 25 的資料行位置 1. 快速返回結果 這樣可以 節省大量的查詢時間!! ---- ### 使用時機 #### ✅ 適合使用索引的情境 經常作為 WHERE 條件的欄位 ```sql= SELECT * FROM Users WHERE Age = 30; ``` JOIN 操作時的欄位 ```sql= SELECT * FROM Orders JOIN Users ON Orders.UserID = Users.UserID; ``` ORDER BY 或 GROUP BY 的欄位 ```sql= SELECT Age, COUNT(*) FROM Users GROUP BY Age; ``` #### ❌ 不適合索引的情境 1. 經常更新 (UPDATE) 或刪除 (DELETE) 的欄位 索引需要同步更新,頻繁變更的欄位可能影響效能。 2. 小型表格 如果表格只有幾百筆資料,索引效益不大,反而佔用額外空間。 3. 太多索引 過多的索引會影響 插入(INSERT)與更新(UPDATE) 效率。 ---- ### 其他類型 #### 1️⃣ 唯一索引(Unique Index) ```sql= CREATE UNIQUE INDEX idx_users_email ON Users(Email); ``` 📌 效果: * 不能插入 重複的 Email * 查詢 Email 時更快 --- #### 2️⃣ 複合索引(Composite Index) ```sql= CREATE INDEX idx_users_name_age ON Users(Name, Age); ``` 📌 效果: 查詢 Name 和 Age 時,效能更快: ```sql= SELECT * FROM Users WHERE Name = '小明' AND Age = 25; ``` ### 總結 🔹 CREATE INDEX idx_users_age ON Users(Age); ✔ 在 Age 欄位建立索引,加速查詢 ✔ 適合 WHERE 條件中常用的欄位 ✔ 可以改善 ORDER BY 或 GROUP BY 的效能 ✔ 適用於大規模資料表(幾十萬筆以上) 📌 但要小心: * 不要在 經常更新的欄位 建立索引,會影響效能 * 索引會佔用額外空間 * 不是每個欄位都需要索引,應該根據實際需求來建立 索引就像一本書的目錄,用得好可以加快查詢速度,但用太多反而會影響資料庫效能 ## 建立視圖(View) ```sql= CREATE VIEW AdultUsers AS SELECT * FROM Users WHERE Age >= 18; ``` ### 使用 ```sq= SELECT * FROM AdultUsers; ``` ## 存儲程序(Stored Procedure) 💡💡存儲程序(SP)是什麼? 事先寫好的 SQL 指令集合,像是 SQL 版本的函式。 | 優點 | 說明 | |--------------------------|------------------------------------------------------------| | 提高執行效能 | SQL Server 會預先編譯 SP,避免每次執行時重新解析 SQL。 | | 降低重複寫 SQL 的成本 | 只要呼叫 SP,不用每次都重新寫 SQL 指令,程式碼更整潔。 | | 增加安全性 | 可以設定**權限**🔐,限制哪些使用者能執行存儲程序。 | | 支援參數與邏輯運算 | SP 可以接收輸入參數、進行計算、回傳結果,類似程式語言的函式。 | | 減少資料庫與應用程式的流量 | 只要傳送 SP 名稱和參數,不需要傳整段 SQL 指令,減少伺服器負擔。 | ### 建立一個查詢用戶的存儲程序 假設有一個 Users 表: ```sql= CREATE TABLE Users ( UserID INT PRIMARY KEY, Name VARCHAR(100), Age INT ); ``` 建立一個存儲程序,讓使用者透過 UserID 查詢姓名和年齡: ```sql= CREATE PROCEDURE GetUserInfo @UserID INT -- 定義輸入參數 AS BEGIN -- 查詢指定 UserID 的使用者資料 SELECT Name, Age FROM Users WHERE UserID = @UserID; END; ``` 📌 這樣 GetUserInfo 就變成 SQL Server 裡的一個「函式」,可以隨時呼叫! --- 💠執行 💡💡當輸入我們函式名稱:GetUserInfo,就會執行查詢的步驟 ```sql= EXEC GetUserInfo @UserID = 1; ``` 如果 UserID = 1,就會回傳該用戶的 Name 和 Age。 ### 傳回多個結果 * 範例:傳回用戶總數 ```sql= CREATE PROCEDURE GetUserCount @TotalUsers INT OUTPUT -- 定義輸出參數 AS BEGIN SELECT @TotalUsers = COUNT(*) FROM Users; -- 計算總用戶數 END; ``` * 執行 SP 並取得 OUTPUT 值 ```sql= DECLARE @Count INT; EXEC GetUserCount @TotalUsers = @Count OUTPUT; PRINT '總用戶數: ' + CAST(@Count AS VARCHAR); ``` 📌這樣可以回傳 Users 表內的總筆數! ---- ### IF、WHILE 邏輯 存儲程序可以像程式一樣,使用 IF 判斷式或 WHILE 迴圈。 🔹 範例:根據年齡篩選用戶 ```sql= CREATE PROCEDURE GetUsersByAge @MinAge INT, @MaxAge INT AS BEGIN IF @MinAge > @MaxAge BEGIN PRINT '錯誤: 最小年齡不能大於最大年齡'; RETURN; END -- 查詢符合年齡範圍的使用者 SELECT * FROM Users WHERE Age BETWEEN @MinAge AND @MaxAge; END; ``` 🔹 執行: ```sql= EXEC GetUsersByAge @MinAge = 18, @MaxAge = 30; ``` 📌 這樣可以避免錯誤輸入,並確保查詢條件正確! --- ### 修改或刪除存儲程序 🔹 修改存儲程序 使用 ALTER PROCEDURE 來修改: ```sql= ALTER PROCEDURE GetUserInfo @UserID INT AS BEGIN SELECT Name, Age FROM Users WHERE UserID = @UserID; PRINT '查詢完成'; -- 加入提示訊息 END; ``` 🔹 刪除存儲程序 ```sql= DROP PROCEDURE GetUserInfo; ``` ## 存儲程序權限設定 GRANT、REVOKE、DENY 來管理 存儲程序 的執行權限,確保只有**特定使用者**能夠執行、修改或刪除存儲程序 | 權限 | 作用 | |--------------------|--------------------------------------| | EXECUTE | 執行存儲程序 | | ALTER | 修改存儲程序 | | CONTROL | 擁有完整的控制權,包括修改、刪除 | | TAKE OWNERSHIP | 允許變更擁有者 | | VIEW DEFINITION | 允許查看存儲程序的結構,但不能執行 | ### 如何設定 🔹 允許某個使用者執行存儲程序 ```sql= GRANT EXECUTE ON GetUserInfo TO UserName; ``` 📌 這樣 UserName 就能執行 GetUserInfo 存儲程序! --- 🔹 收回某個使用者的執行權限 如果不想讓某個使用者執行存儲程序: ```sql= REVOKE EXECUTE ON GetUserInfo FROM UserName; ``` 📌 這樣 UserName 就無法執行 GetUserInfo 了! ------- 🔹 明確禁止某個使用者執行存儲程序 如果要完全 禁止 某個使用者使用 EXECUTE: ```sql= DENY EXECUTE ON GetUserInfo TO UserName; ``` 📌 DENY 比 REVOKE 更強,會覆蓋任何已授予的權限! --- ### 不同角色的權限 在 SQL Server 中,可以透過 角色(Role) 來管理多個使用者的權限,這樣不用逐個設定權限。 🔹 允許 db_executor 角色執行所有存儲程序 ```sql= GRANT EXECUTE TO db_executor; ``` 📌 這樣所有屬於 db_executor 角色的使用者都能執行存儲程序! ---- 🔹 讓某個使用者加入 db_executor 角色 ```sql= EXEC sp_addrolemember 'db_executor', 'UserName'; ``` 📌 這樣 UserName 就可以執行所有存儲程序了! ---- ### 查看權限 🔹 查看某個存儲程序的權限 ```sql= SELECT * FROM sys.database_permissions WHERE major_id = OBJECT_ID('GetUserInfo'); ``` 📌 這樣可以查詢 GetUserInfo 存儲程序的權限! ----- 🔹 查看某個使用者的權限 ```sql= SELECT * FROM sys.database_permissions WHERE grantee_principal_id = DATABASE_PRINCIPAL_ID('UserName'); ``` 📌 這樣可以檢查 UserName 在這個資料庫中的所有權限! ## 觸發器(Trigger) 資料表發生特定事件(INSERT、UPDATE、DELETE)時自動執行。 當 Users 表有新記錄插入時,自動記錄到 Logs 表: ```sql= CREATE TABLE Logs ( LogID INT PRIMARY KEY IDENTITY(1,1), Message NVARCHAR(255), CreatedAt DATETIME DEFAULT GETDATE() ); CREATE TRIGGER trg_AfterInsert ON Users AFTER INSERT AS BEGIN INSERT INTO Logs (Message) SELECT '新使用者 ' + UserName + ' 已被新增' FROM inserted; END; ``` ### 觸發器的類型 | 類型 | 執行時機 | 說明 | |--------------------------|--------------------|------------------------------------------------------------| | AFTER 觸發器(FOR 觸發器) | 資料變更後 | 只有當 SQL 指令成功執行後才會觸發,適用於 INSERT、UPDATE、DELETE。 | | INSTEAD OF 觸發器 | 取代 SQL 指令 | 在 INSERT、UPDATE、DELETE 執行前攔截並執行替代動作,可以用來限制或改變操作。 | | DDL 觸發器 | 監控資料庫結構變更 | 監控 CREATE TABLE、ALTER TABLE、DROP TABLE 等結構變更。 | ### 1. AFTER 觸發器(FOR 觸發器) 當 INSERT、UPDATE 或 DELETE 成功執行後,觸發器才會執行! 🔹 範例:當新用戶加入時,自動在 Logs 表中記錄 假設我們有一個 Users 表: ```sql= CREATE TABLE Users ( UserID INT PRIMARY KEY, Name VARCHAR(100), Age INT ); ``` 再建立 Logs 表來記錄變更: ```sql= CREATE TABLE Logs ( LogID INT IDENTITY(1,1) PRIMARY KEY, LogMessage VARCHAR(255), LogDate DATETIME DEFAULT GETDATE() ); ``` 🔹 建立 AFTER INSERT 觸發器 ```sql= CREATE TRIGGER trg_AfterInsert_Users ON Users AFTER INSERT AS BEGIN INSERT INTO Logs (LogMessage) SELECT '新用戶加入:' + Name + ',年齡:' + CAST(Age AS VARCHAR) FROM inserted; END; ``` 📌 這樣,每當有新用戶被加入 Users,Logs 表就會自動新增一筆記錄! 🔹 測試觸發器 ```sql= INSERT INTO Users (UserID, Name, Age) VALUES (1, 'Alice', 25); SELECT * FROM Logs; -- 應該會看到「新用戶加入:Alice,年齡:25」 ``` ### 2. INSTEAD OF 觸發器 這種觸發器可以「攔截」原本的 INSERT、UPDATE 或 DELETE 操作,然後執行替代的指令。 🔹 範例:防止用戶刪除 ```sql= CREATE TRIGGER trg_InsteadOfDelete_Users ON Users INSTEAD OF DELETE AS BEGIN PRINT '刪除操作被攔截,Users 表的資料不能刪除!'; END; ``` 📌 這樣,Users 表的資料就無法刪除了! 🔹 測試觸發器 ```sql= DELETE FROM Users WHERE UserID = 1; -- 執行後會顯示「刪除操作被攔截,Users 表的資料不能刪除!」但資料不會被刪除 ``` ### 3. DDL 觸發器(監控資料庫結構變更) 有時候,你可能希望監控資料庫的結構變更,例如防止某些重要的 TABLE 被刪除。 🔹 防止刪除 Users 表 ```sql= CREATE TRIGGER trg_PreventDrop_Users ON DATABASE FOR DROP_TABLE AS BEGIN PRINT '禁止刪除 Users 表!'; ROLLBACK; -- 取消刪除操作 END; ``` 📌 這樣 Users 表就不能被刪除了! 🔹 測試 ```sql= DROP TABLE Users; -- 會顯示「禁止刪除 Users 表!」並且 `Users` 表不會被刪除。 ``` ### 刪除觸發器 如果不需要某個觸發器,可以刪除: ```sql= DROP TRIGGER trg_AfterInsert_Users; ``` 📌 這樣 trg_AfterInsert_Users 這個觸發器就被移除了! 如果是 DDL 觸發器,則需要加上 ON DATABASE: ```sql= DROP TRIGGER trg_PreventDrop_Users ON DATABASE; ``` ## 交易(Transaction) 💡💡簡單來說,交易就像「包裹」,要嘛全部成功,要嘛全部取消! 確保多個 SQL 語句一起執行,若發生錯誤則回滾: ```sql= BEGIN TRANSACTION; UPDATE Users SET Age = Age + 1 WHERE UserName = '小明'; DELETE FROM Users WHERE UserName = '小華'; COMMIT; ``` 如果發生錯誤,回滾: ```sql= BEGIN TRANSACTION; UPDATE Users SET Age = Age + 1 WHERE UserName = '小明'; DELETE FROM Users WHERE UserName = '小華'; IF @@ERROR <> 0 BEGIN ROLLBACK; END ELSE BEGIN COMMIT; END; ``` ### 交易的四大特性(ACID) | 特性 | 說明 | |------------------------|-------------------------------------------------------| | Atomicity(原子性) | 全部成功或全部回滾,確保不會只執行部分 SQL。 | | Consistency(一致性) | 確保交易後資料庫維持一致性,不會留下錯誤資料。 | | Isolation(隔離性) | 多個交易同時執行時,彼此不互相干擾。 | | Durability(持久性) | 交易成功後,資料永久儲存,即使系統崩潰也不會丟失。 | ### 基本語法 ```sql= BEGIN TRANSACTION; -- 開始交易 SQL 操作... COMMIT TRANSACTION; -- 確認提交 ``` 📌 如果交易成功,COMMIT 會永久儲存變更! ### 交易的回滾(ROLLBACK) 如果發生錯誤,可以使用 ROLLBACK 取消變更,確保資料完整性! 🔹 例子:銀行轉帳 ```sql= BEGIN TRANSACTION; UPDATE Accounts SET Balance = Balance - 1000 WHERE AccountID = 1; -- 扣款 UPDATE Accounts SET Balance = Balance + 1000 WHERE AccountID = 2; -- 存款 IF @ERROR <> 0 -- 檢查是否有錯誤 ROLLBACK TRANSACTION; -- 取消交易 ELSE COMMIT TRANSACTION; -- 確認交易 ``` 📌 如果扣款成功但存款失敗,ROLLBACK 會回復原狀,避免金額消失! ### TRY...CATCH 搭配交易 可以使用 TRY...CATCH 來處理錯誤,讓交易更加安全。 🔹 例子:防止錯誤導致資料錯亂 ```sql= BEGIN TRANSACTION; BEGIN TRY UPDATE Accounts SET Balance = Balance - 1000 WHERE AccountID = 1; UPDATE Accounts SET Balance = Balance + 1000 WHERE AccountID = 2; COMMIT TRANSACTION; -- 交易成功,提交 END TRY BEGIN CATCH ROLLBACK TRANSACTION; -- 發生錯誤,自動回滾 PRINT '交易失敗,已回滾!'; END CATCH ``` 📌 這樣即使 UPDATE 發生錯誤,也不會影響資料庫的一致性! ### 隔離等級 交易的隔離性決定了多個交易同時執行時,是否會互相影響。 | 隔離等級 | 說明 | 解決的問題 | |-------------------------|------------------------------------|---------------------------------| | READ UNCOMMITTED | 允許讀取未提交的資料 | 可能讀到錯誤數據(Dirty Read) | | READ COMMITTED(預設) | 只能讀取已提交的資料 | 避免 Dirty Read,但可能發生 Non-Repeatable Read | | REPEATABLE READ | 交易內讀取的資料不會被其他交易改變 | 避免 Non-Repeatable Read,但可能發生 Phantom Read | | SERIALIZABLE | 完全鎖住資料,確保交易隔離 | 避免 Phantom Read,但效能最低 | 🔹 設定交易的隔離等級 ```sql= SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION; -- SQL 操作... COMMIT TRANSACTION; ``` 📌 這樣可以確保交易內的資料完全不被其他交易影響! ### 總結 | 功能 | 語法 | |--------------------|-------------------------------| | 開始交易 | BEGIN TRANSACTION; | | 提交交易 | COMMIT TRANSACTION; | | 回滾交易 | ROLLBACK TRANSACTION; | | 使用錯誤處理 | TRY...CATCH | | 設定隔離等級 | SET TRANSACTION ISOLATION LEVEL ... | ## 資料表別名 其實這兩種寫法是一樣的意思: ```SQL= FROM Vehicle AS V ``` 與 ```SQL= FROM Vehicle V ``` 效果 **完全相同**,都是給 Vehicle 這張資料表取一個別名叫 V,後面就可以用 V.欄位名 來引用。 | 寫法 | 說明 | | -------------- | --------------- | | `Vehicle V` | ✅ 最常見寫法,簡潔清楚 | | `Vehicle AS V` | 🔸 比較正式,有些人覺得好讀 | 可以使程式碼更簡短好懂,實際範例: ```sql= FROM Vehicle V JOIN AccountWithVehicle AWV ON V.ID = AWV.VehicleID ``` ## NEWID() 特定ID 使用 NEWID() 函數,它會自動生成一個新的 GUID(全局唯一標識符)。 **💠GUID 格式** NEWID() 返回的 GUID 格式如下: * 96833A93 (8位十六進制數) * 1924 (4位十六進制數) * 4CD2 (4位十六進制數) * 93AF (4位十六進制數) * 0E7354723D79 (12位十六進制數) ## ALTER 用來修改資料 ### 新增欄位 (ADD) * 新增欄位 (ADD): ```sql= ALTER TABLE TableName ADD ColumnName DataType; ``` 例如,新增一個 Age 欄位: ```sql= ALTER TABLE Employees ADD Age INT; ``` * 刪除欄位 (DROP COLUMN): ```sql= ALTER TABLE TableName DROP COLUMN ColumnName; ``` 例如,刪除 Age 欄位: ```sql= ALTER TABLE Employees DROP COLUMN Age; ``` * 修改欄位資料型態 (ALTER COLUMN): ```sql= TABLE TableName ALTER COLUMN ColumnName NewDataType; ``` 例如,將 Age 欄位的資料型態修改為 VARCHAR: ```sql= ALTER TABLE Employees ALTER COLUMN Age VARCHAR(100); ``` ### 修改表格的名稱 (RENAME 在某些 SQL 系統中,通常使用 sp_rename 在 SQL Server 中) ```sql= EXEC sp_rename 'OldTableName', 'NewTableName'; ``` ### 新增約束 * 新增主鍵: ```sql= ALTER TABLE TableName ADD CONSTRAINT PK_ConstraintName PRIMARY KEY (ColumnName); ``` 例如,將 EmployeeID 設為主鍵: ```sql= ALTER TABLE Employees ADD CONSTRAINT PK_EmployeeID PRIMARY KEY (EmployeeID); ``` * 新增外鍵: ```sql= ALTER TABLE TableName ADD CONSTRAINT FK_ConstraintName FOREIGN KEY (ColumnName) REFERENCES OtherTable (OtherColumn); ``` 例如,將 DepartmentID 設為外鍵,參照 Departments 表的 DepartmentID 欄位: ```sql= ALTER TABLE Employees ADD CONSTRAINT FK_DepartmentID FOREIGN KEY (DepartmentID) REFERENCES Departments (DepartmentID); ``` ### 刪除約束 刪除外鍵: ```sql= ALTER TABLE TableName DROP CONSTRAINT FK_ConstraintName; ``` 例如,刪除外鍵約束: ```sql= ALTER TABLE Employees DROP CONSTRAINT FK_DepartmentID; ``` ## CTE 臨時結果集 它可以在同一個 WITH 查詢內被多次引用。這讓查詢更易讀,也能讓一些操作更高效。 ```sql= WITH CTE名稱 AS ( -- 這裡放查詢 SELECT 欄位1, 欄位2, ... FROM 表名 WHERE 條件 ) -- 這裡可以使用 CTE SELECT * FROM CTE名稱; ``` ### 去除重複的資料 ```sql= WITH CTE AS ( SELECT Username, HashPassword, ROW_NUMBER() OVER (PARTITION BY Username ORDER BY (SELECT NULL)) AS rn FROM Account ) DELETE FROM CTE WHERE rn > 1; ``` 這段 SQL 的作用: 1. **WITH CTE AS (...)** 定義了一個 CTE,名稱為 CTE。 2. SELECT Username, HashPassword, **ROW_NUMBER() OVER (PARTITION BY Username ORDER BY (SELECT NULL)) AS rn:** * **ROW_NUMBER()** 是 SQL Server 的視窗函數,用來對每個 **Username** 分組 **(PARTITION BY Username)**,並給它們編號。 * **ORDER BY (SELECT NULL)** 代表不指定排序,這樣 **ROW_NUMBER()** 會隨機給 **Username** 相同的資料編號。 3. **DELETE FROM CTE WHERE rn > 1;** **rn > 1** 代表刪除重複的資料,只保留第一筆。 這樣就能刪除重複的 Username,並保留其中第一筆資料。 💠補充:保留「最後一筆」 ```sql= WITH CTE AS ( SELECT Username, HashPassword, ROW_NUMBER() OVER (PARTITION BY Username ORDER BY LastUpdatedTime DESC) AS rn FROM Account ) DELETE FROM CTE WHERE rn > 1; ``` 這樣 ORDER BY LastUpdatedTime DESC 會讓 ROW_NUMBER() 把最新的時間編號為 1,其他重複的行則刪除。 ---- ### 為什麼要用 CTE? 1. 讓 SQL 更清晰: 你可以把 CTE 視為臨時表,讓 SQL 查詢更直觀。 2. 可以重複使用: 在同一個查詢中,CTE 可以被多次使用。 3. 性能優化: 某些情況下,CTE 可以優化查詢,減少不必要的運算。 ## 資料庫圖表 從鑰匙那個地方按住拖拉即可於另一邊資料進行連結 ![螢幕擷取畫面 2025-03-19 154926](https://hackmd.io/_uploads/B1UoDeO2yx.png=50%) ### 如果無法開啟 將自己設為 dbo 1. 執行這段 SQL(請替換 [你的資料庫名稱] 和 [你的登入名稱]): ```sql= USE [你的資料庫名稱]; ALTER AUTHORIZATION ON DATABASE::[你的資料庫名稱] TO [你的登入名稱]; ``` 2. 如果不確定自己的登入名稱,可以執行: ```sql= SELECT SUSER_NAME(); ``` 3. 執行後,重新嘗試建立圖表。 ### 如果無法開啟2 右鍵資料庫 屬性 > 檔案 > 擁有者 > 新增目前使用者 [查看圖片](https://drive.google.com/file/d/1LY7HaK_AMTLRFtTzQbImh-eP_nfcjM6_/view?usp=sharing) # 資料匯出匯入 兩個差別很大,看是要匯入 **資料庫** 還是 **資料表** ## 匯出匯入資料 [SQL Server資料匯入與匯出](https://www.youtube.com/watch?v=C2jEhvT0ha8&ab_channel=%E8%AC%9D%E9%82%A6%E6%98%8C) 這個老師整理得很好!! 就大概這個位置:匯入、匯出點下去就對,剩下中文應該也懂吧:DD ![image](https://hackmd.io/_uploads/By3K1ZJoyx.png=50%) ## 還原檔案 [SQL Server Management Studio:完整備份及還原資料庫教學](https://mnya.tw/cc/word/2227.html) 這個整理得很完整 ![螢幕擷取畫面 2025-03-19 125415](https://hackmd.io/_uploads/HJHbCaw3yg.png=50%) 出錯無法處理的原因 ---- 資料要放在資料夾的:C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Backup 再不行的話換伺服器位置: ![螢幕擷取畫面 2025-03-19 153052](https://hackmd.io/_uploads/BkVyNlu21l.png=50%) 出錯無法處理2 ----- 按 Win + R ➡️ 輸入: ``` services.msc ``` 或者 Win + Q 搜尋 ``` 服務 ``` 找 SQL Server (XXX),確定有啟動。 Server Name (XXX)就是: * ANNA(如果是 MSSQLSERVER) * ANNA\SQLEXPRESS(如果是 SQLEXPRESS) ## 備份資料 他到時候就會存到他寫的位置去,十分方便:)) [遊覽圖片說明](https://drive.google.com/file/d/1fNS5IMXSJNm5hbEwH4AOhZQFPRqFWAnd/view?usp=sharing)