Use RAND() in User Defined Function

One more idea: Use the function just to do the calculation for your business logic and hand in the non-deterministic part. In your case you seem to pick a random number between zero and the highest employeeID (what about missing IDs?)

As told before RAND() is difficult to use. It will come back with the same value in multiple calls. Therefore I use NEWID, cast it to VARBINARY(8) and cast this to BIGINT.

Have a look at this:

This function will take a GUID and scale it between the given borders:

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

--This table will be filled with random values

CREATE TABLE testTable(RndNumber BIGINT,Tile INT);

--The CTE creates more than 6 mio dummy rows

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;

--Now the table is tiled in 10 equal fragments

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

--check the random result

SELECT * FROM testTable
ORDER BY Tile,RndNumber;

--This shows clearly, that the covered range per tile is almost the same which is a proof for a fairly good random spread

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

--clean up

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

The result

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

You can see, that each tile covers roughly the same count of elements. The elements inside are covering almost the same range. This shows, that the numbers are evenly spread within the table.


Just pass RAND() value as parameter from outside:

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

And call as getNumber(10, RAND())

No any side effects.


be carefull with RAND!

If you check this, you'll see, that multiple calls to this VIEW come all back with the same value. This is different with NEWID(). So if you really want random numbers it could be better to take NEWID() und do some "tricks" to get a number from - let's say - the first bytes...

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      

This is a result:

  1. 0,684530884058892 D1809581-BBD1-4D23-A7F9-BC697E869BB0
  2. 0,684530884058892 A4BAECDE-E993-46C1-B571-7440A713C371
  3. 0,684530884058892 D7A1CB65-D2BC-41B2-990D-C3BC52B056A2

A view for a random BIGINT could look like this:

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

Hint: I checked this with many rows and it seems (just by sight), that this approach is not really random (all BIGINTs have the same width...). This seems to work properly:

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

The problem is that you cannot call a non-deterministic function from inside a user-defined function.

I got around this limitation by creating a view, call that function inside the view and use that view inside your function, something like this......

View Definition

CREATE VIEW vw_getRANDValue
AS
SELECT RAND() AS Value

Function Definition

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