【问题标题】:How can i get the first free ID from table如何从表中获取第一个免费 ID
【发布时间】:2014-05-08 17:53:23
【问题描述】:

我有一个带有主键字段 ID 的表。我不想使用身份,因为我需要给用户手动选择新对象的 ID 的可能性。所以我的想法是:

  1. 默认情况下,在编辑视图中,ID 字段将为 0。
  2. 如果用户不更改,我需要找到第一个空闲ID并使用它。
  3. 如果用户更改 ID,我首先需要检查是否有另一个具有该 ID 的对象,在这种情况下抛出错误。
  4. 如果没有,请使用用户选择的 ID。
  5. 创建新对象

问题是如何查询SQL Server表来获得第一个免费ID号?

示例 1:

ID
--
1
2
10

第一个空闲 ID 是 3

示例 2:

ID
--
1
2
3
4

第一个空闲 ID 是 5

有没有办法做到这一点? 我能想到的就是获取最小值和最大值,为可能的值创建一个循环,然后与表数据进行比较,但这涉及到对数据库的太多查询。 谢谢!

【问题讨论】:

  • 我有一个带有主键字段 ID 的表 - 嗯。如果您弄乱了该字段,则不要将其设为主键。仅对该值使用普通列。
  • @juergend 。 . .阅读 OP 想要做什么。它不是“搞乱那个领域”。 OP 只是想让用户选择一个值,如果可能的话,否则在一个范围内获得另一个合理的值。
  • 没错。就像手动实现自动数字字段一样,可以跳过一些值。
  • @ercpap - 有什么理由专门这样做吗?我认为当您可以使用 IDENTITY 或自动增量之类的东西时,该解决方案将很难实施
  • 身份字段跳过值是有原因的,它与数据完整性有关。在您的场景中重用已删除的值是一个非常危险的想法。这是您向用户推送的要求类型,告诉他们这是非常危险的,不是一个好的做法。如果您这样做,请确保您在数据库中设置了正确的 FK,否则您将 100% 保证在短时间内获得错误数据。顺便说一句,您要从不更改它们的用户那里输入多少个 0?

标签: sql sql-server


【解决方案1】:

你可以找到第一个空闲的id作为没有“next”值的第一个id:

select coalesce(min(t.id) + 1, 0)
from table t left outer join
     table t2
     on t.id = t2.id - 1
where t2.id is null;

编辑:

如果您想将“1”作为潜在的缺失值处理:

select (case when min(minid) > 1 then 1 else coalesce(min(t.id) + 1, 0) end)
from table t left outer join
     table t2
     on t.id = t2.id - 1 cross join
     (select min(id) as minid from table t) const
where t2.id is null;

【讨论】:

  • @ercpap 。 . .哎呀。不知何故,not 悄悄进入了这种状态。
  • 好主意!!!几乎适用于所有场景。如果出现以下情况,它将不起作用:1)空 ID 是表的第一个(如果 ID 是 2、3、4,它会给我 5 作为第一个空闲)。 2)如果表上没有数据,则返回null。
  • 我建议改成MIN(ISNULL(t.id, 0)) + 1 这样可以返回1
  • 一个建设性的批评先生,如果桌子是空的会发生什么:)
  • @M.Ali 是的,你是对的。它实际上是ISNULL(MIN(T.ID), 0) + 1
【解决方案2】:

测试表

CREATE TABLE ID_TABLE(ID INT)
INSERT INTO ID_TABLE VALUES
(1),(2),(10)

存储过程

ALTER PROCEDURE dbo.usp_GetNextValue
@nxt_ID_Wanted INT = 0,
@nxt_ID_Available INT  OUTPUT
AS
BEGIN
  SET NOCOUNT ON;

  -- If user hasnt passed any value get next avilable value
IF (@nxt_ID_Wanted = 0)   
 BEGIN 
          SELECT TOP 1 @nxt_ID_Available  =  ID + 1 
        FROM 
         (
          SELECT   ID , ROW_NUMBER() OVER (ORDER BY ID ASC) AS rn
         FROM ID_TABLE
         )Q
        WHERE ID = rn
        ORDER BY ID DESC

      IF (@nxt_ID_Available IS NULL)
        BEGIN
          SET @nxt_ID_Available = 1;
        END
 END
     -- If user has passed a value check if it exists and raise error
ELSE  IF EXISTS(SELECT 1 FROM ID_TABLE WHERE ID = @nxt_ID_Wanted)
 BEGIN

       RAISERROR('Selected ID value already exists',16,1)
       SET @nxt_ID_Wanted = 0;
       RETURN;
 END 
ELSE      -- else just let the user have the value he/she wanted
 BEGIN 
   SET @nxt_ID_Available = @nxt_ID_Wanted;
 END

END

执行程序

DECLARE @ID INT;
EXECUTE dbo.usp_GetNextValue @nxt_ID_Wanted = 6
                             ,@nxt_ID_Available = @ID OUTPUT

SELECT @ID

【讨论】:

    【解决方案3】:

    此函数获取 PostgreSQL (PL/pgSQL) 中表的第一个空闲 ID:

    CREATE OR REPLACE FUNCTION next_free_id_from(table_name TEXT) 
      RETURNS BIGINT AS
    $$
    DECLARE
      next_id BIGINT;
    BEGIN
      execute 'select rn from (select id, row_number() over (order by id) as rn from ' || quote_ident(table_name) || ') as T where id > rn limit 1' into next_id;
      if next_id is null then
        execute 'select COALESCE(max(id), 0) + 1 from ' || quote_ident(table_name) into next_id;
      end if;
      return next_id;
    END;
    $$ language 'plpgsql' STRICT;
    

    执行:

    select next_free_id_from('test');
    

    test 是一个表名。这个函数调用可以放在主键的DEFAULT中。

    【讨论】:

    • 我真的不明白你为什么用 PostgreSQL 解决方案回答一个七年前的请求,而该请求是关于 SQL Server 的。顺便说一句,您的总体思路很好,但缺少两种情况: 1. 表中没有行,您应该返回 1 作为第一个可用 ID。 2. 表格中有行但没有间隙,您应该返回 max(id) + 1 作为下一个可用 ID。
    • @ThorstenKettner 谢谢,我正在寻找解决方案,我想分享它。顺便说一句,我已经更新了我的答案以包含您建议的修复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-02
    相关资源
    最近更新 更多