65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL Server 表和列命名约定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (52投票s)

2015 年 12 月 17 日

CPOL

15分钟阅读

viewsIcon

239705

downloadIcon

793

本文描述了表和列的标准命名约定,并提供了利用这些约定来可视化依赖关系以及自动验证和生成外键约束的代码。

引言

几周前,我发表了一篇题为“C# 中闪电般快速的访问控制列表”的文章,其中我描述了一种存储和查询权限的方法,该方法独立于数据库架构。在用于撰写该文章的项目中,由于我无法控制的限制,我不能修改数据库架构。

许多读者随后提出了这个问题:

如果您可以更改数据库架构……它会是什么样子?

在这篇分为两部分的文章中,我将详细解答这个问题。

在本第一部分中

  • 我将首先描述我的数据库表和列的标准命名约定。
  • 然后,我将展示有助于证明这些约定的代码。有些约定乍一看可能显得不寻常,而代码示例有助于解释其背后的原理。

然后,在第二部分中

  • 我将逐步介绍为实际应用程序创建功能齐全的 SQL Server 数据库架构的步骤。该架构将包括用于支持访问控制列表和嵌套用户组的表,正如原始文章及其后续文章的读者所要求的那样。

为了说清楚:本文讨论或包含最基础的 SQL。因此,您在这里不会找到任何 C# 的讨论;没有 Entity Framework,没有数据访问层,没有单元测试,也没有用户界面。这里的重点仅限于数据库层。该解决方案技术栈中的其他层将在未来的文章中进行探讨。

命名约定

多年来,我接触过无数遵循无数命名约定(有时甚至没有任何约定)的数据库。

我还撰写了许多不同的命名标准和指南,有时会随着年份的推移而自相矛盾,这取决于哪些已被证明有效,哪些没有。

我们都知道编码标准很难做到完美。否则,我们都会使用相同的标准。

我得出的结论是,没有哪个约定是绝对正确或错误的。我也得出结论,一个给定的标准可以是“好的”,但仍然不一定适合所有解决方案。最终,您需要做对您的项目最有效的事情,而我将要描述的约定可能适合您,也可能不适合。如果不是这样,那就只采用对您有用的部分,忽略其余部分。

提前警告

我的一些约定违背了流行的智慧,另一些则会惹恼或令数据建模的纯粹主义者反感。我能说什么呢?它们源于我在其中付出了大量心血的实践经验。这本身并不能神奇地让它们成为好的约定,但它们是久经考验的,这值得一定程度的考虑,无论多少。

这是一组相当简约的列表——经典的十大列表——我将尽量保持我的评论简短……

#1. 使用标题大写

对数据库名称、表名称和列名称使用标题大写。我不喜欢大写字母充斥的架构,也不喜欢使用下划线分隔单词。

-- This seems loud and obnoxious:
create table EMPLOYEE_MAILING_LIST

-- I still dislike the underscores here (and we're not writing JavaScript, are we?)
create table employee_mailing_list

-- This reads like normal, friendly, solid code:
create table EmployeeMailingList

在表名和列名中也要避免使用空格、连字符和其他特殊字符。

#2. 在数据库名称前加上前缀以指示环境

您可能有一个本地开发环境、一个测试和 QA 环境、一个演示环境、一个培训环境以及一个实时/生产环境——所有这些都是针对同一个应用程序的。

为了避免混淆,请在每个数据库名称前加上它所支持的环境名称。

DemoAcmeLiveAcme 这样的数据库名称具有这种自描述性。自描述性在软件架构中始终是一个优点,在这种情况下,它可以帮助避免类似以下的愚蠢错误

use Acme;
go

-- Purge the mailing list table to test something weird and wonderful.
delete from EmployeeMailingList;

-- Oops... Did I just blow out a million records in a production database?

在这里,我不介意使用下划线来分隔环境名称和数据库名称,因此一个典型的应用程序(为方便举例,称之为“Snap”)的数据库实例名称看起来如下:

#3. 使用架构名称来组织数据库对象

当数据库包含数十(或数百)个表和视图时,架构名称对于将数据库对象组织成逻辑组非常有用。

我使用一个字母的缩写来保持架构名称的简短和简单。例如:

  • c = 所有与联系人管理相关的数据库对象
  • e = 与电子邮件和消息管理相关的数据库对象
  • m = 与元数据相关的对象
  • o = 与内容相关的对象
  • w = 与工作流相关的对象
  • x = 用于支持特定一次性使用、重用潜力极小或为零的自定义扩展

#4. 对表名称使用单数名词

所有表都使用单数名词命名。大多数数据库使用复数名词来命名表,因此我在这一点上打破传统。这在很大程度上是个人偏好的问题,但我确实认为单数名词更便于相关名称的字母排序。考虑以下字母排序的表名列表:

  • InvoiceItems
  • InvoicePayments
  • InvoiceReceipts
  • 发票
  • InvoiceTaxes

这样看起来还可以,但我认为这样看起来更好:

  • 发票
  • InvoiceItem
  • InvoicePayment
  • InvoiceReceipt
  • InvoiceTax

区别很微妙,对于大多数 DBA 和开发人员来说并不重要。对我来说很重要,因为我纠结于很多小细节。显而易见。:)

