我正在尝试创建一个用户定义的函数,在其中调用系统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
我该如何解决这个问题?
答案 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
这是一个结果:
随机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]