How can I claim ranges of integers to assign to an entity using a SQL Server stored procedure?

Donuts

I have a SQL Server 2008 R2 database that manages lists of recipients. Each list of recipients needs to be assigned a block of serial numbers that are guaranteed to be unique for the last 90 days. A serial number is simply an integer between 1 and 999999.

I don't have to track which serial number gets associated with which recipient. There could be 500K+ recipients per list.

I have the following tables:

ListJob

    ListJobId int PK
    ListJobName varchar(64)

Recipient

    RecipientId int PK
    ListJobId int FK
    Name varchar(64)

ListJobSerialRange

    ListJobSerialRangeId int PK
    ListJobId int FK
    DateClaimed datetime
    SerialNumberStart int
    SerialNumberEnd int

The ListJobSerialRange table stores the claimed serial number range(s) that will be assigned to the recipients in the application layer. The sum of all the ranges that are assigned to a list job must equal the number of recipients, as each recipient will ultimately be assigned one serial number.

Recipients can be added and removed from a ListJob frequently. If we add recipients, we will need to claim additional serial numbers for them. If we remove recipients we want to free up the claimed serial numbers so we can re-use them so we don't waste serial numbers within the 90 day period.

Each list job will ultimately have one set of ranges. It won't have some that are claimed before the 90 days and some that are after the 90 days.

Here's a quick example:

  • Possible serial numbers are 1 to 999,999.
  • ListJob A has claimed 1 to 10,000
  • ListJob B has claimed 10,501 to 20,500

Now ListJob C has 1000 recipients. It needs to claim TWO ranges to fulfill all recipients:

10,001 to 10,500 
20,501 to 21,000

Here's what I have so far for my stored procedure:

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
Donuts

I finally got something I think will work.. First, I grab all of the claimed serial number ranges within the date threshold and sort them by the start number ascending and dump them in a temp table. Then the next part is the key concept.. I can peek at the previous row and the next row. That is a slight performance issue that i have to select from the temp table 3 times. but there really shouldn't be that many in there since i have already narrowed it down to the ones that are greater than the date threshold. then it's just a matter of looking at the cases where there can be gaps.

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

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How can I back up a stored procedure in SQL Server?

From Dev

How can I test stored procedure errors in SQL Server

From Dev

How to retrieve list of integers in SQL Server stored procedure?

From Dev

How can I avoid TimeOut exception using Entity Framework with a stored procedure?

From Dev

how can i check return value of stored procedure in entity framework

From Dev

how can i check return value of stored procedure in entity framework

From Dev

SQL Server: using IF with as in stored procedure

From Dev

How can I pass and process an array of varchars as parameters to/within a SQL Server stored procedure parameter?

From Dev

How can I use the OUT parameter from a SQL Server stored procedure in a vb.net code

From Dev

How can I merge 3 distinct Sqls by the same grouping but different columns (Sql Server Stored Procedure)

From Dev

How can I create a cursor from xml nodes in a stored procedure in SQL Server?

From Dev

How can I write two update queries in single stored procedure in SQL Server 2008

From Dev

How can I use the OUT parameter from a SQL Server stored procedure in a vb.net code

From Dev

How can I make it so a where clause with a value of 0 makes it not check the where in a SQL Server stored procedure?

From Dev

How can I pass and process an array of varchars as parameters to/within a SQL Server stored procedure parameter?

From Dev

How can I trace out modification of stored procedure with TSQL Command in SQL Server?

From Dev

How can I stop Hibernate's SQLExceptionConverter from converting error messages stored in a SQL Server procedure?

From Dev

How to assign values to variables in SQL stored procedure?

From Dev

How do I get an accurate timing of a SQL Server stored procedure?

From Dev

SQL Server Stored Procedure Using a Linked Server

From Dev

How to store result of stored procedure in a variable using SQL Server

From Dev

How to generate random passwords in SQL Server using stored procedure?

From Dev

How to get a SQL Server stored procedure returned rowset using pyodbc?

From Dev

How to access SQL Server using stored procedure in windows forms application?

From Dev

How can I implement MS SQL 2014 Stored Procedure

From Dev

How can i call a packages function with an stored PL/SQL procedure?

From Dev

How can I send a null value to a stored procedure using SqlParameter?

From Dev

How to call a SQL Server procedure using Entity Framework 6

From Dev

Using insert stored procedure with SQL Server

Related Related

  1. 1

    How can I back up a stored procedure in SQL Server?

  2. 2

    How can I test stored procedure errors in SQL Server

  3. 3

    How to retrieve list of integers in SQL Server stored procedure?

  4. 4

    How can I avoid TimeOut exception using Entity Framework with a stored procedure?

  5. 5

    how can i check return value of stored procedure in entity framework

  6. 6

    how can i check return value of stored procedure in entity framework

  7. 7

    SQL Server: using IF with as in stored procedure

  8. 8

    How can I pass and process an array of varchars as parameters to/within a SQL Server stored procedure parameter?

  9. 9

    How can I use the OUT parameter from a SQL Server stored procedure in a vb.net code

  10. 10

    How can I merge 3 distinct Sqls by the same grouping but different columns (Sql Server Stored Procedure)

  11. 11

    How can I create a cursor from xml nodes in a stored procedure in SQL Server?

  12. 12

    How can I write two update queries in single stored procedure in SQL Server 2008

  13. 13

    How can I use the OUT parameter from a SQL Server stored procedure in a vb.net code

  14. 14

    How can I make it so a where clause with a value of 0 makes it not check the where in a SQL Server stored procedure?

  15. 15

    How can I pass and process an array of varchars as parameters to/within a SQL Server stored procedure parameter?

  16. 16

    How can I trace out modification of stored procedure with TSQL Command in SQL Server?

  17. 17

    How can I stop Hibernate's SQLExceptionConverter from converting error messages stored in a SQL Server procedure?

  18. 18

    How to assign values to variables in SQL stored procedure?

  19. 19

    How do I get an accurate timing of a SQL Server stored procedure?

  20. 20

    SQL Server Stored Procedure Using a Linked Server

  21. 21

    How to store result of stored procedure in a variable using SQL Server

  22. 22

    How to generate random passwords in SQL Server using stored procedure?

  23. 23

    How to get a SQL Server stored procedure returned rowset using pyodbc?

  24. 24

    How to access SQL Server using stored procedure in windows forms application?

  25. 25

    How can I implement MS SQL 2014 Stored Procedure

  26. 26

    How can i call a packages function with an stored PL/SQL procedure?

  27. 27

    How can I send a null value to a stored procedure using SqlParameter?

  28. 28

    How to call a SQL Server procedure using Entity Framework 6

  29. 29

    Using insert stored procedure with SQL Server

HotTag

Archive