仅在运行函数一次时,在where子句中多次使用用户定义的函数(UDF)

时间:2012-08-30 17:19:45

标签: sql sql-server sql-server-2008

我有一个表来保存任务列表,这些任务需要在特定的日期和时间处理。棘手的部分是这些任务是递归运行时间必须根据5个不同的参数计算。

通过UDF计算运行时间很简单:

Function dbo.task_next_run(
    @task_type varchar(10),
    @task_schedule_day_of_week varchar(20),
    @task_schedule_time varchar(20),
    @task_period smallint,
    @last_run datetime
)
Returns datetime
...
...
...
Return @next_run

我的最终任务查询是:

SELECT id, 
       task_name, 
       last_run 
From tasks 
Where dbo.task_next_run
(
   task_type, @task_schedule_day_of_week, 
   @task_schedule_time, @task_period, @last_run
) < getdate() and 
dbo.task_next_run
(
     task_type, @task_schedule_day_of_week, 
     @task_schedule_time, @task_period, @last_run
) > last_run

我的问题是在where子句中运行2次相同的功能。我需要一个解决方案,将计算值用作where子句中的别名。

3 个答案:

答案 0 :(得分:4)

你为什么不这样做:

DECLARE @now DATETIME = CURRENT_TIMESTAMP;

SELECT id, task_name, last_run
FROM 
(
  SELECT id, task_name, last_run, d = dbo.task_next_run
  (task_type, @task_schedule_day_of_week, @task_schedule_time, @task_period, @last_run)
  From tasks 
) AS x
WHERE x.d < @now
AND x.d > x.last_run;

我很确定SQL Server会将它折叠到同一个东西,并且只调用一次而不是两次。但是,根据函数的性质,它仍然可以每行执行一次。您是否考虑过将UDF转换为内联表值函数?这些通常优化得更好。

另一种选择是(如评论中所述):

DECLARE @now DATETIME = CURRENT_TIMESTAMP;

DECLARE @d TABLE(task_type INT PRIMARY KEY, post DATETIME);

INSERT @d SELECT task_type, dbo.task_next_run(task_type, @variables)
  FROM (SELECT task_type FROM dbo.tasks GROUP BY task_type);

现在你可以说:

SELECT t.id, t.task_name, t.last_run
FROM dbo.tasks AS t
INNER JOIN @d AS d
ON t.task_type = d.task_type
AND t.last_run > d.post
WHERE d.post < @now;

您甚至可以先进一步过滤:

DELETE @d WHERE post >= @now;

这允许您消除上面的WHERE。

所有人都告诉它可能仍然会优化相同,但可能值得一点点表现更好(这里的任何人都可以预测30,000英尺以外的任何变量)。

答案 1 :(得分:2)

交叉申请是我所需要的。这是Cross Apply的最终查询。

SELECT id, task_name, last_run, func.next_run
FROM tasks
Cross Apply (Select dbo.task_next_run(task_type, @task_schedule_day_of_week, @task_schedule_time, @task_period, @last_run) as next_run) as func
WHERE 
func.next_run < getdate() and
func.next_run > last_run

答案 2 :(得分:-1)

SELECT id, 
       task_name, 
       last_run 
       From tasks 
WHERE dbo.task_next_run
      (
            task_type, @task_schedule_day_of_week, 
            @task_schedule_time, @task_period, @last_run
      ) BETWEEN  last_run AND getdate()