#5. 使用合理的��键

每个表都必须有一个主键。在可行的情况下使用自动递增的标识列,但要避免盲目地将其应用于数据库中的每个表——尤其是在多列主键有助于提高和强制执行数据完整性时。

例如,假设您想强制执行一项业务规则,即同一发票上的两个明细项不得引用同一产品。也就是说,当给定产品出现在发票上时,它应该只出现在一个明细项中。InvoiceItem.InvoiceIDInvoiceItem.ProductID 上的复合主键可以用来强制执行此业务规则,这样您就不必担心有问题的代码将来会破坏此规则并损害您数据的完整性。

#6. 对启用了标识的主键使用相同的模式

当主键是单列、自动递增、标识整数值时,它总是命名为“EntityID”。“Entity”前缀与表名匹配,“ID”后缀表示整数标识符。

此约定可能有些冗余,但它生成的��键名称是自描述且无歧义的。此外,它有助于确保架构符合 SQL 的 ISO 标准中的语义规则。(请参阅 ISO/IEC 11179 的第 5 部分。)

-- I can't tell if this ID is intended to be an auto-incrementing identity-enabled primary key:
select ID from Hero

-- However, this column name tells me exactly what I'm dealing with:
select HeroID from Hero

#7. 使用自描述的外键列名

旨在引用另一个表的外键的列遵循此命名约定:AttributeEntityID

  • Entity 与包含表名称不匹配时,它会将该列标识为外键引用。
  • Attribute 限定了列表示的属性的名称。在需要同一外键表引用同一主表的情况下,这非常有用。
  • Entity 是被引用的主表的名称。
  • ID 表示整数标识符。

以下是一些示例

  • 名为 AuthorContactID 的外键列存储 Author 的标识符;它引用一个名为 Contact 的主表。
  • 名为 StatusOptionID 的外键列存储 Status 的标识符;它引用一个名为 Option 的表。
  • 名为 AddressID 的外键列存储地址的标识符;它引用一个名为 Address 的表。在此示例中,忽略了列名的 Attribute 部分以显示它是可选的。当您有一个不包含多个引用同一主表的外键的表时,这是一个很好的快捷表示法,仍然符合约定。

你们中的一些人会比我的主键命名约定更讨厌这个外键命名约定。(事实上,你们中的一些人现在可能咬牙切齿;我可以稍等片刻,让你们数到十,然后去洗手间处理一下……)

我的疯狂是有方法的:我遵循这个约定是因为它生成的��键列名是完全自描述的。无论您在数据中找到什么,也无论实际定义了哪些约束,其意图都是清晰无歧义的。

这是一个在更详细的示例中此命名约定外观的示例:

当我继承别人创建的数据库时,通常没有设置任何引用完整性规则,而创建数据库的人早已离职。因此,我必须猜测开发人员假定引用完整性的位置,而这并不总是从阅读架构中显而易见的。

假设您刚刚被要求解决一个您从未见过的数据库中的问题。您的老板(或您的客户)很焦急,因为用户正在抱怨。所有人都盯着您,因为您被认为是专家,而他们最后一位专家(恰好是构建了数据库的人)的耐心早已耗尽。他们想要解决方案,而且马上就要。

为了让生活更有趣,数据库的架构中没有定义外键约束。您知道问题以某种方式与员工数据有关,并且在其中一个表中,您发现了一个名为 EmployeeID 的列。那么它是什么?它是员工编号吗?它是系统生成的标识符吗?它是自动编号标识吗?它是外键吗?如果是外键,如果不存在名为 Employee 的表怎么办?它打算引用什么?

