SQL Server:如何限制一个表包含一行?


80

我想在应用程序的配置表中存储一行。我要强制该表只能包含一行。

强制单行约束的最简单方法是什么?


为什么不使用带有(Name, Value)在Name上具有主键的列的表。然后,您select Value from Table where Name = ?可以确定不会返回任何行或一行。
2010年

2
我不确定sql是这里的最佳解决方案。也许简单的xml文件更适合配置。我曾经认为配置!= data和sql是为数据而做的。
雷米·伯格(Remi bourgarel)2010年

2
@ar-我已经看到,当您期望读取一个整数,并且在value列中得到一些格式错误的值时,这会出错。
Damien_The_Unbeliever

@Damien_The_Unbeliever为什么会这样?因为您为Name?指定了不存在的值
本体

1
@Noumenon-请注意,我的评论是对ars评论的回应。问题是,如果您仅存储名称/值对,则值必须很好地为字符串,并且您无法在数据库中强制执行验证。当您对每个设置使用一个带有单独的单行表时(如OP所希望的),则可以通过检查约束轻松地对每个配置设置实施验证。
Damien_The_Unbeliever

Answers:


97

您确保其中一列只能包含一个值,然后确保主键(或应用唯一性约束)。

CREATE TABLE T1(
    Lock char(1) not null,
    /* Other columns */,
    constraint PK_T1 PRIMARY KEY (Lock),
    constraint CK_T1_Locked CHECK (Lock='X')
)

我在各种数据库中都有许多这样的表,主要用于存储配置。很高兴知道,如果config项应该是一个int,则只会从数据库中读取一个int。


2
+1。这是Celko在“ SQL for Smarties”中用于辅助常数表的方法
Martin Smith,2010年

这差不多就变得简单了。谢谢。
马丁2010年

+1:有趣的解决方案。我以前没看过这个,谢谢您的分享。总是这样,容易当你知道如何....
约翰·桑塞姆

这是一个需要考虑的后续问题。该表的主键是什么?:)
nvogel 2010年

2
@BZ-cdt的缩写comp.databases.theory,是一个usenet小组(可通过Google小组查看),我承认我最近没有阅读太多内容。它比关系理论更着重于关系理论,而不是SQL-但我碰巧知道dportas / sqlvogel也经常光顾同一个小组。TTM引用了《第三宣言》,这是一本很好的书(再次)谈论关系理论而不是SQL。
Damien_The_Unbeliever

53

我通常使用Damien的方法,这种方法对我一直很有效,但是我还要添加一件事:

CREATE TABLE T1(
    Lock char(1) not null DEFAULT 'X',
    /* Other columns */,
    constraint PK_T1 PRIMARY KEY (Lock),
    constraint CK_T1_Locked CHECK (Lock='X')
)

添加“ DEFAULT'X'”,您将不必处理Lock列,也不必记住第一次加载表时哪个是锁值。


4
还应该命名默认约束,否则它将获得一个自动生成的令人困惑的名称。Lock char(1) not null CONSTRAINT DF_T1_Lock DEFAULT 'X'
David S.

1
此外,您应该将默认值设置为“ X”以外的值。我给了我一个更长的字符串,如果尝试插入第二行,这是我的错误消息:违反主键约束'PK_RestrictToOneRow'。无法在对象“ dbo.1ROWTABLE”中插入重复键。重复的键值为(此表被锁定到一行)。
杰夫·格里斯瓦尔德

14

您可能需要重新考虑此策略。在类似情况下,我经常发现留下旧配置行作为历史信息非常宝贵。

为此,您实际上有一个额外的列creation_date_time(插入或更新的日期/时间)和一个插入或插入/更新触发器,可以使用当前日期/时间正确地填充它。

然后,为了获得当前配置,请使用类似以下内容的内容:

select * from config_table order by creation_date_time desc fetch first row only

(取决于您的DBMS风格)。

这样,您仍然可以维护历史记录以进行恢复(如果表太大,可以建立清除过程,但这不太可能),并且仍然可以使用最新配置。


+1:我听到您在说什么,但我希望将更改的历史记录记录在单独的审核表中。
马丁

+1:分享实现审计跟踪的有趣想法。
约翰·桑索

真好 JustSELECT TOP 1 ... ORDER BY creation_date_time DESC
Baodad

5

您可以实现INSTEAD OF触发器,以在数据库中实施这种类型的业务逻辑。

触发器可以包含检查表中是否已存在记录的逻辑,如果存在,则回滚插入。

现在,退后一步来查看大图,我想知道是否存在另一种更合适的方式来存储此信息,例如存储在配置文件或环境变量中?


+1-我可以看到触发器将如何工作,也许我会退而求其次,但是我喜欢Damien的简单约束。关于什么类型的配置数据属于配置文件以及什么属于数据库,将进行有趣的讨论。在这种情况下,我认为数据库是正确的位置。毫无疑问,我会为此感到后悔... :-)
马丁2010年

2

我对名称为IsActive的主键使用一个位字段。因此最多可以有2行,并且要获得有效行的sql是:如果表名为Settings,则从Settings中选择*,其中IsActive = 1。


2

这是我想出的一种解决方案,用于一个锁型表,该表只能包含一行,并持有Y或N(例如,应用程序锁定状态)。

用一列创建表。我将检查约束放在一列上,以便只能在其中输入Y或N。(或1或0,或其他)

以“正常”状态在表中插入一行(例如N表示未锁定)

然后在仅具有SIGNAL(DB2)或RAISERROR(SQL Server)或RAISE_APPLICATION_ERROR(Oracle)的表上创建INSERT触发器。这使得应用程序代码可以更新表,但是任何INSERT都会失败。

DB2示例:

create table PRICE_LIST_LOCK
(
    LOCKED_YN       char(1)   not null  
        constraint PRICE_LIST_LOCK_YN_CK  check (LOCKED_YN in ('Y', 'N') )
);
--- do this insert when creating the table
insert into PRICE_LIST_LOCK
values ('N');

--- once there is one row in the table, create this trigger
CREATE TRIGGER ONLY_ONE_ROW_IN_PRICE_LIST_LOCK
   NO CASCADE 
   BEFORE INSERT ON PRICE_LIST_LOCK
   FOR EACH ROW
   SIGNAL SQLSTATE '81000'  -- arbitrary user-defined value
     SET MESSAGE_TEXT='Only one row is allowed in this table';

为我工作。


1

您可以在表的插入操作上编写触发器。每当有人尝试在表中插入新行时,请取消删除插入触发代码中最新行的逻辑。


1

旧问题,但是如何使用小列类型的IDENTITY(MAX,1)?

CREATE TABLE [dbo].[Config](
[ID] [tinyint] IDENTITY(255,1) NOT NULL,
[Config1] [nvarchar](max) NOT NULL,
[Config2] [nvarchar](max) NOT NULL

您可以使用SET IDENTITY_INSERT添加另一行。
拉兹万·索科尔


-1

在这里,我们还可以创建一个不可见的值,该值在数据库中第一次输入后将是相同的。示例:学生表:Id:int firstname:char在输入框中,我们必须为id列指定相同的值,这将限制在第一次输入之后,除了由于主键约束而写锁bla bla之外,因此永远只有一行。希望这可以帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.