Sql server 构建更高效的MS SQL函数以返回均匀分割的范围

Sql server 构建更高效的MS SQL函数以返回均匀分割的范围,sql-server,sql-function,sql-optimization,Sql Server,Sql Function,Sql Optimization,假设您有以下SQL表: -- create temp table CREATE TABLE [tempNums] ( id INT NOT NULL, somedate datetime NULL ) GO 有关tempSplitStringToInts的定义,请参见下面的一些数据: -- with date INSERT INTO [tempNums] SELECT id, GETUTCDATE() FROM [tempSplitStringToInts]

假设您有以下SQL表:

-- create temp table
CREATE TABLE [tempNums] 
(
    id INT NOT NULL,
    somedate datetime NULL
)
GO
有关tempSplitStringToInts的定义,请参见下面的一些数据:

-- with date
INSERT INTO [tempNums]
    SELECT id, GETUTCDATE()
    FROM [tempSplitStringToInts] ('1,2,3,5,10,100,101,102,103,233,1001,5002,5003,5005,5007,5010',',') 
GO

-- without date
INSERT INTO [tempNums]
    SELECT id, NULL
    FROM [tempSplitStringToInts] ('6,7,8,150,151,152,153,433,2001,2002,2003,2005,3007,10010',',') 
GO
您如何构建更好/更快的功能,使其具有多个范围,以及 将位标记为输入并返回范围值表

例如,类似的方法可以工作,但对于非常大的表来说速度较慢:

-- create range function
CREATE FUNCTION [tempFnGetIdRanges]
(
    @apps INT,
    @has_date BIT
)
RETURNS @ret TABLE
(
    RangeNum INT, 
    MinNum INT, 
    MaxNum INT
)
AS
BEGIN

    DECLARE @i INT = 0;
    DECLARE @count INT;
    DECLARE @min INT;
    DECLARE @max INT = 0;

    IF @has_date = 1
    BEGIN
        SELECT @count = COUNT(id) 
            FROM [tempNums] 
            WHERE somedate IS NOT NULL
    END
    ELSE
    BEGIN
        SELECT @count = COUNT(id) 
            FROM [tempNums] 
            WHERE somedate IS NULL

    END

    DECLARE @top INT = @count/@apps;

    WHILE @i<@apps
    BEGIN

        IF @i+1=@apps
        BEGIN
            -- on last get reminder
            SET @top = @top + @apps 
        END

        IF @has_date = 1
        BEGIN       
            SELECT @min = MIN(id), @max = MAX(id)
            FROM
            (
                SELECT TOP (@top) id 
                FROM [tempNums] 
                WHERE somedate IS NOT NULL
                    AND id > @max
                ORDER BY id
            ) XX
        END
        ELSE
        BEGIN
            SELECT @min = MIN(id), @max = MAX(id)
            FROM
            (
                SELECT TOP (@top) id 
                FROM [tempNums] 
                WHERE somedate IS NULL
                    AND id > @max
                ORDER BY id
            ) XX
        END


        INSERT INTO @ret VALUES(@i, @min, @max)

        SET @i = @i + 1;
        CONTINUE
    END

    RETURN
END
GO
第一个语句的结果:

RangeNum    MinNum  MaxNum
0           6       8
1           150     152
2           153     2001
3           2002    10010
RangeNum    MinNum  MaxNum
0           1       5
1           10      102
2           103     5002
3           5003    5010
第二次陈述的结果:

RangeNum    MinNum  MaxNum
0           6       8
1           150     152
2           153     2001
3           2002    10010
RangeNum    MinNum  MaxNum
0           1       5
1           10      102
2           103     5002
3           5003    5010
拆分函数供参考,但不是此问题的重点:

-- create split string function
CREATE  FUNCTION [tempSplitStringToInts] ( @SourceString VARCHAR(MAX) , @delimeter VARCHAR(10))
RETURNS @IntList TABLE
   (
     id INT
   )
AS
BEGIN
IF RIGHT(@SourceString, LEN(@delimeter))<> @delimeter
    BEGIN
        SELECT @SourceString = @SourceString + @delimeter
    END

DECLARE @LocalStr VARCHAR(MAX)
DECLARE @start INT
DECLARE @end INT
SELECT @start = 1
SELECT @end =  CHARINDEX ( @delimeter , @SourceString , @start ) 