除非有架构的技术文档(“哈!这太好了,对吧?”),否则您可能要花费数小时来筛选别人的查询、存储过程、数据访问代码、业务逻辑代码等等。

没有人应该为回答一个简单问题而付出如此艰辛的努力,我也不想让任何人在使用我的数据库时经历那种痛苦和折磨,所以尽管我的外键命名约定有点难看(我承认),但它消除了猜测,这对它有利。

它还有一些额外的优点,我将在稍后介绍。

#9. 智能使用 NVARCHAR

仅在您知道需要时才使用 NVARCHAR 数据类型。如果您需要一个列来存储多字符集、多语言的数据,那么这就是 Unicode(和 NVARCHAR)的目的。

我看到很多流行的建议,建议开发人员在 NVARCHAR 数据类型的列中存储所有文本值。毕竟,Unicode 支持地球上的所有字符集,因此如果您定义一个 NVARCHAR 列,您就可以永远安然入睡,知道它可以容纳任何可以想象到的输入:英语、法语、德语、西班牙语、乌克兰语、阿拉伯语、希伯来语、日语、中文、斯瓦希里语、克林贡语、辛达语……

当数据库相对较小时,流行建议是成立的:使用 VARCHAR 还是 NVARCHAR 并不重要。我对此也已经多年来随意对待了。然后,我遇到一个庞大的数据库,它像疯狗一样消耗磁盘空间。其数据文件、事务文件和备份文件都非常巨大。该系统已经运行了十多年,它支持的应用程序仅由英语使用者使用,数据库仅包含英语文本,而且在可预见的未来,其语言需求不会发生变化的预期,数据库包含数百万条记录。DBA 抓耳挠腮。所以我通过将所有 NVARCHAR 替换为 VARCHAR 来取消了 Unicode 选项,立即将系统的磁盘空间消耗减少了 50%,没有数据、功能或特性丢失。(DBA 得以保住他仅剩的一点头发。)

话虽如此,如果您需要 Unicode,那么我就会使用它。但如果您不需要:就不要使用它。

#10. 小心 MAX

除非您确定需要无限制地存储文本值列,否则避免使用 VARCHAR(MAX)NVARCHAR(MAX)

有多种原因,但我只描述最重要的一点:

虽然大多数 DBA 已经知道这一点,但许多开发人员似乎没有意识到 SQL Server 查询优化器会使用字段大小来确定其最有效的执行计划。当字段没有定义大小时,优化器的选项会受到限制,数据库性能可能会受到影响。有时影响会非常严重。

如果性能不是问题(这对于小型数据库可能是这样),那么可以忽略这一点。但是,当数据库很大时,VARCHAR(MAX)VARCHAR(300) 之间的性能差异可能是一个数量级(或更多)。

自动创建外键约束

由于这些命名约定编码了开发人员的意图,因此它们可以用于实现一些非常酷且有趣的功能。例如,它们可以用于方便地自动生成和验证外键约束。

当我向数据库添加新表和列时,并非总是会记住创建外键约束。即使我记住了,我也并非唯一对数据库进行架构更改的人,其他人也可能忘记。

在本节中,我将描述如何实现一个解决方案,该方案允许 DBA 或开发人员查询架构中应该存在的��键约束——并自动创建这些约束。

首先,您需要一些支持性数据库视图来提供必要的元数据。

表和列

以下视图围绕 INFORMATION_SCHEMA 基表提供了一个基本包装器。它包含了在查询表元数据时您可能需要的一切。

CREATE VIEW m.MetaTable
AS
SELECT
    TABLE_SCHEMA AS SchemaName
  , CASE TABLE_SCHEMA
      WHEN 'c' THEN 'Contact'
      WHEN 'e' THEN 'Email'
      WHEN 'm' THEN 'Metadata'
      WHEN 'o' THEN 'Content'
      WHEN 'w' THEN 'Workflow'
      WHEN 'x' THEN 'Extension'
      ELSE TABLE_SCHEMA
    END AS SchemaDescription
  , CASE TABLE_SCHEMA
      WHEN 'c' THEN 'Orange'
      WHEN 'e' THEN 'LimeGreen'
      WHEN 'm' THEN 'AntiqueWhite4'
      WHEN 'o' THEN 'DodgerBlue'
      WHEN 'w' THEN 'Crimson'
      WHEN 'x' THEN 'Purple4'
      ELSE 'Black'
    END AS SchemaColor
  , TABLE_NAME AS TableName
