在用户定义的函数中检索随机行?

时间:2016-08-03 23:28:21

标签: sql-server tsql sql-server-2014

我试图定义此功能:

CREATE FUNCTION getRandomName  ()
    RETURNS VARCHAR(48)
AS BEGIN
    -- concatenate two random strings from two columns in a table and return as a new string
    DECLARE @finalStr VARCHAR(48);
    SET @finalStr = (SELECT TOP 1 st1 FROM randomStrings ORDER BY RAND()) + 
        ' ' + 
        (SELECT TOP 1 st2 FROM randomStrings ORDER BY RAND());
    RETURN @finalStr;
END

我不能这样做是因为:

Msg 443, Level 16, State 1, Procedure getRandomName, Line 6
Invalid use of a side-effecting operator 'rand' within a function.

我在网上发现的与此问题相关的帖子建议在调用函数时传递随机值作为参数,或者使用视图并在函数中查询该视图以将单个随机数转换为变量。我无法使用这些方法,因为我试图在ORDER BY子句中使用随机化。

有没有办法实现这个目标?

(SQL Server 2014)

编辑:

所以你可以使用视图来获得如下所述的结果,但现在我发现自己需要将一个参数传递给函数:

CREATE FUNCTION getRandomName  (
    @maxPieceSize int
)
    RETURNS VARCHAR(48)
AS BEGIN
    -- concatenate two random strings from two columns in a table and return as a new string
    DECLARE @finalStr VARCHAR(48);
    SET @finalStr = (SELECT TOP 1 st1 FROM randomStrings WHERE LEN(st1) <= @maxPieceSize ORDER BY RAND()) + 
        ' ' + 
        (SELECT TOP 1 st2 FROM randomStrings WHERE LEN(st1) <= @maxPieceSize ORDER BY RAND());
    RETURN @finalStr;
END

因此,我无法为此方案创建视图,因为您无法将参数传递给视图。

所以这就是我的困境:

  • 功能:我不能使用它,因为我不能在函数中使用任何非确定性函数。
  • 查看:我无法使用此功能,因为我需要将参数传递给&#34;功能&#34;。
  • 过程:我能看到这样做的唯一方法是使用输出变量,这意味着声明变量等。我不能简单地执行EXECUTE getRandomName(6)SELECT getRandomName(6)之类的操作

我是不是坚持使用程序并且这样做了#34;艰难的方式&#34; (使用输出变量,每次我想使用该方法时都必须声明该变量)?

再次编辑:

我尝试将实际方法编写为存储过程,然后从声明变量的函数调用该存储过程,分配它然后返回它。这很有道理。除了....

Msg 557, Level 16, State 2, Line 1
Only functions and some extended stored procedures can be executed from within a function.

我猜测SQL Server 确实并不希望我拥有一个可以返回随机值的函数。 (有趣,因为它本身并不是RAND()一个功能吗?)

2 个答案:

答案 0 :(得分:2)

为什么你坚持使用功能?将视图用作函数:

CREATE view getRandomName
AS 
    SELECT (SELECT TOP 1 st1 FROM randomStrings ORDER BY Newid()) + 
        ' ' + 
        (SELECT TOP 1 st1 FROM randomStrings ORDER BY Newid())
as RandomName
GO
SELECT (SELECT RandomName FROM getRandomName) + ' - This is random name'
GO

在存储过程中还有一种古老而疯狂的方法来获取随机行:

CREATE PROCEDURE usp_Random_Message
@i INT
AS
SELECT TOP 1 * FROM (
    SELECT TOP (@i) * FROM sys.Messages
    ORDER BY message_id
) AS a ORDER BY message_id DESC
GO
DECLARE @i INT = CAST(RAND() * 100 as INT);
EXEC usp_Random_Message @i;

答案 1 :(得分:0)

首先,

SELECT TOP 1 st1 FROM randomStrings ORDER BY RAND()

不会返回您期望的内容,因为RAND是运行时常量。这意味着服务器生成一次随机数,并在查询期间使用它。

您希望以随机顺序排列所有行,然后选择顶行。以下查询将执行此操作:

SELECT TOP 1 st1 FROM randomStrings ORDER BY NEWID()

SELECT TOP 1 st1 FROM randomStrings ORDER BY CRYPT_GEN_RANDOM(4)

如果您查看执行计划,您会看到randomStrings表已完整扫描,然后排序并选择一个顶行。

我猜你想要使用你的功能:

SELECT
    SomeTable.SomeColumn
    ,dbo.GetRandomName() AS RandomName
FROM SomeTable

对于SomeTable中的每一行,您希望获得一些随机字符串。

即使您通过一些技巧使您的原始方法工作,您也可以完整地扫描randomStrings表并对SomeTable的每一行进行排序(两次)。这可能效率不高。

提高效率并避免欺骗的一种方法是确保randomStrings表的intID的值从1到此表中的最大行数。也把它作为主键。

然后你的函数将接受两个参数 - 在1..N范围内的两个随机数,该函数将使用给定的ID构建随机字符串。

该功能可能如下所示:

CREATE FUNCTION dbo.GetRandomName
(
    @ParamID1 int
    ,@ParamID2 int
)
    RETURNS VARCHAR(48)
AS
BEGIN
    DECLARE @FinalStr VARCHAR(48);
    SET @FinalStr = 
        (SELECT st1 FROM randomStrings WHERE ID = @ParamID1)
        + ' ' +
        (SELECT st1 FROM randomStrings WHERE ID = @ParamID2)
    ;
    RETURN @FinalStr;
END

如果randomStrings表有100行,ID为1到100,那么此函数的用法可能如下所示:

SELECT
    SomeTable.SomeColumn
    ,dbo.GetRandomName(
        (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 100 + 1
        ,(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 100 + 1
    ) AS RandomName
FROM SomeTable

CRYPT_GEN_RANDOM(4)生成4个随机字节,它们被强制转换为int并转换为0到1之间的浮点数,乘以randomStrings表中的行数( 100)。它只是生成1 ... N

范围内的随机数的方法之一

CRYPT_GEN_RANDOM每次调用时都会生成一个不同的随机数,并且每行调用两次,因此您应该得到预期的结果。