WHILE @end > 0
    BEGIN 
        SELECT @LocalStr = SUBSTRING ( @SourceString , @start , @end - @start ) 
        IF LTRIM(RTRIM(@LocalStr)) <> '' 
            BEGIN
                INSERT @IntList (id) VALUES (CAST(@LocalStr AS INT))
            END
        SELECT @start = @end + LEN(@delimeter)
        SELECT @end = CHARINDEX ( @delimeter , @SourceString , @start ) 
    END
   RETURN
END
GO
正如我所说,这是可行的,但它是非常大的表缓慢。有没有 编写tempFnGetIdRanges函数的更好方法?土生土长的东西 SQL?如果相关的话,我正在使用MS SQL 2012


不太确定GetRanges函数要做什么,但肯定不需要循环。当您将HasDate作为1传入时,此函数返回与您相同的值

create function GetRanges
(
    @NumGroups int
) returns table as return

    with MyGroups as
    (
        select NTILE(@NumGroups) over(order by t.id) as GroupNum
            , t.id
        from tempnums t
    )

    select GroupNum
        , MIN(id) as MinNum
        , MAX(id) as MaxNum
    from MyGroups
    group by GroupNum
-编辑-

现在我看到您发布了两组示例数据,我理解了这个问题

下面是如何调整它以适应somedate中的NULL或notnull

alter function GetRanges
(
    @NumGroups int
    , @HasDate bit
) returns table as return

    with MyGroups as
    (
        select NTILE(@NumGroups) over(order by t.id) as GroupNum
            , t.id
        from tempnums t
        where
        (
            @HasDate = 1
            AND
            t.somedate is not null
        )
        OR
        (
            @HasDate = 0
            AND
            t.somedate is null
        )
    )

    select GroupNum
        , MIN(id) as MinNum
        , MAX(id) as MaxNum
    from MyGroups
    group by GroupNum

我看到的问题是,您只有14行为NULL,因此不确定为什么您希望的输出是这样的。由于NTIL将不均匀的行分组的方式不同,使用NTIL将在样本数据上产生略微不同的结果。

您在这里遇到了一些明确的性能挑战。首先,您正在创建表值函数,但它们是多语句表值函数,几乎总是比标量函数慢。为了提高表值函数的性能,它必须是一个select语句,仅此而已。我会先把你的分流器弄坏。这是最糟糕的。它在表值函数中有一个循环。这里有一些更好的选择。太好了,谢谢你的评论。我来看看更好的拆分器函数。那个主函数在做什么?当HasDate=1时,它只是将它们拆分为组,但当它为null时,输出对我来说没有任何意义。这里的逻辑是什么?实际上有数千个使用xml拆分字符串的示例。我对它们进行了基准测试,与其他方法相比,随着源字符串长度的增加,它们的速度越来越快。GetRanges函数试图获得相同数量的元素,或者尽可能接近每个范围中的相同数量,以及每个范围的最小数、最大数。所以,当date为NULL时,对于下面的ids 6,7,815015115215343320012002200530710010,我需要每个范围的MIN和MAX。当请求4个范围时,范围1为6,7,8,其中6为最小值,8为最大值,范围2为150151152,其中150为最小值,152为最大值,范围3为1534332001,其中433为最小值,2001为最大值,最后一个范围4为20022003200530710010,其中2002为最小值,10010为最大值。为什么选择6,7和8?151 - 153? 这些其他数字来自哪里?从我看来,这些只是随机数,根本没有逻辑依据。一定有一些规则您没有共享。假设您在DB中有一个表,ID列是identity,但某些行被删除。我的示例是任意的,但我试图创建随机值来说明这一点。我不能在我的数据库中给出表名和值的例子。我不希望有任何真实的数据。但是你的例子不是我可以用代码来反对的,因为缺少的数据或任何东西都没有任何意义。这显然是随机数。这是否可能是对另一个表进行左联接,并查找联接值为NULL的表?如果是这种情况,则可以简单地调整上述cte。但如果没有什么东西可以使用,我几乎无法猜到这一块拼图。没有连接表。我有一个包含ID和datetime列的表。对于某些ID,datetime为NULL,而对于某些ID,则不是。无论如何,我会想一想,试着调整我的问题,使之更有意义。