FROM
    INFORMATION_SCHEMA.TABLES
WHERE
    TABLE_TYPE = 'BASE TABLE'
    AND LOWER(TABLE_NAME) NOT IN (N'aspstatetempapplications',
                                  N'aspstatetempsessions', N'dtproperties',
                                  N'sysdiagrams');

请注意,我利用这个机会描述每个架构/子系统名称,并为每个架构提供唯一的颜色编码。接下来,您需要一个类似的包装器来支持对基表列的查询。

CREATE VIEW m.MetaColumn
AS
SELECT
    T.SchemaName
  , T.SchemaDescription
  , T.SchemaColor
  , T.TableName
  , COLUMN_NAME AS ColumnName
  , DATA_TYPE AS DataType
  , CHARACTER_MAXIMUM_LENGTH AS MaximumLength
  , CAST(CASE WHEN IS_NULLABLE = 'YES' THEN 0
              WHEN IS_NULLABLE = 'NO' THEN 1
              ELSE NULL
         END AS BIT) AS IsRequired
  , CAST(CASE WHEN COLUMNPROPERTY(OBJECT_ID(T.SchemaName + '.' + T.TableName),
                                  COLUMN_NAME, 'IsIdentity') = 1 THEN 1
              ELSE 0
         END AS BIT) AS IsIdentity
  , C.ORDINAL_POSITION AS OrdinalPosition
FROM
    INFORMATION_SCHEMA.COLUMNS AS C
INNER JOIN m.MetaTable AS T
    ON C.TABLE_NAME = T.TableName
       AND C.TABLE_SCHEMA = T.SchemaName;

外键

您还希望有一个元数据视图,该视图返回数据库中实际定义的��键约束集。这是 SQL Server 系统表的包装器。

CREATE VIEW m.MetaForeignKeyConstraint
AS
    SELECT  OBJECT_SCHEMA_NAME(fk.parent_object_id) AS ForeignSchemaName
          , ForeignTable.SchemaDescription AS ForeignSchemaDescription
          , OBJECT_NAME(fk.parent_object_id) AS ForeignTableName
          , cpa.name AS ForeignColumnName
          , OBJECT_SCHEMA_NAME(fk.referenced_object_id) AS PrimarySchemaName
          , PrimaryTable.SchemaDescription AS PrimarySchemaDescription
          , OBJECT_NAME(fk.referenced_object_id) AS PrimaryTableName
          , cref.name AS PrimaryColumnName
          , fk.name AS ConstraintName
    FROM    sys.foreign_keys fk
            INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
            INNER JOIN sys.columns cpa ON fkc.parent_object_id = cpa.object_id
                AND fkc.parent_column_id = cpa.column_id
            INNER JOIN sys.columns cref ON fkc.referenced_object_id = cref.object_id
                AND fkc.referenced_column_id = cref.column_id
            INNER JOIN m.MetaTable AS PrimaryTable
                ON OBJECT_SCHEMA_NAME(fk.referenced_object_id) = PrimaryTable.SchemaName
                AND OBJECT_NAME(fk.referenced_object_id) = PrimaryTable.TableName
            INNER JOIN m.MetaTable AS ForeignTable
                ON OBJECT_SCHEMA_NAME(fk.parent_object_id) = ForeignTable.SchemaName
                AND OBJECT_NAME(fk.parent_object_id) = ForeignTable.TableName;

与此配对的是,您需要一个视图来告诉您数据库中应该存在哪些外键约束,这基于表和列的命名约定。

