如何使用SQL Server存储过程声明要分配给实体的整数范围?
我有一个SQL Server 2008 R2数据库,用于管理收件人列表。每个收件人列表都需要分配一组序列号,这些序列号保证在过去90天内是唯一的。序列号只是一个介于1和999999之间的整数 我不必跟踪哪个序列号与哪个收件人关联。每个列表可能有超过500K个收件人 我有以下表格: 列表作业如何使用SQL Server存储过程声明要分配给实体的整数范围?,sql,sql-server,sql-server-2008,stored-procedures,Sql,Sql Server,Sql Server 2008,Stored Procedures,我有一个SQL Server 2008 R2数据库,用于管理收件人列表。每个收件人列表都需要分配一组序列号,这些序列号保证在过去90天内是唯一的。序列号只是一个介于1和999999之间的整数 我不必跟踪哪个序列号与哪个收件人关联。每个列表可能有超过500K个收件人 我有以下表格: 列表作业 ListJobId int PK ListJobName varchar(64) 收件人 RecipientId int PK ListJobId int FK Na
ListJobId int PK
ListJobName varchar(64)
收件人
RecipientId int PK
ListJobId int FK
Name varchar(64)
ListJobSerialRange
ListJobSerialRangeId int PK
ListJobId int FK
DateClaimed datetime
SerialNumberStart int
SerialNumberEnd int
ListJobSerialRange
表存储将分配给应用层中收件人的声明序列号范围。分配给列表作业的所有范围之和必须等于收件人数量,因为每个收件人最终将被分配一个序列号
可以经常从列表作业中添加和删除收件人。如果我们添加收件人,我们将需要为他们申请额外的序列号。如果我们删除收件人,我们希望释放声明的序列号,以便我们可以重复使用它们,这样我们就不会在90天内浪费序列号
每个列表作业最终将有一组范围。它不会有一些在90天之前和90天之后的索赔
下面是一个简单的例子:
- 可能的序列号为1至999999
- ListJob A已索赔1至10000英镑
- ListJob已索赔10501至20500英镑
现在ListJob C有1000个收件人。它需要声明两个范围才能满足所有收件人的要求:
10,001 to 10,500
20,501 to 21,000
到目前为止,我的存储过程有以下内容:
CREATE PROCEDURE [dbo].[ClaimSerialNumbers]
@ListJobId int,
@NumDaysUnique int,
@MaxSerialNumber int
as begin
set nocount on
declare @RecipientCount int
declare @QuantityClaimed int
declare @QuantityNeeded int
declare @DateThreshold smalldatetime
set @DateThreshold = dateadd(day, 0-@NumDaysUnique, getdate())
select @RecipientCount = count(*)
from dbo.Recipient
where ListJobId = @ListJobId
select @QuantityClaimed = sum(SerialNumberEnd - SerialNumberStart + 1)
from dbo.ListJobSerialRange
where ListJobId = @ListJobId
set @QuantityNeeded = @QuantityClaimed - @RecipientCount
if (@QuantityNeeded < 0) begin
delete dbo.ListJobSerialRange
where ListJobId = @ListJobId
set @QuantityNeeded = @RecipientCount
end
if (@QuantityNeeded = 0) begin
-- if we run the sproc twice and nothing has changed, then nothing to do
return 0
end
-- now the hard part:
-- i need to claim some serial numbers
-- ???
end
创建过程[dbo]。[ClaimSerialNumber]
@ListJobId int,
@努姆达伊苏尼克国际酒店,
@MaxSerialNumber int
作为开始
不计较
声明@RecipientCount int
声明@QuantityClaimed int
声明@QuantityRequired int
声明@DateThreshold smalldatetime
set@DateThreshold=dateadd(day,0-@NumDaysUnique,getdate())
选择@RecipientCount=count(*)
来自dbo.Recipient
其中ListJobId=@ListJobId
选择@QuantityClaimed=sum(SerialNumberRend-SerialNumberStart+1)
从dbo.ListJobSerialRange
其中ListJobId=@ListJobId
设置@QuantityRequired=@QuantityClaimed-@RecipientCount
如果(@QuantityRequired<0)开始
删除dbo.ListJobSerialRange
其中ListJobId=@ListJobId
设置@QuantityRequired=@RecipientCount
结束
如果(@QuantityRequired=0)开始
--如果我们运行了两次存储过程,但没有任何更改,则无需执行任何操作
返回0
结束
--现在最难的部分是:
--我需要索取一些序列号
-- ???
结束
我将填充表格,然后只进行更新。下面是一个关于分配范围的示例
--Populate the table; only needs done once if not a table variable
DECLARE @ListJobSerialRange TABLE
(
ListJobSerialRangeId INT IDENTITY(1,1),
SerialNumberStart int,
SerialNumberEnd int,
ListJobId int,
DateClaimed datetime
)
DECLARE @Counter BIGINT=1
WHILE @Counter<1000000
BEGIN
INSERT INTO @ListJobSerialRange
SELECT @Counter,@Counter+10000,NULL,NULL
SET @Counter=@Counter+10000
END
-- manual update to show that it will skip ranges to get contiguous ranges
UPDATE @ListJobSerialRange
SET ListJobId = 9999,DateClaimed=GETDATE()
WHERE ListJobSerialRangeId=1
-- manual update to show that it will skip ranges to get contiguous ranges
UPDATE @ListJobSerialRange
SET ListJobId = 9929,DateClaimed=GETDATE()
WHERE ListJobSerialRangeId=3
-- this is where my sproc would begin
DECLARE @ListJobSerialRangeIds TABLE(
ListJobSerialRangeId INT
)
DECLARE @MaxListJobSerialRange INT
SELECT @MaxListJobSerialRange=MAX(ListJobSerialRangeID) FROM @ListJobSerialRange
DECLARE @SerialNumbersNeeded INT = 20001
DECLARE @BlocksNeeded INT= (@SerialNumbersNeeded / 10000) + (@SerialNumbersNeeded % 10000)
DECLARE @ContBlocksAvail INT=0
SET @Counter=1
WHILE @Counter<=@MaxListJobSerialRange AND @ContBlocksAvail<>@BlocksNeeded
BEGIN
IF EXISTS (
SELECT *
FROM @ListJobSerialRange
WHERE ListJobSerialRangeID = @Counter
AND ListJobId IS NULL
)
BEGIN
INSERT INTO @ListJobSerialRangeIds SELECT @Counter
SET @ContBlocksAvail=@ContBlocksAvail+1
END
ELSE
BEGIN
SET @ContBlocksAvail=0
DELETE @ListJobSerialRangeIds
END
SET @Counter=@Counter+1
END
UPDATE l
SET ListJobId = 1123,DateClaimed=GETDATE()
FROM @ListJobSerialRange l
INNER JOIN @ListJobSerialRangeIds ids on ids.ListJobSerialRangeId=l.ListJobSerialRangeId
SELECT * FROM @ListJobSerialRange
——填充表格;如果不是表变量,则只需执行一次
声明@ListJobSerialRange表
(
ListJobSerialRangeId整数标识(1,1),
SerialNumberStart int,
SerialNumberRend int,
ListJobId int,
日期声明日期时间
)
声明@Counter BIGINT=1
虽然@Counter我终于找到了一些我认为有效的方法。。首先,我获取日期阈值内所有声明的序列号范围,并按开始编号升序对它们进行排序,然后将它们转储到临时表中。下一部分是关键概念。。我可以看一下前一行和下一行。这是一个轻微的性能问题,我必须从临时表中选择3次。但真的不应该有那么多,因为我已经把范围缩小到了那些大于日期阈值的。然后,只需看看可能存在差距的情况
CREATE PROCEDURE [dbo].[ClaimSerialNumbers]
@ListJobId int,
@NumDaysUnique int,
@MaxSerialNumber int
as
SET NOCOUNT ON;
BEGIN TRANSACTION
declare @RecipientCount int = 0
declare @QuantityClaimed int = 0
declare @QuantityNeeded int = 0
declare @Now smalldatetime = getdate()
declare @DateThreshold smalldatetime = dateadd(day, 0-@NumDaysUnique, @Now)
declare @RunningTotal int = 0
declare @UnusedRangeCount int = 0
--***************************************
-- Get number of recipients for list job
--***************************************
select @RecipientCount = coalesce(count(*), 0)
from dbo.Recipient with(nolock)
where ListJobId = @ListJobId
--***************************************
-- get how many serial numbers are already claimed
--***************************************
select @QuantityClaimed = coalesce(sum(SR.SerialNumberEnd - SR.SerialNumberStart + 1), 0)
from dbo.ListJobSerialRange SR
where ListJobId = @ListJobId
--***************************************
-- Determine how many we still need
--***************************************
set @QuantityNeeded = @QuantityClaimed - @RecipientCount
--***************************************
-- if we have more claimed that number of recipients
-- we have deleted recipients and need to free up
-- some serial numbers
--***************************************
if (@QuantityNeeded < 0) begin
delete dbo.ListJobSerialRange
where ListJobId = @ListJobId
set @QuantityNeeded = @RecipientCount
end
--***************************************
-- if we need to claim some serial numbers
--***************************************
if (@QuantityNeeded > 0) begin
if object_id('tempdb..#SortedRanges') is not null drop table #SortedRanges
if object_id('tempdb..#UnusedRanges') is not null drop table #UnusedRanges
create table #SortedRanges(RowNumber int, SerialNumberStart int, SerialNumberEnd int)
create table #UnusedRanges(SerialNumberStart int, SerialNumberEnd int, RunningTotal int)
--***************************************
-- put all the existing ranges within the last N days into a temp table
--***************************************
insert #SortedRanges
(RowNumber, SerialNumberStart, SerialNumberEnd)
select RowNumber = row_number() over (order by SerialNumberStart)
, SerialNumberStart
, SerialNumberEnd
from dbo.ListJobSerialRange
where DateClaimed >= @DateThreshold
order by SerialNumberStart
--***************************************
-- if no ranges exist in the last N days, then the whole set is available
--***************************************
if (@@rowcount = 0)
begin
insert dbo.ListJobSerialRange
(ListJobId, DateClaimed, SerialNumberStart, SerialNumberEnd)
values (@ListJobId, @Now, 1, @QuantityNeeded)
end
--***************************************
-- if we have serial numbers that were used in the last n days
-- then look for gaps in the rangesand add those gaps to the unused range temp table
--***************************************
else
begin
;with UnusedRangesFirstPass as (
select distinct
case
-- start of range (gap at beginning)
when P.RowNumber is null and C.SerialNumberStart > 1 then 1
-- start of range (gap at end)
when N.RowNumber is null and C.SerialNumberEnd < @MaxSerialNumber then C.SerialNumberEnd + 1
-- start of range (middle row gaps)
else
case
when P.SerialNumberEnd is not null and P.SerialNumberEnd < C.SerialNumberStart - 1 then P.SerialNumberEnd + 1
when N.SerialNumberStart is not null and N.SerialNumberStart > C.SerialNumberEnd + 1 then C.SerialNumberEnd + 1
end
end as UnusedStart,
case
-- end of range (gap at beginning)
when P.RowNumber is null and C.SerialNumberStart > 1 then (C.SerialNumberStart - 1)
-- end of range (gap at end)
when N.RowNumber is null and C.SerialNumberEnd < @MaxSerialNumber then @MaxSerialNumber
-- end of range (middle row gaps)
else
case
when P.SerialNumberEnd is not null and P.SerialNumberEnd < C.SerialNumberStart - 1 then C.SerialNumberStart - 1
when N.SerialNumberStart is not null and N.SerialNumberStart > C.SerialNumberEnd + 1 then N.SerialNumberStart - 1
end
end as UnusedEnd
from #SortedRanges C -- current row
left join #SortedRanges P ON P.RowNumber = C.RowNumber - 1 -- peek at previous row
left join #SortedRanges N ON N.RowNumber = C.RowNumber + 1 -- peek at next row
)
insert #UnusedRanges
(SerialNumberStart, SerialNumberEnd, RunningTotal)
select UnusedStart, UnusedEnd, 0
from UnusedRangesFirstPass
where UnusedStart is not null
and UnusedEnd is not null
--***************************************
-- update all unused ranges and calculate a running total
-- for each row
--***************************************
update #UnusedRanges
set @RunningTotal = RunningTotal = @RunningTotal + (SerialNumberEnd - SerialNumberStart + 1)
--***************************************
-- claim the unused ranges.
-- only claim the exact number we need
--***************************************
insert dbo.ListJobSerialRange
(ListJobId, DateClaimed, SerialNumberStart, SerialNumberEnd)
select @ListJobId, @Now, UR1.SerialNumberStart,
case
when UR1.RunningTotal > @QuantityNeeded then UR1.SerialNumberEnd - (UR1.RunningTotal - @QuantityNeeded)
else UR1.SerialNumberEnd
end
from #UnusedRanges UR1
left join #UnusedRanges UR2 on UR1.RunningTotal > UR2.RunningTotal
group by UR1.SerialNumberStart, UR1.SerialNumberEnd, UR1.RunningTotal
having coalesce(sum(UR2.RunningTotal),0) < @QuantityNeeded
end
end
--***************************************
-- return results and commit
--***************************************
select ListJobSerialRangeId, ListJobId, DateClaimed, SerialNumberStart, SerialNumberEnd
from dbo.ListJobSerialRange
where ListJobId = @ListJobId
COMMIT TRANSACTION
创建过程[dbo]。[ClaimSerialNumber]
@ListJobId int,
@努姆达伊苏尼克国际酒店,
@MaxSerialNumber int
作为
不计数;
开始交易
声明@RecipientCount int=0
声明@QuantityClaimed int=0
声明@QuantityRequired int=0
声明@Now smalldatetime=getdate()
声明@DateThreshold smalldatetime=dateadd(天,0-@NumDaysUnique,@Now)
声明@RunningTotal int=0
声明@UnusedRangeCount int=0
--***************************************
--获取列表作业的收件人数
--***************************************
选择@RecipientCount=合并(计数(*),0)
来自dbo.Recipient,带有(nolock)
其中ListJobId=@ListJobId
--***************************************
--获取已声明的序列号数量
--***************************************
选择@QuantityClaimed=coalesce(总和(SR.SerialNumberRend-SR.SerialNumberStart+1),0)
来自dbo.ListJobSerialRange SR
其中ListJobId=@ListJobId
--***************************************
--确定我们还需要多少
--***************************************
设置@QuantityRequired=@QuantityClaimed-@RecipientCount
--***************************************
--如果我们有更多的人申请领取
--我们已删除收件人,需要释放
--一些序列号
--***************************************
如果(@QuantityRequired<0)开始
删除dbo.ListJobSerialRange
其中ListJobId=@ListJobId
设置@QuantityRequired=@RecipientCount
结束
--***************************************
--如果我们需要索取一些序列号
--***************************************
如果(@QuantityRequired>0)开始
如果对象id('tempdb..#SortedRanges')不为空,则放置表#SortedRanges
如果对象id('tempdb..#UnusedRanges')不为空,则删除表#UnusedRanges
创建表#SortedRanges(rownumberint、SerialNumberStart int、serialnumberrend int)
创建表#未使用的范围(SerialNumberStart int、SerialNumberRend int、RunningTotal int)
--***************************************
--将过去N天内的所有现有范围放入临时表中
--***************************************
插入#分拣机