【问题标题】:Unique constraint on another column if not null如果不为空,则对另一列的唯一约束
【发布时间】:2018-07-26 19:37:18
【问题描述】:

我想创建具有以下约束的表,是否可以使用 SQL Server Management Studio?

  • 身份证
  • ProductId [可空列]
  • CompanyId [可空列]
  • 用户名 [对于每个 productId 都是唯一的 - 如果 DeletedAt 不为 NULL]
  • 标识符 [Nullable 列] [每个 companyId 都是唯一的 - 如果 DeletedAt 不为 NULL]
  • DeletedAt [可空列]

更新: 我的表创建查询如下:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[User](
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [ProductId] [int] NULL,
    [CompanyId] [int] NULL,
    [Username] [nvarchar](max) NOT NULL,
    [Identifier] [nvarchar](max) NULL,
    [DeletedAt] [datetime2](7) NULL,
 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

【问题讨论】:

  • 我猜你会INSTEAD OF触发强制业务规则约束。
  • 所以你可以有 2 个 JohnSmith 用户名作为 productID 1,只要两者都有 DeletedAt 值?
  • @dfundako 是的。
  • 您可以创建这些约束,但在约束中使用如此复杂的逻辑,您会受到性能影响,我建议使用存储过程(用于更新、插入和删除)来处理业务逻辑和不要使用这种复杂的约束。
  • “DeletedAt”是日期时间吗?当我阅读需求时,我的第一个想法是“这是一个缓慢变化的维度,他使用 'DeletedAt' 作为逻辑删除标志。”如果我是对的,您可以将 DeletedAt 添加到唯一约束。或者,您可以制作一个存档表,每当您从主表中删除一条记录时,您都会在存档表中制作一份副本。

标签: sql sql-server


【解决方案1】:

您需要使用unique filtered index。给你找桌子:

/****** Object:  Table [dbo].[the_table]    Script Date: 7/26/2018 4:04:00 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[the_table](
    [id] [int] NOT NULL,
    [productid] [varchar](50) NULL,
    [companyid] [varchar](50) NULL,
    [username] [varchar](50) NULL,
    [identifier] [varchar](50) NULL,
    [deleteat] [varchar](50) NULL,
 CONSTRAINT [PK_the_table] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

以及用户名的索引:

SET ANSI_PADDING ON
GO

/****** Object:  Index [UIDX_USERNAME]    Script Date: 7/26/2018 4:03:31 PM ******/
CREATE NONCLUSTERED INDEX [UIDX_USERNAME] ON [dbo].[the_table]
(
    [username] ASC
)
WHERE ([DELETEAT] IS NOT NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

另一列的索引逻辑相同,但我不想通过发布更多来混淆这个答案!我不会乱把逻辑放在触发器或存储过程中,坏数据仍然可能潜入。如果你保持良好的约束,没有人可以把你的数据弄得乱七八糟:)

【讨论】:

  • 嗨。尝试创建索引时出现以下错误:表 'dbo.User' 中的列 'Username' 的类型不能用作索引中的键列。
  • 我刚刚用 create table 语句更新了我的问题。也许我做了一些意想不到的改变
  • @CodingAcademy 我认为您不能在 VARCHAR(MAX) 数据类型上建立索引。你可以做的就是把它改成VARCHAR(8000)或更合理的值,比如100。我怀疑你的用户名真的那么大:)
【解决方案2】:

您可以使用过滤索引。语法如下:

create unique index unq_thetable_username on the_table(username)
    where deleteAt is not null;

我不知道您是否可以通过点击您的方式获得此解决方案。我会按照上面的逻辑写。

【讨论】:

  • 我收到您的语法错误:表 'dbo.User' 中的列 'Username' 的类型不能用作索引中的键列。是因为它不能为空吗?
  • @CodingAcademy 。 . .这在 SQL Fiddle 上运行良好:sqlfiddle.com/#!18/1f357.
【解决方案3】:

在 ProductId 和 Username 上创建一个唯一索引,并使用 where 子句将该索引限制为 WHERE DeletedAt IS NOT NULLAnswer showing an example of a unique filtered index.

您可能还需要子句AND ProductId IS NOT NULL,虽然没有将其声明为要求,但由于该列可以为空,您应该考虑这一点。

Identifier 类似。

您可能还想考虑一个表的值,其中除Id 之外的所有列都为空的行是有效的...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多