CREATE VIEW m.MetaForeignKey
AS
WITH    CTE
          AS (SELECT
                c.SchemaName AS ForeignSchemaName
              , c.SchemaDescription AS ForeignSchemaDescription
              , c.SchemaColor AS ForeignSchemaColor
              , c.TableName AS ForeignTableName
              , c.ColumnName AS ForeignColumnName
              , c.IsRequired AS ForeignColumnRequired
              , t.SchemaName AS PrimarySchemaName
              , t.SchemaDescription AS PrimarySchemaDescription
              , t.SchemaColor AS PrimarySchemaColor
              , t.TableName AS PrimaryTableName
              , t.TableName + 'ID' AS PrimaryColumnName
              , CASE WHEN fk.PrimaryColumnName IS NOT NULL THEN CAST(1 AS BIT)
                     ELSE CAST(0 AS BIT)
                END AS IsEnforced
              , c.TableName + '_' + c.ColumnName AS UniqueName
              , ROW_NUMBER() OVER (ORDER BY c.TableName, c.ColumnName) AS RowNumber
              FROM
                m.MetaColumn AS c
              INNER JOIN m.MetaTable AS t
                ON c.ColumnName LIKE '%' + t.TableName + 'ID'
                   AND (c.TableName <> t.TableName
                        OR c.ColumnName = 'Parent' + t.TableName + 'ID'
                       )
              LEFT JOIN m.MetaForeignKeyConstraint AS fk
                ON fk.ForeignSchemaName = c.SchemaName
                   AND fk.ForeignTableName = c.TableName
                   AND fk.ForeignColumnName = c.ColumnName
                   AND fk.PrimaryTableName = t.TableName
              WHERE
                c.ColumnName LIKE '%ID'
             )
    SELECT
        *
    FROM
        CTE AS A
    WHERE
        NOT EXISTS ( SELECT
                        *
                     FROM
                        CTE AS B
                     WHERE
                        A.UniqueName = B.UniqueName
                        AND A.RowNumber <> B.RowNumber
                        AND B.PrimaryTableName LIKE '%' + A.PrimaryTableName );

IsEnforced 列告诉您物理约束是否存在于数据库中,而 UniqueName 列告诉您该约束的名称应该是什么——无论约束是否实际存在于数据库中。 后者至关重要。

删除外键约束

现在,您可以利用上述视图的结果,以编程方式删除数据库中的所有外键约束。这在批量加载数据或运行执行大量架构更改的脚本时非常方便。

CREATE PROCEDURE m.DropForeignKeyConstraints
AS
BEGIN

    DECLARE X CURSOR
    FOR
    SELECT
        ForeignSchemaName + '.[' + ForeignTableName + ']'
      , ConstraintName
    FROM
        m.MetaForeignKeyConstraint;

    DECLARE @TableName NVARCHAR(128);
    DECLARE @ConstraintName NVARCHAR(MAX);

    OPEN X;
    FETCH NEXT FROM X INTO @TableName, @ConstraintName;

    WHILE @@FETCH_STATUS = 0
        BEGIN

            DECLARE @SqlStatement NVARCHAR(MAX) = 'ALTER TABLE ' + @TableName
                + ' DROP CONSTRAINT ' + @ConstraintName;
            EXEC sp_executesql @SqlStatement;

            FETCH NEXT FROM X INTO @TableName, @ConstraintName;
        END;

    CLOSE X;
    DEALLOCATE X;

END;

创建外键约束

接下来是我们一直努力实现的神奇之处。我们想要的是一个存储过程,它可以根据数据库使用的表和列命名约定,自动创建数据库中应该存在的所有外键约束。

例如,如果我们有一个名为 Employment 的表,其中有一个名为 OccupationID 的列,那么我们希望强制执行两个假设:

  1. 存在一个名为 Occupation 的表,其主键名为 OccupationID,并且
  2. Employment 表的此列与 Occupation 表之间存在一个外键约束。

代码出奇地轻量级:

CREATE PROCEDURE m.CreateForeignKeyConstraints
AS
BEGIN

    DECLARE X CURSOR
    FOR
    SELECT DISTINCT
        ForeignSchemaName + '.[' + ForeignTableName + ']'
      , ForeignColumnName
      , 'FK_' + SUBSTRING(UniqueName, 1, LEN(UniqueName) - 2)
      , PrimarySchemaName + '.[' + PrimaryTableName + '] (' + PrimaryColumnName + ')'
    FROM
        m.MetaForeignKey;

    DECLARE @TableName NVARCHAR(128);
    DECLARE @ColumnName NVARCHAR(128);
    DECLARE @ConstraintName NVARCHAR(128);
    DECLARE @PK NVARCHAR(128);

    OPEN X;
    FETCH NEXT FROM X INTO @TableName, @ColumnName, @ConstraintName, @PK;

    WHILE @@FETCH_STATUS = 0
        BEGIN

            DECLARE @SqlStatement NVARCHAR(MAX) = 'ALTER TABLE ' + @TableName
                + ' ADD CONSTRAINT ' + @ConstraintName + ' FOREIGN KEY ('
                + @ColumnName + ') REFERENCES ' + @PK;
                
            EXEC sp_executesql @SqlStatement;

            FETCH NEXT FROM X INTO @TableName, @ColumnName, @ConstraintName, @PK;
        END;

    CLOSE X;
    DEALLOCATE X;

