---
# System prepended metadata

title: SQL
tags: [SQL, SQL server]

---

# 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)