在用户定义的函数中使用RAND()

时间:2015-07-17 05:20:17

标签: sql-server sql-server-2012

我正在尝试创建一个用户定义的函数,在其中调用系统RAND()函数,当我尝试使用以下消息创建错误输出的函数时:

  

Msg 443,Level 16,State 1,Procedure getNumber,Line 10
  无效使用副作用运算符' rand'在一个函数内。

我的功能代码:

CREATE FUNCTION getNumber(@_id int)
RETURNS DECIMAL(18,4)
AS
BEGIN
   DECLARE @RtnValue DECIMAL(18,4);

   SELECT TOP 1 @RtnValue = EmployeeID 
   FROM dbo.Employees
   ORDER BY EmployeeID DESC

   SET @RtnValue = RAND() * @RtnValue * (1/100)

   RETURN @RtnValue;
END

我该如何解决这个问题?

6 个答案:

答案 0 :(得分:12)

问题在于您无法从用户定义的函数内部调用非确定性函数。

我通过创建一个视图来解决这个限制,在视图中调用该函数并在你的函数中使用该视图,就像这样......

查看定义

CREATE VIEW vw_getRANDValue
AS
SELECT RAND() AS Value

功能定义

ALTER FUNCTION getNumber(@_id int )
RETURNS DECIMAL(18,4)
AS
BEGIN
   DECLARE @RtnValue DECIMAL(18,4);
   SELECT TOP 1 @RtnValue = EmployeeID 
   FROM dbo.Employees
   ORDER BY EmployeeID DESC

   SET @RtnValue = (SELECT Value FROM vw_getRANDValue) * @RtnValue * (1.0000/100.0000) --<-- to make sure its not converted to int
    RETURN @RtnValue;
END

答案 1 :(得分:5)

小心兰德!

如果你检查这个,你会看到,对这个VIEW的多次调用都返回了相同的值。这与NEWID()不同。因此,如果你真的想要随机数字,最好采取NEWID()并做一些“技巧”来获取一个数字 - 让我们说 - 第一个字节......

CREATE VIEW vw_getRANDValue
AS
SELECT RAND() AS Value
GO
CREATE VIEW vw_getNEWID
AS
SELECT NEWID() AS Value
GO
CREATE FUNCTION dbo.Test() 
RETURNS TABLE AS
RETURN
WITH Numbers AS
(SELECT 1 AS x UNION SELECT 2 UNION SELECT 3) 
SELECT *
     ,(SELECT Value FROM vw_getRANDValue) AS myRandom
     ,(SELECT Value FROM vw_getNEWID) AS myNewid
FROM Numbers
GO
SELECT * FROM dbo.Test();
GO
DROP FUNCTION dbo.Test;
GO
DROP VIEW vw_getRANDValue;
GO
DROP VIEW  vw_getNEWID;
GO      

这是一个结果:

  1. 0,684530884058892 D1809581-BBD1-4D23-A7F9-BC697E869BB0
  2. 0,684530884058892 A4BAECDE-E993-46C1-B571-7440A713C371
  3. 0,684530884058892 D7A1CB65-D2BC-41B2-990D-C3BC52B056A2
  4. 随机BIGINT的视图可能如下所示:

    CREATE VIEW vw_getRandomBigInt
    AS
    SELECT CONVERT(BIGINT,CONVERT(VARBINARY(16),NEWID(),1)) * (-1) AS Value
    GO
    

    提示:我用很多行来检查它,看起来(只是通过视觉),这种方法并不是真正随机的(所有BIGINT都具有相同的宽度......)。这似乎工作正常:

    CREATE VIEW vw_getRandomInt
    AS
    SELECT sys.fn_replvarbintoint(sys.fn_cdc_hexstrtobin(LEFT(REPLACE(CONVERT(VARCHAR(100),NEWID()),'-',''),4))) AS Value
    GO
    

答案 2 :(得分:4)

只需从外部传递RAND()值作为参数:

CREATE FUNCTION getNumber(@_id int, @RAND FLOAT)
RETURNS DECIMAL(18,4)
AS
BEGIN
   DECLARE @RtnValue DECIMAL(18,4);

   SELECT TOP 1 @RtnValue = EmployeeID 
   FROM dbo.Employees
   ORDER BY EmployeeID DESC

   SET @RtnValue = @RAND * @RtnValue * (1/100)

   RETURN @RtnValue;
END

并以getNumber(10, RAND())

呼叫

没有任何副作用。

答案 3 :(得分:1)

你不能在函数中使用RAND函数,而是可以创建Rand函数的简单视图并在function中使用它。这只是一种解决方法