END;

现在,您只需两行 T-SQL 即可删除和重新创建所有外键约束,并确信所有引用完整性规则都已到位。

EXEC m.DropForeignKeyConstraints;
EXEC m.CreateForeignKeyConstraints;

因为您的意图已编码到您的架构编写中,所以这些意图可以由架构强制执行。正如法老在那部老经典电影中所说:“写下来,就这么做。”这就是您想要的数据库的简洁性和确定性。

数据库结构可视化

结合起来,命名约定和元数据视图还可以实现数据库结构的自动化可视化。

模式

例如,我们可以使用简单的查询结果作为 GraphViz 的输入,来可视化子系统之间的依赖关系。如果您正在寻找架构和表之间的循环依赖关系,这尤其有用。

SELECT  DISTINCT
        SchemaDescription + ' [color="' + SchemaColor + '"];' AS Vertex
FROM    m.MetaTable
WHERE   SchemaName NOT IN ('dbo', 'x')
ORDER BY SchemaDescription + ' [color="' + SchemaColor + '"];'

SELECT  ForeignSchemaDescription + ' -> ' + PrimarySchemaDescription
        + ' [color="' + fk.PrimarySchemaColor + '", label="'
        + CAST(COUNT(*) AS VARCHAR) + '"];' AS Edge
FROM    m.MetaForeignKey AS fk
WHERE   ForeignSchemaDescription <> PrimarySchemaDescription
        AND ForeignSchemaName NOT IN ('dbo', 'x')
GROUP BY ForeignSchemaDescription
      , PrimarySchemaDescription
      , PrimarySchemaColor
ORDER BY ForeignSchemaDescription
      , PrimarySchemaDescription;

DOT 语言语法如下所示:

digraph
{
    node [fontname="Arial Bold", fontsize=11, shape=box, fontcolor=AntiqueWhite4, style=bold];
    edge [fontname=arial, fontsize=8, fontcolor=gray];

    Contact [color=Orange];
    Email [color=LimeGreen];
    Metadata [color=AntiqueWhite4];
    Content [color=DodgerBlue];
    Workflow [color=Crimson];
    Extension [color=Purple4];

    Contact -> Metadata [color=AntiqueWhite4, label=2];
    Email -> Content [color=DodgerBlue, label=1];
    Content -> Metadata [color=AntiqueWhite4, label=1];
    Content -> Contact [color=Orange, label=1];
    Workflow -> Metadata [color=AntiqueWhite4, label=6];
    Workflow -> Contact [color=Orange, label=3];
    Extension -> Metadata [color=AntiqueWhite4, label=2];
    Extension -> Workflow [color=Crimson, label=1];
    Extension -> Contact [color=Orange, label=2];
}

……这将生成一个彩色编码的图形,如下所示:

注意每条边的基数标签。这显示了两个子系统之间依赖关系中的外键数量,这让您对其“权重”有所了解。

例如,从图表中可以看到,在工作流架构中的表和元数据架构中的表之间有 6 个外键引用。这表明工作流和元数据子系统中的实体比其他任何两个子系统之间的耦合更紧密。

该图还显示子系统之间没有依赖循环。

表格

很容易花费数小时在 SQL Server Management Studio、Visual Studio、Visio、Gliffy 以及任何其他绘图工具中调整图形。我喜欢这类事情,但通常没有时间。

或者,您可以在大约 15 秒内完成一个彩色编码的图表,并且可以确信它精确地模拟了您的数据库结构。

这是 SQL:

SELECT  TableName + ' [color=' + SchemaColor + '];' AS Vertex
FROM    m.MetaTable AS T
WHERE   EXISTS ( SELECT *
                 FROM   m.MetaForeignKey
                 WHERE  ForeignTableName = T.TableName
                        OR PrimaryTableName = T.TableName )
        AND SchemaName NOT IN ('dbo', 'x')
ORDER BY TableName;

SELECT  fk.ForeignTableName + ' -> ' + fk.PrimaryTableName + ' [color='
        + fk.PrimarySchemaColor + ', label="' + CAST(COUNT(*) AS VARCHAR)
        + '"];' AS Edge
