【问题标题】:Retrieve a random row in a user defined function?在用户定义的函数中检索随机行?
【发布时间】:2016-08-03 23:28:20
【问题描述】:

我正在尝试定义这个函数:

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

所以我不能为这个场景创建一个视图,因为你不能将参数传递给视图。

所以这是我的困境:

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

我是否坚持使用过程并以“艰难的方式”执行它(使用输出变量,并且每次我想使用该方法时都必须声明该变量)?


再次编辑:

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

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() 本身不是一个函数吗?)

【问题讨论】:

  • 如果你想要类似的东西,你可以通过newid()而不是rand来订购
  • Msg 443,级别 16,状态 1,过程 getRandomName,第 6 行 在函数中无效使用副作用运算符“newid”。
  • FWIW: stackoverflow.com/questions/3531450/… 有额外的解释和“技巧”。
  • 确实如此。正如答案所解释的,您不能在标准函数中使用随机性,但可以有随机参数。例如create function myFunction ( @input1 varchar(255), @input2 varchar(255) ) returns varchar(255) as begin return @input1 + ' ' + @input2; end 并调用 select dbo.myFunction((select top 1 st1 from randomstrings order by newid()), (select top 1 st1 from randomstrings order by newid())); 之类的函数
  • 想象一下,您设法编写了满足您需求的函数。请告诉我们您打算如何使用它。很可能在没有该函数的情况下可以重写查询。 (功能代码可以在主查询中“内联”)。这将消除对技巧的需求并提高整体查询效率。

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


【解决方案1】:

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

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;

【讨论】:

  • 好的,您的方法有效,但现在我发现自己需要向调用发出参数。见上文。
  • 看看第二个查询是否能满足您的要求。
【解决方案2】:

首先,

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 每次调用都会生成一个不同的随机数,并且每行调用两次,所以应该会得到预期的结果。

【讨论】:

    猜你喜欢
    • 2014-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多