RAND in UDF

Попробовал написать вот такую функцию, которая возращает случайную дату из заданного диапазона. Но подлый мастдай не дает - говорит, что используется побочный эффект.

create function RandDate(@BegDate smalldatetime, @EndDate smalldatetime) returns smalldatetime
as
begin
declare @res smalldatetime
select @res=convert(smalldatetime, convert(int, convert(int,@BegDate)+rand()*datediff(day, @BegDate,@EndDate)))
return @res
end

В BOL ни каких упоминаний о том, что RAND нельзя вставлять в UDF нет. Может кто подскажешь как это обойти.

Forums: 

> В BOL ни каких

> В BOL ни каких упоминаний о том, что RAND нельзя вставлять в UDF нет.
Есть, недетерминистические функции нельзя использовать в UDF(т.к.они должны быть детерм...)

"Стандартных" обхода два - либо передавать как параметр, либо выбирать из таблицы/вьюхи, обычно, из вьюхи, типа того:

create view vw_rand as select rand() [rand]
go
create function RandDate(@BegDate smalldatetime, @EndDate smalldatetime) returns smalldatetime
as
begin
declare @res smalldatetime
select @res=convert(smalldatetime, convert(int, convert(int,@BegDate)+[rand]*datediff(day, @BegDate,@EndDate)))
from vw_rand
return @res
end
go

Спасибо за подсказку

Но MS меня огорчил ...
Получается запрет на прямой вызов RAND это чистая перестраховка MS.
Т.к. Ваше решение по воздействию на seed, который использует RAND() полностью эквивалентно явному вызову из UDF.

Изображение пользователя st.

Кстати, в твоем

Кстати, в твоем случае может иметь смысл делать не функцию, а вьюшку с вычислимым полем.
Тогда в любом запросе ее можно пристыковать.

Будет фигня полная

Т.к. к view будет всего одно обращение не зависомо от кол-ва строк в запросе.
Только UDF дает нужный результат, т.к. вычисляется для каждой строки.

create view vw_rand as select rand() [rand]
go
create function RandDate(@BegDate smalldatetime, @EndDate smalldatetime) returns smalldatetime
as
begin
declare @res smalldatetime
select @res=convert(smalldatetime, convert(int, convert(int,@BegDate)+[rand]*datediff(day, @BegDate,@EndDate)))
from vw_rand
return @res
end
go

declare @tbl table
(id int,
[Rand] float NULL,
RandDate smalldatetime NULL
)
declare @cnt int
set @cnt=1
while @cnt<=100
begin
insert into @tbl (id) select @cnt
select @cnt=@cnt+1
End

update @tbl set [Rand]=vw_rand.[rand], RandDate=dbo.RandDate('20010101', '20071001') from vw_rand

select * from @tbl

Тот же эффект, если заменить vw_rand.[rand] на RAND().

"производная" от newid()

В Вашем случае я бы порекомендовал использовать "производную" от newid()(которая вызывается для каждой строки), например
select rand(checksum(newid())) [rand] from sysobjects

Можно и так

Можно и так :)
checksum(newid()) уже сам генератор случайной последовательности, так что rand() это уже избыточность.

диапазон от 0 до 1

> Можно и так
UDF в запросах - существенные тормоза, если можно обойтись без них, то лучше обойтись (мнение очень многих разработчиков t-sql)

> rand() это уже избыточность
так ведь используется для превращения значений в диапазон от 0 до 1, накладные расходы мизерные...

Согласен

>UDF в запросах - существенные тормоза, если можно обойтись без них, то лучше обойтись
В общем случае да. Но как всегда, многое зависит от конкретной ситуации.
Приминительно к обсуждаемой задаче производительность будет сопоставимой, т.к. вызов RAND(...), будет происходить одинаковое кол-во раз в обоих вариантах (view, UDF).
По-моему, запрос с UDF более выразителен.

>(мнение очень многих разработчиков t-sql)
перестраховка и отсуствие понимания как работает движок БД
Что бы ламер не наваял сложный запрос с агрегацией по большой таблице в UDF, лучше объявим этот механизм вредным вообще.
Напоминает крестовый поход "паскалистов" против goto в 80-х прошлого века.

Дело в том, что

Дело в том, что вызов UDF "тяжелей" вызова сист.функ.(подобной RAND(...)), простой тест:

if object_id('test_func') is not null drop function test_func
go
create function dbo.test_func(@aid int, @bid int) returns int as
begin return abs(@aid)-abs(@bid) end
go
declare @dt datetime;set @dt=getdate()
select top 1 * from master..sysobjects a, master..sysobjects b
order by rand(abs(a.id)-abs(b.id)) -- это заставит применить к каждой записи
print datediff(ms,@dt,getdate())
go
declare @dt datetime;set @dt=getdate()
select top 1 * from master..sysobjects a, master..sysobjects b
order by dbo.test_func(a.id, b.id) -- это заставит применить к каждой записи
print datediff(ms,@dt,getdate())
go
if object_id('test_func') is not null drop function test_func
-----------------------------------
(1 row(s) affected)
29483

(1 row(s) affected)
47233

Поэтому рекомендацию можно конкретизировать - "если можно обойтись без вызова UDF (для каждой строки запроса) - лучше обойтись"