FROM    m.MetaForeignKey AS fk
WHERE   ForeignTableName <> PrimaryTableName
        AND ForeignSchemaName NOT IN ('dbo', 'x')
GROUP BY ForeignTableName
      , PrimaryTableName
      , PrimarySchemaColor
ORDER BY ForeignTableName
      , PrimaryTableName;

……这是 DOT 语法:

digraph
{
    node [fontname="Arial Bold", fontsize=11, shape=box, fontcolor=AntiqueWhite4, style=bold];
    edge [fontname=arial, fontsize=8, fontcolor=gray];

    Address [color=Orange];
    Contact [color=Orange];
    Identity [color=Orange];
    Membership [color=Orange];
    Permission [color=Orange];
    Job [color=LimeGreen];
    Recipient [color=LimeGreen];
    RecipientField [color=LimeGreen];
    AppSetting [color=AntiqueWhite4];
    Display [color=AntiqueWhite4];
    DisplayText [color=AntiqueWhite4];
    Localization [color=AntiqueWhite4];
    Log [color=AntiqueWhite4];
    Option [color=AntiqueWhite4];
    Resource [color=AntiqueWhite4];
    ResourceEdge [color=AntiqueWhite4];
    Tenant [color=AntiqueWhite4];
    Content [color=DodgerBlue];
    Notification [color=DodgerBlue];
    Subscription [color=DodgerBlue];
    Activity [color=Crimson];
    ActivityItem [color=Crimson];
    Assignment [color=Crimson];
    AssignmentCalculation [color=Purple4];
    AssignmentSnapshot [color=Purple4];

    Contact -> Address [color=Orange, label=2];
    Contact -> Resource [color=AntiqueWhite4, label=1];
    Identity -> Contact [color=Orange, label=1];
    Membership -> Contact [color=Orange, label=2];
    Permission -> Contact [color=Orange, label=1];
    Permission -> Resource [color=AntiqueWhite4, label=1];
    Job -> Notification [color=DodgerBlue, label=1];
    Recipient -> Job [color=LimeGreen, label=1];
    RecipientField -> Recipient [color=LimeGreen, label=1];
    DisplayText -> Display [color=AntiqueWhite4, label=1];
    Option -> Option [color=AntiqueWhite4, label=1];
    Option -> Resource [color=AntiqueWhite4, label=1];
    ResourceEdge -> Resource [color=AntiqueWhite4, label=2];
    Tenant -> Resource [color=AntiqueWhite4, label=1];
    Content -> Content [color=DodgerBlue, label=1];
    Notification -> Content [color=DodgerBlue, label=1];
    Notification -> Resource [color=AntiqueWhite4, label=1];
    Subscription -> Contact [color=Orange, label=1];
    Subscription -> Notification [color=DodgerBlue, label=1];
    Activity -> Option [color=AntiqueWhite4, label=1];
    Activity -> Activity [color=Crimson, label=1];
    Activity -> Resource [color=AntiqueWhite4, label=1];
    ActivityItem -> Activity [color=Crimson, label=1];
    Assignment -> Activity [color=Crimson, label=1];
    Assignment -> Option [color=AntiqueWhite4, label=3];
    Assignment -> Contact [color=Orange, label=3];
    Assignment -> Resource [color=AntiqueWhite4, label=1];
    AssignmentCalculation -> Option [color=AntiqueWhite4, label=1];
    AssignmentCalculation -> Assignment [color=Crimson, label=1];
    AssignmentCalculation -> Contact [color=Orange, label=1];
    AssignmentSnapshot -> Option [color=AntiqueWhite4, label=1];
    AssignmentSnapshot -> Contact [color=Orange, label=1];
}

……这是图形:

下一步:构建数据库

现在我们已经涵盖了命名约定、外键和元数据,我们为即将构建的数据库奠定了基础。这是下一篇文章的重点,我将很快在此发布。

历史

  • 2015 年 12 月 17 日 - 初稿
  • 2015 年 12 月 18 日 - 修复了小的错别字并添加了源代码下载
  • 2015 年 12 月 19 日 - 修复了小的错别字并改进了图表
  • 2015 年 12 月 21 日 - 修复了小的错别字
  • 2015 年 12 月 29 日 - 根据 CodeProject 社区成员 Jörgen Andersson 的出色评论,修改了主键和外键的命名约定
  • 2015 年 12 月 30 日 - 根据编辑反馈修改了代码片段;删除了未引用的图像;替换了缩略图
© . All rights reserved.