April 4, 2005 at 12:02 pm
The query below is taking 3-4 seconds to run under a light load, which
seems to be a bit lengthy for the indexes that are in place and the
amount of data that exists in the tables. I have outlined everything
below, including all table definitions, indexes, and row counts. Any
help at all will be appreciated. It seems no matter how I think an
index will function it never seems to work properly.
==
BEGIN QUERY
==
SELECT tblC.catDesc AS Category_Name,
COUNT(DISTINCT tblS.set_ID) AS Set_Count,
tblC.cat_ID AS Category_ID,
COUNT(tblI.Img_ID) AS Image_Count,
MIN(tblI.Img_ID) AS Image_ID,
COALESCE(SUM(
CASE WHEN tblI.d_t > @d_t
AND tblI.display_status = 1 THEN
1
END
), 0) AS New_Image_Count,
COALESCE(COUNT(DISTINCT
CASE WHEN tblI.d_t > @d_t THEN
tblI.set_ID
END
), 0) AS New_Set_Count
FROM tblCategories tblC
LEFT JOIN tblImage tblI ON tblC.cat_ID = tblI.cat_ID
LEFT JOIN tblSets tblS ON tblI.set_ID = tblS.set_ID
WHERE tblC.skin_ID = @skin_ID
GROUP BY tblC.cat_id, tblC.catDesc
ORDER BY Category_Name
==
END QUERY
==
==
tblImage (approx. 71000 rows)
==
Definition:
Img_ID (int, Not Null) - PK
set_ID (int, Null)
cat_ID (int, Null)
d_t (datetime, Null)
display_status (int, Null)
Indexes:
1. Img_ID (clustered)
2. cat_id DESC, display_status DESC, d_t DESC
3. d_t DESC, display_status DESC, set_ID, cat_ID
4. set_ID DESC
==
END tblImage
==
==
tblCategories (approx. 35 rows)
==
Definition:
cat_ID (int, Not Null) - PK
catDesc (varchar(25), Null)
skin_ID (int, Null)
Indexes:
1. cat_ID (clustered)
2. skin_ID, cat_ID
==
END tblCategories
==
==
tblSets (approx. 1500 rows)
==
Definition:
set_ID (int, Not Null) - PK
setName (varchar(25), Null)
setKeywords (varchar(500), Null)
Indexes:
1. set_ID (clustered)
==
END tblSets
==
Thanks!!
-- Shawn
April 4, 2005 at 12:12 pm
Can you run with SET SHOWPLAN ON and post the results ? It would be useful to see what execution plan the optimizer is actually choosing.
April 4, 2005 at 12:16 pm
Receiving the following error:
'SHOWPLAN' is not a recognized option.
when trying to execute:
SET SHOWPLAN ON
prior to the query. Sorry guys, I'm a little new at reading execution plans.
April 4, 2005 at 12:20 pm
Ok.. Think I got it.. Is this what you're lookin for? It's probably going to wrap and look a little odd.
|--Sort(ORDER BY[tblC].[catDesc] ASC)) |--Compute Scalar(DEFINE[Expr1008]=If ([Expr1006]NULL) then [Expr1006] else 0, [Expr1009]=If ([Expr1007]NULL) then [Expr1007] else 0)) |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID])) |--Hash Match(Inner Join, HASH[tblC].[cat_ID])=([tblC].[cat_ID]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID])) |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID])) | |--Compute Scalar(DEFINE[Expr1007]=Convert([Expr1038]))) | |--Stream Aggregate(GROUP BY[tblC].[cat_ID]) DEFINE[Expr1038]=COUNT_BIG([Expr1020]))) | |--Sort(ORDER BY[tblC].[cat_ID] ASC)) | |--Hash Match(Aggregate, HASH[tblC].[cat_ID], [Expr1020]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID] AND [Expr1020]=[Expr1020])) | |--Table Spool | |--Compute Scalar(DEFINE[Expr1020]=If ([tblI].[d_t]>[@d_t]) then [tblI].[set_ID] else NULL)) | |--Hash Match(Right Outer Join, HASH[tblS].[set_ID])=([tblI].[set_ID]), RESIDUAL[tblI].[set_ID]=[tblS].[set_ID])) | |--Clustered Index Scan(OBJECT[dbImageArchive].[dbo].[tblSets].[PK_tblSets] AS [tblS])) | |--Hash Match(Left Outer Join, HASH[tblC].[cat_ID])=([tblI].[cat_ID]), RESIDUAL[tblC].[cat_ID]=[tblI].[cat_ID])) | |--Bookmark Lookup(BOOKMARK[Bmk1000]), OBJECT[dbImageArchive].[dbo].[tblCategories] AS [tblC])) | | |--Index Seek(OBJECT[dbImageArchive].[dbo].[tblCategories].[skin_ID_cat_ID] AS [tblC]), SEEK[tblC].[skin_ID]=[@skin_id]) ORDERED FORWARD) | |--Index Scan(OBJECT[dbImageArchive].[dbo].[tblImage].[d_t_display_status_set_id_cat_id] AS [tblI])) |--Hash Match(Inner Join, HASH[tblC].[cat_ID])=([tblC].[cat_ID]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID])) |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID])) | |--Compute Scalar(DEFINE[Expr1003]=Convert([Expr1039]))) | |--Hash Match(Aggregate, HASH[tblC].[cat_ID]) DEFINE[Expr1039]=COUNT_BIG([tblS].[set_ID]))) | |--Hash Match(Aggregate, HASH[tblC].[cat_ID], [tblS].[set_ID]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID] AND [tblS].[set_ID]=[tblS].[set_ID])) | |--Table Spool |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID])) |--Compute Scalar(DEFINE[Expr1004]=Convert([Expr1040]), [Expr1006]=If ([Expr1041]=0) then NULL else [Expr1042])) |--Hash Match(Aggregate, HASH[tblC].[cat_ID]) DEFINE[Expr1040]=COUNT_BIG([tblI].[Img_ID]), [Expr1041]=COUNT_BIG(If ([tblI].[d_t]>[@d_t] AND [tblI].[display_status]=1) then 1 else NULL), [Expr1042]=SUM(If ([tblI].[d_t]>[@d_t] AND [tblI].[display_status]=1) then 1 else NULL), [Expr1005]=MIN([tblI].[Img_ID]), [tblC].[catDesc]=ANY([tblC].[catDesc]))) |--Table Spool
April 4, 2005 at 12:21 pm
How are you correlating C to S?
Good Hunting!
AJ Ahrens
webmaster@kritter.net
April 4, 2005 at 1:07 pm
Not sure if I understand your question directly, but here is what I think you are asking for:
tblCategories (C) and tblSets (S) do not relate to each-other directly. Records in tblImages (I) are related to tblCategories (C) and tblSets (S), hence the SQL statement:
FROM tblCategories tblC
LEFT JOIN tblImage tblI ON tblC.cat_ID = tblI.cat_ID
LEFT JOIN tblSets tblS ON tblI.set_ID = tblS.set_ID
April 4, 2005 at 2:05 pm
Just to update everybody, I have modified the query a bit to 86 the join on tblSets:
SELECT tblC.catDesc AS Category_Name, COUNT(DISTINCT tblI.set_ID) AS Set_Count, tblC.cat_ID AS Category_ID, COUNT(tblI.Img_ID) AS Image_Count, MIN(tblI.Img_ID) AS Image_ID, COALESCE(SUM( CASE WHEN tblI.d_t > @d_t AND tblI.display_status = 1 THEN 1 END ), 0) AS New_Image_Count, COALESCE(COUNT(DISTINCT CASE WHEN tblI.d_t > @d_t THEN tblI.set_ID END ), 0) AS New_Set_Count FROM tblCategories tblC LEFT JOIN tblImage tblI ON tblC.cat_ID = tblI.cat_ID WHERE tblC.skin_ID = @skin_ID GROUP BY tblC.cat_id, tblC.catDesc ORDER BY Category_Name
April 5, 2005 at 2:39 am
I'd query your need to 'cluster' the tblimage on imageid....unless it adds some benefit to another part of your application. There may be a better candidate for a clustered index....IF ANY should be used.
Also the Index Scan on "dbImageArchive].[dbo].[tblImage].[d_t_display_status_set_id_cat_id] AS [tblI]))" isn't good news. You should be looking to get this as index seek.....(maybe the DESC part of the index definition doesn't help....why DESC anyway?....surely ordering for output is a function of any SELECT statement that operates on that data)
April 5, 2005 at 8:06 am
I have removed img_id from the clustered index on tblImage.
I have also changed all indexes on this table so the sort order is ASC instead of DESC. The reason I made some fields DESC was because that the newest records are going to be accessed the most, and I thought it would be better if those would be at the top of the index, no?
In any case, here is the updated query and new execution plan:
SELECT tblC.catDesc AS Category_Name, COUNT(DISTINCT tblI.set_ID) AS Set_Count, tblC.cat_ID AS Category_ID, COUNT(tblI.Img_ID) AS Image_Count, MIN(tblI.Img_ID) AS Image_ID, COALESCE(SUM( CASE WHEN tblI.d_t > @d_t AND tblI.display_status = 1 THEN 1 END ), 0) AS New_Image_Count, COALESCE(COUNT(DISTINCT CASE WHEN tblI.d_t > @d_t THEN tblI.set_ID END ), 0) AS New_Set_Count FROM tblCategories tblC LEFT JOIN tblImage tblI ON tblC.cat_ID = tblI.cat_ID WHERE tblC.skin_ID = @skin_ID GROUP BY tblC.cat_id, tblC.catDesc ORDER BY Category_Name |--Compute Scalar(DEFINE[Expr1007]=If ([Expr1005]NULL) then [Expr1005] else 0, [Expr1008]=If ([Expr1006]NULL) then [Expr1006] else 0)) |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID], [tblC].[catDesc]=[tblC].[catDesc])) |--Merge Join(Inner Join, MANY-TO-MANY MERGE[tblC].[catDesc])=([tblC].[catDesc]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID] AND [tblC].[catDesc]=[tblC].[catDesc])) |--Sort(ORDER BY[tblC].[catDesc] ASC)) | |--Hash Match(Inner Join, HASH[tblC].[cat_ID], [tblC].[catDesc])=([tblC].[cat_ID], [tblC].[catDesc]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID] AND [tblC].[catDesc]=[tblC].[catDesc])) | |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID], [tblC].[catDesc]=[tblC].[catDesc])) | | |--Compute Scalar(DEFINE[Expr1002]=Convert([Expr1037]))) | | |--Hash Match(Aggregate, HASH[tblC].[cat_ID], [tblC].[catDesc]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID] AND [tblC].[catDesc]=[tblC].[catDesc]) DEFINE[Expr1037]=COUNT_BIG([tblI].[set_ID]))) | | |--Hash Match(Aggregate, HASH[tblC].[cat_ID], [tblC].[catDesc], [tblI].[set_ID]), RESIDUAL([tblC].[cat_ID]=[tblC].[cat_ID] AND [tblC].[catDesc]=[tblC].[catDesc]) AND [tblI].[set_ID]=[tblI].[set_ID])) | | |--Table Spool | | |--Compute Scalar(DEFINE[Expr1019]=If ([tblI].[d_t]>[@d_t]) then [tblI].[set_ID] else NULL)) | | |--Nested Loops(Left Outer Join, OUTER REFERENCES[tblC].[cat_ID]) WITH PREFETCH) | | |--Clustered Index Seek(OBJECT[dbImageArchive].[dbo].[tblCategories].[skin_ID_cat_ID] AS [tblC]), SEEK[tblC].[skin_ID]=[@skin_id]) ORDERED FORWARD) | | |--Clustered Index Seek(OBJECT[dbImageArchive].[dbo].[tblImage].[cat_id_img_id] AS [tblI]), SEEK[tblI].[cat_ID]=[tblC].[cat_ID]) ORDERED FORWARD) | |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID], [tblC].[catDesc]=[tblC].[catDesc])) | |--Compute Scalar(DEFINE[Expr1003]=Convert([Expr1038]), [Expr1005]=If ([Expr1039]=0) then NULL else [Expr1040])) | |--Hash Match(Aggregate, HASH[tblC].[cat_ID], [tblC].[catDesc]), RESIDUAL[tblC].[cat_ID]=[tblC].[cat_ID] AND [tblC].[catDesc]=[tblC].[catDesc]) DEFINE[Expr1038]=COUNT_BIG([tblI].[Img_ID]), [Expr1039]=COUNT_BIG(If ([tblI].[d_t]>[@d_t] AND [tblI].[display_status]=1) then 1 else NULL), [Expr1040]=SUM(If ([tblI].[d_t]>[@d_t] AND [tblI].[display_status]=1) then 1 else NULL), [Expr1004]=MIN([tblI].[Img_ID]))) | |--Table Spool |--Compute Scalar(DEFINE[tblC].[cat_ID]=[tblC].[cat_ID], [tblC].[catDesc]=[tblC].[catDesc])) |--Compute Scalar(DEFINE[Expr1006]=Convert([Expr1041]))) |--Stream Aggregate(GROUP BY[tblC].[catDesc], [tblC].[cat_ID]) DEFINE[Expr1041]=COUNT_BIG([Expr1019]))) |--Sort(ORDER BY[tblC].[catDesc] ASC, [tblC].[cat_ID] ASC)) |--Hash Match(Aggregate, HASH[tblC].[cat_ID], [tblC].[catDesc], [Expr1019]), RESIDUAL([tblC].[cat_ID]=[tblC].[cat_ID] AND [tblC].[catDesc]=[tblC].[catDesc]) AND [Expr1019]=[Expr1019])) |--Table Spool
April 5, 2005 at 8:20 am
looks better.
what's the change in performance?
remember to flush buffers/cache to get a proper performance benchmark.
April 5, 2005 at 8:57 am
SELECT
cat_ID,
catDesc,
[dbo].[CountAllSetByCatID](cat_id) AS Set_Count,
[dbo].[CountAllImgByCatID](cat_id) as Image_Count,
[dbo].[GetMinImgIDbyCatID](cat_ID) AS Image_ID,
[dbo].[GetSUMDispStatByCatIDDT](cat_ID,@d_t) AS New_Image_Count,
[dbo].[CountAllSetByCatID_DT](cat_id,@d_t) AS New_Set_Count
FROM tblCategories
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[CountAllImgByCatID]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[CountAllImgByCatID]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[CountAllSetByCatID]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[CountAllSetByCatID]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[CountAllSetByCatID_DT]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[CountAllSetByCatID_DT]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[GetMinImgIDbyCatID]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[GetMinImgIDbyCatID]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[GetSUMDispStatByCatIDDT]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[GetSUMDispStatByCatIDDT]
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE FUNCTION [dbo].[CountAllImgByCatID]
(@cat_ID int)
RETURNS int
AS
BEGIN
declare @result int
select @result=count(img_ID) from tblImage where
return @result
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE FUNCTION [dbo].[CountAllSetByCatID]
(@cat_ID int)
RETURNS int
AS
BEGIN
declare @myReturn int
select @myReturn=count(distinct set_ID) from tblImage
where cat_ID=@cat_ID
return @myReturn
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE FUNCTION [dbo].[CountAllSetByCatID_DT]
(@cat_ID int,
@D_T datetime)
RETURNS int
AS
BEGIN
declare @myReturn int
select @myReturn=count(distinct set_ID) from tblImage
where cat_ID=@cat_ID and d_t>@D_T
return @myReturn
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE FUNCTION [dbo].[GetMinImgIDbyCatID]
(@cat_ID int)
RETURNS int
AS
BEGIN
DECLARE @result int
SELECT @result=ISNULL(MIN(img_ID),0)
FROM tblImage where cat_ID=@cat_ID
RETURN @result
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE FUNCTION [dbo].[GetSUMDispStatByCatIDDT]
(@cat_ID int,@d_t datetime)
RETURNS int
AS
BEGIN
declare @result int
select @result=SUM(display_status) from tblImage where cat_ID=@cat_ID and d_t > @d_t
return @result
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
I assumed that display_status can be 0 or 1
try to get loose the NULL's from tables you don't need them
I just looked in the Query analizer to compare wich metod is more effective 75% old vs 25% cost this one you can try and see how it works
: )
Vasc
April 5, 2005 at 9:06 am
I am flushing the buffers and cache with:
DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE
Performance has not really improved. Still 3 seconds to execute
April 5, 2005 at 9:12 am
Can you take out some of the 'funny sh*t' and see the effect of each portion on the overall affair?
ie....build it up from a small query and see which straw breaks the camels back? Maybe you don't 'need' it all in 1.
Posting sample input data, and matching expected results may drive someone to spot a better solution.
Also what does the count(distinct()), etc do for you?
SELECT tblC.catDesc AS Category_Name,
COUNT(DISTINCT tblI.set_ID) AS Set_Count,
tblC.cat_ID AS Category_ID,
COUNT(tblI.Img_ID) AS Image_Count,
MIN(tblI.Img_ID) AS Image_ID,
COALESCE(SUM(
CASE WHEN tblI.d_t > @d_t
AND tblI.display_status = 1 THEN
1
END
), 0) AS New_Image_Count,
COALESCE(COUNT(DISTINCT
CASE WHEN tblI.d_t > @d_t THEN
tblI.set_ID
END
), 0) AS New_Set_Count
April 5, 2005 at 11:12 am
Re: The above example using Functions. This will never execute faster than a normal set-based approach. Using a Function in the SELECT posrtion of a query, where the function itself queries data from tables, turns your efficient set-based query into something similar to a cursor based approach, where row-based function evaluation has to occur.
April 5, 2005 at 2:00 pm
PW you are 100% right ...
functions used like that are putting too much overhead even if the table had a few rows... the plan was way too bad : )
After another look I observed that the cost is mainly given by the DISTINCT clause... so another aproach...
SELECT catDesc as Category_Name,
tC.cat_ID as Category_ID,
ISNULL(SUM(Set_Count),0) as Set_Count,
ISNULL(SUM(Image_Count),0) as Image_Count,
MIN(Image_ID) as Image_ID,
ISNULL(SUM(New_Image_Count),0) as New_Image_Count,
ISNULL(SUM(New_Set_Count),0) as New_Set_Count
FROM tblCategories tC left join
(SELECT
cat_ID AS Category_ID,
1 AS Set_Count,
COUNT(tblI.Img_ID) AS Image_Count,
MIN(tblI.Img_ID) AS Image_ID,
COALESCE(SUM(CASE WHEN tblI.d_t > @d_t AND tblI.display_status = 1 THEN 1 END), 0) AS New_Image_Count,
COALESCE(CASE WHEN SUM(CASE WHEN tblI.d_t > @d_t THEN 1 END)>0 THEN 1 END,0) AS New_Set_Count
FROM tblImage tblI
GROUP BY cat_id, tblI.set_ID
) t
on tC.cat_ID=t.Category_ID
WHERE tC.skin_ID = @skin_ID
GROUP BY tC.cat_ID, tC.catDesc
ORDER BY tC.catDesc
this should work
: )
I added for tblImage index=cat_ID,set_ID
for tblCategories=skin_ID,catDesc
Vasc
Viewing 15 posts - 1 through 15 (of 27 total)
You must be logged in to reply to this topic. Login to reply