【问题标题】:SQL Server: how to constrain a table to contain a single row?SQL Server:如何约束表包含单行?
【发布时间】:2011-04-27 09:53:25
【问题描述】:

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

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

【问题讨论】:

  • 为什么不使用列有(Name, Value) 的表,主键是Name。然后您可以select Value from Table where Name = ? 确定不会返回任何行或返回一行。
  • 我不确定 sql 是这里的最佳解决方案。也许一个简单的 xml 文件更适合配置。我曾经认为配置!= 数据和 sql 是为数据制作的。
  • @ar - 当您希望读取一个整数时,我发现这会出现严重错误,并且您会在 value 列中得到一些格式错误的值。
  • @Damien_The_Unbeliever 为什么会发生这种情况?因为您为 Name 指定了一个不存在的值?
  • @Noumenon - 请注意我的评论是对ars 评论的回应。问题是,如果您只是存储名称/值对,则该值必须是字符串,并且您无法在数据库中执行 enforcing 验证。当您为每个设置使用具有单独 的单行表时(如 OP 所希望的那样),您可以轻松通过检查约束对每个配置设置强制验证。跨度>

标签: sql-server database-design singleton


【解决方案1】:

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

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

我在各种数据库中有许多这样的表,主要用于存储配置。如果配置项应该是一个 int,那么您将只能从 DB 中读取一个 int。

【讨论】:

  • +1。这是 Celko 在“SQL for Smarties”中用于辅助常量表的方法
  • 这很简单。谢谢。
  • +1:有趣的解决方案。我以前没见过这个,所以谢谢分享。始终如一,只要您知道如何,就很容易......
  • 这是一个需要思考的后续问题。这个表的主键是什么? :)
  • @BZ - c.d.t 是comp.databases.theory 的简写,这是一个我承认我最近没有读过的usenet 组(通过​​Google 组可见)。它比 SQL 更面向关系理论——但我碰巧知道 dportas/sqlvogel 也经常光顾同一组。 TTM 是对The Third Manifesto 的引用,这是一本(再次)谈论关系理论而不是 SQL 的好书。
【解决方案2】:

我通常使用 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 列,也不必在第一次加载表时记住哪个是锁定值。

【讨论】:

  • 默认约束也应该被命名,否则它会得到一个自动生成的混淆名称。 Lock char(1) not null CONSTRAINT DF_T1_Lock DEFAULT 'X'
  • 此外,您应该将默认值设为“X”以外的值。我让我的字符串更长,如果我尝试插入第二行,这就是我现在的错误消息:违反主键约束“PK_RestrictToOneRow”。无法在对象“dbo.1ROWTABLE”中插入重复键。重复键值为(此表锁定为一行)。
【解决方案3】:

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

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

然后,为了获得您当前的配置,您可以使用以下内容:

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

(取决于您的 DBMS 风格)。

这样,您仍然可以出于恢复目的维护历史记录(如果表变得太大,您可以执行清理程序,但这不太可能)并且您仍然可以使用最新配置。

【讨论】:

  • +1:我听到你在说什么,但我更喜欢将更改历史记录在单独的审计表中。
  • +1:分享实施审计跟踪的有趣想法。
  • 不错。只需SELECT TOP 1 ... ORDER BY creation_date_time DESC
【解决方案4】:

您可以实现 INSTEAD OF Trigger 以在数据库中强制执行此类业务逻辑。

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

现在,退一步看大局,我想知道是否有其他更合适的方式来存储这些信息,例如在配置文件或环境变量中?

【讨论】:

  • +1 - 我可以看到触发器是如何工作的,也许我会采用这种方法,但我喜欢 Damien 的简单约束。关于什么类型的配置数据属于配置文件以及什么属于数据库,有一个有趣的讨论。在这种情况下,我认为数据库是正确的地方。毫无疑问,我会为此后悔... :-)
【解决方案5】:

这是我为锁定类型表提出的解决方案,该表只能包含一行,包含 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';

为我工作。

【讨论】:

    【解决方案6】:

    我知道这已经很老了,但有时与其想大,不如想小 使用身份整数 Ex:

    Create Table TableWhatever(keycol int primary key not null identity(1,1) check(keycol =1),Col2 varchar(7))
    

    这样,每次您尝试插入另一行时,检查约束都会引发,阻止您插入任何行,因为身份 p 键不会接受除 1 之外的任何值

    【讨论】:

      【解决方案7】:

      我使用一个名为 IsActive 的主键位域。 所以最多可以有2行,获取有效行的sql是: 从 IsActive = 1 的设置中选择 * 如果表名为 Settings。

      【讨论】:

        【解决方案8】:

        老问题,但是使用小列类型的 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 添加另一行。
        【解决方案9】:

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

        【讨论】:

          【解决方案10】:
          IF NOT EXISTS ( select * from table )
          BEGIN
              ///Your insert statement
          END
          

          【讨论】:

            【解决方案11】:

            这里我们也可以创建一个不可见的值,它在第一次进入数据库后将是相同的。例如: 学生表: 编号:整数 名字:char 在输入框中,我们必须为 id 列指定相同的值,该值将在第一次输入后受到限制,而不是由于主键约束而写入 lock bla bla,因此永远只有一行。 希望这会有所帮助!

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-07-17
              • 1970-01-01
              • 2011-04-19
              • 2015-01-15
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多