views:

574

answers:

4

I have the following query

    
DECLARE @userId INT  
DECLARE @siteId INT

SET @siteId = -1  
SET @userId = 1828  

SELECT  a.id AS alertId,  
        a.location_id,  
        a.alert_type_id,  
        a.event_id,  
        a.user_id,  
        a.site_id,  
        a.accepted_by  
FROM    alerts AS a    
JOIN    alert_types AS ats ON a.alert_type_id = ats.id 
JOIN    events AS tr ON a.event_id = tr.event_id 
WHERE   tr.end_Time IS null
AND     tr.status_id = 0
AND     ats.code = 'E'
AND     a.site_id in (SELECT * FROM dbo.udf_get_event_sitelist(@siteId, @userId))

This query takes between 5 and 17 seconds to run, however under many circumstances the function dbo.udf_get_event_sitelist(@siteId, @userId) returns no rows, so the query will not find any data.

How can I force SQL Server to execute the user defined function first. I appreciate that I could rewrite the query into a stored procedure and perform the sub-select first, however I would like to do it in a single SQL statement if possible.

+3  A: 

you could select the results of udf_get_event_sitelist into a table variable and only proceed with the big query if @@rowcount > 0

Sam Saffron
A: 

Also, modify you UDF to only return the site_ID as I am guessing you do not neet all (*) columns

SELECT * FROM dbo.udf_get_event_sitelist(@siteId, @userId)

to

SELECT site_id FROM dbo.udf_get_event_sitelist(@siteId, @userId)
John Sansom
I assume the UDF is only returning one column, otherwise this would not be valid? I agree that it would look clearer if the column were specified, though (and prevents the query from being broken if the UDF is subsequently amended to return multiple columns).
Ian Nelson
Not only would it look clearer, but it would encourage better practices. Seeing the lack of JOIN type specified, this is definitely a good thing to point out.
StingyJack
Is it considered good practice to always specify implicit join types? I am aware that an INNER JOIN is the default type but I do not specify the JOIN type as I want the default. In the same way I rarely use SORT ASCENDING as it is implied.
Steve Weet
Better to say it than leave it to assumption (the mother of all f'ups) of the next reader. Defaults have been known to change, but probably not this case.
StingyJack
+2  A: 

The problem that you have when using inline functions is that they can be re-evaluated for each row returned in the SELECT. This means, that if the SELECT statement returns 100 rows, then the function can be executed 100 times.

You should really follow Sambo99's advice and extract it to a table variable (or a temp table if you think it needs indexes).

StingyJack
I has assumed that as I had not correlated the Sub query to the outer query then SQL Server would evaluate that condition first, as many other database engines would). I guess I assumed incorrectly.
Steve Weet
It can happen either way, so blindly saying one way or the other is assuming the plan the optimizer is going to take. I think its better to be explicit in any case where it could go either way.
StingyJack
+2  A: 

make the "FROM" table the results set of the function and join the other tables to it

DECLARE @userId INT  
DECLARE @siteId INT

SET @siteId = -1  
SET @userId = 1828  

SELECT  a.id AS alertId,  
        a.location_id,  
        a.alert_type_id,  
        a.event_id,  
        a.user_id,  
        a.site_id,  
        a.accepted_by  
FROM    (SELECT * FROM dbo.udf_get_event_sitelist(@siteId, @userId)) dt
JOIN    alerts AS a ON dt.site_id=a.site_id
JOIN    alert_types AS ats ON a.alert_type_id = ats.id 
JOIN    events AS tr ON a.event_id = tr.event_id 
WHERE   tr.end_Time IS null
AND     tr.status_id = 0
AND     ats.code = 'E'
KM
Thanks Mike. I had just got to the same solution myself but hadn't written it up yet. Out of interest this changed the time taken for the majority of queries from 5-15 seconds down to sub-second
Steve Weet
+1 for another way, in line and non RBAR. Nice Mike!
StingyJack