views:

77

answers:

3

I have contacts that can be in more than one group and have more than one request. I need to simply get contacts for a specific group that have no specific requests.

How do I improve the performance of this query:

SELECT  top 1 con_name ,
        con_id
FROM    tbl_group_to_contact gc
        INNER JOIN tbl_contact c ON gc.con_id = c.id
WHERE   group_id = '81'
        AND NOT c.id IN ( SELECT    con_id
                          FROM      tbl_request_to_contact
                          WHERE     request_id = '124' )

When I run that query with Explanation plan it shows that this query:

SELECT    con_id
                          FROM      tbl_request_to_contact
                          WHERE     request_id = '124'

is expensive with using an index seek.

 |--Top(TOP EXPRESSION:((1)))
       |--Nested Loops(Left Anti Semi Join, OUTER REFERENCES:([c].[id]))
            |--Nested Loops(Inner Join, OUTER REFERENCES:([gc].[con_id], [Expr1006]) WITH UNORDERED PREFETCH)
            |    |--Clustered Index Scan(OBJECT:([db_newsletter].[dbo].[tbl_group_to_contact].[PK_tbl_group_to_contact_1] AS [gc]),  WHERE:([db_newsletter].[dbo].[tbl_group_to_contact].[group_id] as [gc].[group_id]=(81)) ORDERED FORWARD)
            |    |--Clustered Index Seek(OBJECT:([db_newsletter].[dbo].[tbl_contact].[PK_tbl_contact] AS [c]), SEEK:([c].[id]=[db_newsletter].[dbo].[tbl_group_to_contact].[con_id] as [gc].[con_id]) ORDERED FORWARD)
            |--Top(TOP EXPRESSION:((1)))
                 |--Clustered Index Seek(OBJECT:([db_newsletter].[dbo].[tbl_request_to_contact].[PK_tbl_request_to_contact] AS [cc]), SEEK:([cc].[request_id]=(124)),  WHERE:([db_newsletter].[dbo].[tbl_contact].[id] as [c].[id]=[db_newsletter].[dbo].[tbl_request_to_contact].[con_id] as [cc].[con_id]) ORDERED FORWARD)
+2  A: 

Your query is ok, just create the following indexes:

tbl_request_to_contact (request_id, con_id)
tbl_group_to_contact (group_id, con_id)

Since the tables seem to be the link tables, you want to make these composites the primary keys:

ALTER TABLE tbl_request_to_contact ADD CONSTRAINT pk_rc PRIMARY KEY (request_id, con_id)
ALTER TABLE tbl_group_to_contact ADD CONSTRAINT pk_gc (group_id, con_id)

, making sure that request_id and group_id go first.

Also, if your request_id and group_id are integers, pass the integers as the parameters, not strings:

SELECT  con_name, con_id
FROM    tbl_group_to_contact gc
JOIN    tbl_contact c
ON      c.id = gc.con_id
WHERE   group_id = 81
        AND c.id NOT IN
        (
        SELECT  con_id
        FROM    tbl_request_to_contact
        WHERE   request_id = 124
        )

, or an implicit conversion may occur rendering the indexes unusable.

Update:

From your plan I see that you miss the index on tbl_group_to_contact. Full table scan is required to filter the groups.

Create the index:

CREATE UNIQUE INDEX ux_gc ON tbl_group_to_contact (group_id, con_id)
Quassnoi
Agree on the first index. Care to explain on the second? Would add INCLUDE (con_name)
Frank Kalis
you know what? i deleted top 1 from select statement and it started work much faster, how can u explain it? my i use another approach for selecting only one row?
msony
@Frank: the second just helps to filter the groups. `con_name` most probably belongs to `tbl_contact`, not `tbl_group_to_contact`.
Quassnoi
True, who knows with a doubt from the given query. :-)
Frank Kalis
A: 

You may want to try running the SQL Server Database Tuning Advisor.

Adam Robinson
A: 

I agree with @Quassnoi with the indexes. Plus you can use a left join to only show users who don't have requests. This usually has better performance than a sub-query.

What is the request_id = '124' for? Other request id's don't matter?

SELECT  con_name ,
        con_id
FROM    tbl_group_to_contact gc
        INNER JOIN tbl_contact c ON gc.con_id = c.id
        LEFT JOIN tbl_request_to_contact rtc ON gc.con_id = rtc.con_id
WHERE   group_id = '81' and rtc.request_id IS NULL 
Brettski
http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/
Quassnoi
i cannot use request_id IS NULL, i need specific request_id
msony
@Quassnoi: regarding your article... is there any way to contact you for a question or two?
Frank Kalis
@Frank: sure, http://explainextended.com/drop-me-a-line
Quassnoi
aah, thanks. will do this evening when I'm back home.
Frank Kalis
@msony: `request_id IS NULL` applied to the `LEFT JOIN` does the same as the `NOT IN` you originally used. However, in `SQL Server`, `NOT IN` behaves same or better than `LEFT JOIN`, so there is no point in rewriting the query.
Quassnoi
Quassnoi, now that's pretty interesting.
Brettski
@msony, why do you need a specific value? I guess all of this is mute if you go with the not in suggested by Quassnoi
Brettski