查看:

CREATE VIEW random_val_view
AS
SELECT RAND() as  random_value

功能:

CREATE FUNCTION getNumber(@_id int )
RETURNS DECIMAL(18,4)
AS
BEGIN
   DECLARE @RtnValue DECIMAL(18,4);
   SELECT TOP 1 @RtnValue = EmployeeID 
   FROM dbo.Employees
   ORDER BY EmployeeID DESC

   SET @RtnValue = (select random_value from random_val_view) * @RtnValue * (1/100.0)
    RETURN @RtnValue;
END

答案 4 :(得分:1)

还有一个想法:使用该函数只是为您的业务逻辑进行计算并交给非确定性部分。在你的情况下,你似乎在零和最高的employeeID之间选择一个随机数(那么缺少ID?)

如前所述RAND()很难使用。它将在多个调用中返回相同的值。因此,我使用NEWID,将其投放到VARBINARY(8)并将其转换为BIGINT

看看这个:

此功能将在给定边框之间采用GUID比例

CREATE FUNCTION dbo.GetRandomNumber(@lowerLimit BIGINT, @upperLimit BIGINT, @GuidValue UNIQUEIDENTIFIER)
RETURNS BIGINT
AS
BEGIN
    RETURN
    (
    SELECT ABS(CAST(CAST(@GuidValue AS VARBINARY(8)) AS BIGINT)) % (@upperLimit-@lowerLimit)+@lowerLimit
    )
END
GO

- 此表格将填充随机值

CREATE TABLE testTable(RndNumber BIGINT,Tile INT);

- CTE创建超过6 mio的虚拟行

WITH manyRows AS
(
    SELECT 1 AS nr FROM master..spt_values CROSS JOIN master..spt_values AS x
)
INSERT INTO testTable(RndNumber) 
SELECT dbo.GetRandomNumber(-300,700,NEWID()) --<-- Here I pass in the non-deterministic part
FROM manyRows;

- 现在表格平铺成10个相等的片段

WITH UpdateableCTE AS
(
    SELECT Tile
          ,NTILE(10) OVER(ORDER BY RndNumber) AS tileValue
    FROM testTable
)  
UPDATE UpdateableCTE SET Tile=tileValue;

- 检查随机结果

SELECT * FROM testTable
ORDER BY Tile,RndNumber;

- 这清楚地表明,每块瓷砖的覆盖范围几乎相同,这是相当好的随机传播的证据

SELECT Tile
      ,COUNT(*) CountOfNumbers
      ,MAX(RndNumber)-MIN(RndNumber) CoveredRange
FROM testTable
GROUP BY Tile
ORDER BY Tile;
GO

- 清理

DROP TABLE dbo.testTable;
DROP FUNCTION dbo.GetRandomNumber;

结果

T   Counts  min     max     CoveredRange
1   636553  -300    -201      99
2   636553  -201    -101     100
3   636553  -101       0     101
4   636553     0      99      99
5   636553    99     199     100
6   636553   199     299     100
7   636553   299     399     100
8   636553   399     499     100
9   636553   499     599     100
10  636552   599     699     100

你可以看到,每个瓷砖覆盖的元素数大致相同。里面的元素几乎覆盖了相同的范围。这表明数字在表格中均匀分布。

答案 5 :(得分:0)

您可以使用这些脚本从视图中获取随机数或字符(来自 SQL 2017):

-- FLOAT BETWEEN 0 AND 1
CREATE VIEW [dbo].[GetRandomFloat]
AS
    SELECT  CAST('0.'+REPLACE(TRANSLATE(NEWID(), 'ABCDEF-', '#######'),'#','') AS FLOAT) AS RND
GO

-- INT FROM 0 TO 9
CREATE VIEW [dbo].[GetRandomFrom0To9]
AS
    SELECT  CAST(COALESCE(LEFT(REPLACE(TRANSLATE(NEWID(), 'ABCDEF-', '#######'), '#', ''), 1), '0') AS TINYINT) AS RND
GO

-- CHAR FROM 'A' TO 'Z'
CREATE VIEW [dbo].[GetRandomFromAToZ]
AS
    SELECT CHAR(CAST(ROUND(CAST('0.'+REPLACE(TRANSLATE(NEWID(), 'ABCDEF-', '#######'),'#','') AS FLOAT) * 25 + 65, 0) AS TINYINT)) AS RND
GO

选择:

SELECT [RND] FROM [GetRandomFloat]
SELECT [RND] FROM [GetRandomFrom0To9]
SELECT [RND] FROM [GetRandomFromAToZ]