【问题标题】:Getting the first unused Id from a table?从表中获取第一个未使用的 ID?
【发布时间】:2015-11-10 20:30:07
【问题描述】:

在我们的数据库中,我们有一个缺少标识列的表。有一个 Id 列,但是在输入记录时手动填充。 ID 超过 90,000 的任何项目都会被保留,并在全球范围内填充到所有客户数据库中。

我正在构建一个工具来使用 Entity Framework 处理对该表的批量插入。我需要在不遍历每一行的情况下即时找出找到第一个可用 Id 的最有效方法是什么(低于 90,000)。在许多数据库中,很可能有人只是简单地选择了一个没有被采用的随机数并用它来插入行。

我最好的办法是什么?


编辑

在看到列出的一些解决方案后,我尝试在 Linq 中复制 SQL 逻辑。我怀疑它是否完美,但它看起来非常快速和高效。

var availableIds = Enumerable.Range(1, 89999)
                       .Except(db.Table.Where(n => n.Id <= 89999)
                           .Select(n => n.TagAssociationTypeID))
                       .ToList();

【问题讨论】:

  • 我不知道如何找到“第一个可用的”,但您可以很容易地找到“最后使用的”select max(ID) + 1 from YourTable where ID &lt; 90000,确保将所有这些都包含在 Serialzeable 级别的事务中,所以有些人不使用你下面的 ID。
  • 另外“使用实体框架批量插入此表” 实体框架不是为批量插入而设计的,尝试使用它会导致性能非常差。选择另一个 ORM 或使用 EntityFramework.BulkInsert 库来获取批量操作。
  • 你可以试试 GUID.GetHasCode,你得想办法阻止它超过 90,000,-stackoverflow.com/questions/2920696/…
  • 我建议使用 SQLServer 的 SEQUENCE 功能
  • @ScottChamberlain 我正在使用那个库。所以那里不用担心。而且我不只是想继续其他人停止编号的地方,因为我看到人们使用诸如 89,900 之类的值以及其他同样疯狂的东西。

标签: c# sql-server entity-framework linq entity-framework-6


【解决方案1】:

你有没有考虑过这样的事情:

SELECT
    min(RN) AS FirstAvailableID
FROM (
    SELECT
        row_number() OVER (ORDER BY Id) AS RN,
        Id
    FROM
        YourTable
    ) x
WHERE
    RN <> Id

【讨论】:

  • 这行不通,因为它只会返回第一个不存在的数字。该行之后的任何行都将出现,并且所有内容都将被移动。如果有的话,我需要一种生成未占用数字列表并将它们存储到列表中的方法,以便在将值分配给新对象时进行迭代。
  • @Jdsfighter,这不是你想要的。你要求第一个。这会为你做到这一点。
  • @Jdsfighter,抱歉,您的原话是“从表中获取第一个未使用的 ID?”然后在您的帖子中“我需要弄清楚找到第一个可用 ID 的最有效方法是什么......”。当你问一个完全误导性的问题时,没有办法期望得到所需的答案......
  • 我很抱歉不清楚。在上下文中,您的答案就足够了。谢谢。
【解决方案2】:

要回答您关于如何获得可用号码列表的隐含问题:简单,列出所有可能的号码,然后删除正在使用的号码。

--This builds a list of numbers from 1 to 89999
SELECT TOP (89999) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO #AvialableNumbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);

CREATE UNIQUE CLUSTERED INDEX n ON #AvialableNumbers(n)

--Start a seralizeable transaction so we can be sure no one uses a number
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
begin transaction

--Remove numbers that are in use.
delete #AvialableNumbers where n in (select Id from YourTable)

/*
Do your insert here using numbers from #AvialableNumbers
*/    

commit transaction

这是您通过实体框架执行此操作的方法

using(var context = new YourContext(connectionString))
using(var transaction = context.Database.BeginTransaction(IsolationLevel.Serializable))
{
    var query = @"
SELECT TOP (89999) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO #AvialableNumbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);

CREATE UNIQUE CLUSTERED INDEX n ON #AvialableNumbers(n)

--Remove numbers that are in use.
delete #AvialableNumbers where n in (select Id from YourTable)

--Select the numbers out to the result set.
select n from #AvialableNumbers order by n

drop table #AvialableNumbers
";
    List<int> availableIDs = context.Database.SqlQuery<int>(query).ToList();

    /*
       Use the list of IDs here
    */

    context.SaveChanges();
    transaction.Commit();
}

【讨论】:

  • 回到这一点,是否可以在实体框架中以类似的方式使用此代码?
  • 是的,您可以通过context.Database.SqlQuery&lt;int&gt;( 在 Entity Framework 和 Ad-Hoc sql 之间共享事务。有关在 EF 中执行原始 sql 的更多详细信息,请参阅 this page
  • 我更新了我的答案,展示了如何同时使用查询和 EF。
  • 我用上述查询的 linq 版本更新了我的原始问题。我相信它很好地复制了功能。如果我把它放在一个可序列化的事务中,它似乎工作得很好。
  • 是的,应该可以,只是确保处于可序列化级别的事务中以防止“幻像行”(在事务期间添加的新行)
猜你喜欢
  • 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
相关资源
最近更新 更多