Sql server 构建更高效的MS SQL函数以返回均匀分割的范围
假设您有以下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]
-- 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,则不是。无论如何,我会想一想,试着调整我的问题,使之更有意义。