# T-SQL

{% hint style="info" %}
对应的[官方页面地址](https://contributing.bitwarden.com/contributing/code-style/sql)
{% endhint %}

## 存储库 <a href="#repositories" id="repositories"></a>

我们使用存储库模式，并使用 Dapper 编写 MSSQL 存储库。每个存储库方法依次调用一个存储过程，该过程主要从 *Views*（视图）中获取数据。

## 部署脚本 <a href="#deployment-scripts" id="deployment-scripts"></a>

有一些特定的方式来构建部署脚本。这些标准的目标是确保脚本可以重新运行。我们从不打算在一个环境中多次运行脚本，但脚本应该支持它。

### 表 <a href="#tables" id="tables"></a>

#### 创建表 <a href="#creating-a-table" id="creating-a-table"></a>

建表时，首先要检查此表是否存在：

```plsql
IF OBJECT_ID('[dbo].[{table_name}]') IS NULL
BEGIN
    CREATE TABLE [dbo].[{table_name}] (
        [Id]                UNIQUEIDENTIFIER NOT NULL,
        ...
        CONSTRAINT [PK_{table_name}] PRIMARY KEY CLUSTERED ([Id] ASC)
    );
END
GO
```

#### 删除表 <a href="#deleting-a-table" id="deleting-a-table"></a>

删除表时，使用 `IF EXISTS` 以避免当表不存在时出现错误。

```sql
DROP IF EXISTS [dbo].[{table_name}]
GO
```

#### 添加列 <a href="#adding-a-column-to-a-table" id="adding-a-column-to-a-table"></a>

您必须先检查该列是否存在，然后才能将其添加到表中：

```sql
IF COL_LENGTH('[dbo].[{table_name}]', '{column_name}') IS NULL
BEGIN
    ALTER TABLE [dbo].[{table_name}]
        ADD [{column_name}] {DATATYPE} {NULL|NOT NULL};
END
GO
```

向现有表添加新的 `NOT NULL` 列时，请重新评估是否需要它。不要害怕在 C# 和应用层中使用 Nullable \<T> 原语，这总比对 DB 的每一行使用默认值导致占用不必要的空间要好，特别是对于新功能或新特性，需要占用很长时间，才能对大多数行级数据（如果有的话）有用。

如果您决定添加 `NOT NULL` 列，请**使用 DEFAULT 约束**，而不是创建列、更新行以及更改列。这对于像 `dbo.User` 和 `dbo.Cipher` 这样的大型表尤其重要。我们在 Azure 中的 SQL Server 版本使用元数据作为默认约束。这意味着我们可以更新默认的列值，而**无需**更新表中的每一行（这将使用大量的 DB I/O）。

这个很慢：

```sql
IF COL_LENGTH('[dbo].[Table]', 'Column') IS NULL
BEGIN
    ALTER TABLE
        [dbo].[Table]
    ADD
        [Column] INT NULL
END
GO

UPDATE
    [dbo].[Table]
SET
    [Column] = 0
WHERE
    [Column] IS NULL
GO

ALTER TABLE
    [dbo].[Column]
ALTER COLUMN
    [Column] INT NOT NULL
GO
```

这个更好：

```sql
IF COL_LENGTH('[dbo].[Table]', 'Column' IS NULL
BEGIN
    ALTER TABLE
        [dbo].[Column]
    ADD
        [Column] INT NOT NULL CONSTRAINT D_Table_Column DEFAULT 0
END
GO
```

#### 更改列数据类型 <a href="#changing-a-column-data-type" id="changing-a-column-data-type"></a>

您必须将 `ALTER TABLE` 语句包装在条件块中，以便脚本的后续运行不会再次修改数据类型。

```sql
IF EXISTS (
    SELECT *
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE COLUMN_NAME = '{column_name}' AND
        DATA_TYPE = '{datatype}' AND
        TABLE_NAME = '{table_name}')
BEGIN
    ALTER TABLE [dbo].[{table_name}]
    ALTER COLUMN [{column_name}] {NEW_TYPE} {NULL|NOT NULL}
END
GO
```

#### 调整元数据 <a href="#adjusting-metadata" id="adjusting-metadata"></a>

调整表时，您还应该检查该表是否被引用在任何视图中。如果视图中的基础表已被修改，则应运行 `sp_refreshview` 以重新生成视图元数据。

```sql
EXECUTE sp_refreshview N'[dbo].[{view_name}]'
GO
```

### 视图 <a href="#views" id="views"></a>

#### 创建或修改视图 <a href="#create-or-modify-a-view" id="create-or-modify-a-view"></a>

我们建议使用 `CREATE OR ALTER` 语法来添加或修改视图。

```sql
CREATE OR ALTER VIEW [dbo].[{view_name}]
AS
SELECT
    *
FROM
    [dbo].[{table_name}]
GO
```

#### 删除视图 <a href="#deleting-a-view" id="deleting-a-view"></a>

删除视图时，使用 `IF EXISTS` 以避免当表不存在时出现错误。

```sql
DROP IF EXISTS [dbo].[{view_name}]
GO
```

#### 调整元数据 <a href="#adjusting-metadata" id="adjusting-metadata"></a>

更改视图时，您可能还需要刷新引用该视图或函数的模块（存储了过程或函数），以便 SQL Server 更新其统计信息并编译对它的引用。

```sql
IF OBJECT_ID('[dbo].[{procedure_or_function}]') IS NOT NULL
BEGIN
    EXECUTE sp_refreshsqlmodule N'[dbo].[{procedure_or_function}]';
END
GO
```

### 函数和存储过程 <a href="#functions-and-stored-procedures" id="functions-and-stored-procedures"></a>

#### 创建或修改函数或存储过程 <a href="#create-or-modify-a-function-or-stored-procedure" id="create-or-modify-a-function-or-stored-procedure"></a>

我们建议使用 `CREATE OR ALTER` 语法来添加或修改函数或存储过程。

```sql
CREATE OR ALTER {PROCEDURE|FUNCTION} [dbo].[{sproc_or_func_name}]
...
GO
```

#### 删除函数或存储过程 <a href="#deleting-a-function-or-stored-procedure" id="deleting-a-function-or-stored-procedure"></a>

删除函数或存储过程时，请使用 I`F EXISTS` 以避免其不存在时出现错误。

```sql
DROP IF EXISTS [dbo].[{sproc_or_func_name}]
GO
```

### 创建或修改索引 <a href="#create-or-modify-an-index" id="create-or-modify-an-index"></a>

在创建索引时，尤其是在频繁使用的表上，我们的生产数据库很容易出现脱机、无法使用、CPU 达到 100% 以及许多其他不良行为。通常最好使用在线索引构建来执行此操作，以免锁定底层表。这可能会导致索引操作花费更长的时间，但您不会创建一个底层架构表锁来阻止所有读取和连接到该表，而只会在操作过程中锁定表的更新。

一个很好的例子是在 `dbo.Cipher` 或 `dbo.OrganizationUser` 上创建索引时，由于这些表是重读表，锁定会导致 Azure SQL 中异常高的 CPU、等待时间和 Worker 耗尽。

```sql
CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus]
   ON [dbo].[OrganizationUser]([UserId] ASC, [OrganizationId] ASC, [Status] ASC)
   INCLUDE ([AccessAll])
   WITH (ONLINE = ON); -- ** THIS ENSURES ONLINE **
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.ppgg.in/contributing/code-style/t-sql.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
