Subselect Query Improvement - sql

How can I improve the SQL query below (SQL Server 2008)? I want to try to avoid sub-selects, and I'm using a couple of them to produce results like this
StateId TotalCount SFRCount OtherCount
---------------------------------------------------------
AZ 102 50 52
CA 2931 2750 181
etc...
SELECT
StateId,
COUNT(*) AS TotalCount,
(SELECT COUNT(*) AS Expr1 FROM Property AS P2
WHERE (PropertyTypeId = 1) AND (StateId = P.StateId)) AS SFRCount,
(SELECT COUNT(*) AS Expr1 FROM Property AS P3
WHERE (PropertyTypeId <> 1) AND (StateId = P.StateId)) AS OtherCount
FROM Property AS P
GROUP BY StateId
HAVING (COUNT(*) > 99)
ORDER BY StateId

This may work the same, hard to test without data
SELECT
StateId,
COUNT(*) AS TotalCount,
SUM(CASE WHEN PropertyTypeId = 1 THEN 1 ELSE 0 END) as SFRCount,
SUM(CASE WHEN PropertyTypeId <> 1 THEN 1 ELSE 0 END) as OtherCount
FROM Property AS P
GROUP BY StateId
HAVING (COUNT(*) > 99)
ORDER BY StateId

Your alternative is a single self-join of Property using your WHERE conditions as a join parameter. The OtherCount can be derived by subtracting the TotalCount - SFRCount in a derived query.

Another alternative would be to use the PIVOT function like this:
SELECT StateID, [1] + [2] AS TotalCount, [1] AS SFRCount, [2] AS OtherCount
FROM Property
PIVOT ( COUNT(PropertyTypeID)
FOR PropertyTypeID IN ([1],[2])
) AS pvt
WHERE [1] + [2] > 99
You would need to add an entry for each property type which could be daunting but it is another alternative. Scott has a great answer.

If PropertyTypeId is not null then you could do this with a single join. Count is faster than Sum. But is Count plus Join faster than Sum. The test case below mimics your data. docSVsys has 800,000 rows and there are about 300 unique values for caseID. The Count plus Join in this test case is slightly faster than the Sum. But if I remove the with (nolock) then Sum is about 1/4 faster. You would need to test with your data.
select GETDATE()
go;
select caseID, COUNT(*) as Ttl,
SUM(CASE WHEN mimeType = 'message/rfc822' THEN 1 ELSE 0 END) as SFRCount,
SUM(CASE WHEN mimeType <> 'message/rfc822' THEN 1 ELSE 0 END) as OtherCount,
COUNT(*) - SUM(CASE WHEN mimeType = 'message/rfc822' THEN 1 ELSE 0 END) as OtherCount2
from docSVsys with (nolock)
group by caseID
having COUNT(*) > 1000
select GETDATE()
go;
select docSVsys.caseID, COUNT(*) as Ttl
, COUNT(primaryCount.sID) as priCount
, COUNT(*) - COUNT(primaryCount.sID) as otherCount
from docSVsys with (nolock)
left outer join docSVsys as primaryCount with (nolock)
on primaryCount.sID = docSVsys.sID
and primaryCount.mimeType = 'message/rfc822'
group by docSVsys.caseID
having COUNT(*) > 1000
select GETDATE()
go;

Related

Aggregate Function on an Expression Containing A Subquery

With the following t-sql query:
select u.userid
into #temp
from user u
where u.type = 1;
select top 50
contentid,
count(*) as all_views,
sum(case when hc.userid in (select userid from #temp) then 1 else 0 end) as first_count,
sum(case when hc.userid in (40615, 40616) then 1 else 0 end) as another_count
from hitcounts hc
inner join user u on u.userid = hc.userid
group by hc.contentid
order by count(*) desc;
I get an error message
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
However, if just include the column 'another_count' (with the hard-coded list of identifiers), everything works as I expected. Is there a way I should go about only getting the count for userids contained within a subquery? I plan to have multiple columns, each counting up a set/subquery of different userids.
Performance is not a concern at this point and I appreciate any guidance.
You don't need a temporary table for this purpose. Just use a conditional aggregation:
select top 50 contentid,
count(*) as all_views,
sum(case when u.type = 1 then 1 else 0 end) as first_count,
sum(case when hc.userid in (40615, 40616) then 1 else 0 end) as another_count
from hitcounts hc join
user u
on u.userid = hc.userid
group by hc.contentid
order by count(*) desc;

CASE or IF statement in WHERE clause

How can I use CASE statement or IF statement in WHERE clause ?
I am trying to apply a check on the basis of COUNT
SELECT * FROM sometable
WHERE CASE WHEN (SELECT COUNT(*) FROM sometable s WHERE SP = 2 AND sometable.id = s.id) > 2 THEN sometable.SP IS NOT NULL END
So basically if the count of rows is more than 1 it should apply IS NOT NULL condition else it should not.
Your logic suggests something like:
SELECT s.*
FROM (SELECT s.*,
SUM(CASE WHEN sp = 2 THEN 1 ELSE 0 END) OVER (PARTITION BY id) as cnt_2
FROM sometable s
) s
WHERE cnt_2 <= 2 OR s.sp is not null;
That seems equivalent. The logic doesn't seem particularly useful though.

SQL MAX value of two sub queries

I have two queries and I want to get the maximum value of the two of them.
MAX((SELECT COUNT(p.[ItemID]) FROM [dbo].[Table] p WHERE HasHuman=0),
(SELECT COUNT(p.[ItemID]) FROM [dbo].[Table] p WHERE HasHuman=1))
You can calculate both result in a single query and then apply TOP:
select top 1
HasHuman,
COUNT(p.[ItemID]) as cnt
from [dbo].[Table]
group by HasHuman
order by cnt desc
You could even do this in a single query:
SELECT
CASE WHEN SUM(CASE WHEN HasHuman=0 THEN 1 ELSE 0 END) >
SUM(CASE WHEN HasHuman=1 THEN 1 ELSE 0 END)
THEN SUM(CASE WHEN HasHuman=0 THEN 1 ELSE 0 END)
ELSE SUM(CASE WHEN HasHuman=1 THEN 1 ELSE 0 END) END
FROM [dbo].[Table]
WHERE ItemID IS NOT NULL -- you were not counting NULLs
SELECT MAX(RC)
FROM (SELECT COUNT(p.ItemID) AS RC FROM dbo.[Table]
WHERE HasHuman=0
UNION
SELECT COUNT(p.ItemID) AS RC FROM dbo.[Table]
WHERE HasHuman=1
) A

SQL Aggreate Functions

I have table which list a number of cases and assigned primary and secondary technicians. What I am trying to accomplish is to aggregate the number of cases a technician has worked as a primary and secondary tech. Should look something like this...
Technician Primary Secondary
John 4 3
Stacy 3 1
Michael 5 3
The table that I am pulling that data from looks like this:
CaseID, PrimaryTech, SecondaryTech, DOS
In the past I have used something like this, but now my superiors are asking for the number of secondary cases as well...
SELECT PrimaryTech, COUNT(CaseID) as Total
GROUP BY PrimaryTech
I've done a bit of searching, but cant seem to find the answer to my problem.
Select Tech,
sum(case when IsPrimary = 1 then 1 else 0 end) as PrimaryCount,
sum(case when IsPrimary = 0 then 1 else 0 end) as SecondaryCount
from
(
SELECT SecondaryTech as Tech, 0 as IsPrimary
FROM your_table
union all
SELECT PrimaryTech as Tech, 1 as IsPrimary
FROM your_table
) x
GROUP BY Tech
You can group two subqueries together with a FULL JOIN as demonstrated in this SQLFiddle.
SELECT Technician = COALESCE(pri.Technician, sec.Technician)
, PrimaryTech
, SecondaryTech
FROM
(SELECT Technician = PrimaryTech
, PrimaryTech = COUNT(*)
FROM Cases
WHERE PrimaryTech IS NOT NULL
GROUP BY PrimaryTech) pri
FULL JOIN
(SELECT Technician = SecondaryTech
, SecondaryTech = COUNT(*)
FROM Cases
WHERE SecondaryTech IS NOT NULL
GROUP BY SecondaryTech) sec
ON pri.Technician = sec.Technician
ORDER By Technician;
SELECT COALESCE(A.NAME, B.NAME) AS NAME, CASE WHEN A.CASES IS NOT NULL THEN A.CASES ELSE 0 END AS PRIMARY_CASES,
CASE WHEN B.CASES IS NOT NULL THEN B.CASES ELSE 0 END AS SECONDARY_CASES
FROM
(
SELECT COUNT(*) AS CASES, PRIMARYTECH AS NAME FROM YOUR_TABLE
GROUP BY PRIMARYTECH
) AS A
FULL OUTER JOIN
(
SELECT COUNT(*) AS CASES, SECONDARYTECH AS NAME FROM YOUR_TABLE
GROUP BY SECONDARYTECH
) AS B
ON A.NAME = B.NAME

SQL select and Group by

I have a table in SQL like this.
OrderID ItemID ItemPrice ItemType
1 A 100 1
1 B 50 1
1 C 10 0
2 A 100 1
2 F 60 0
3 G 10 0
So I want to get out put like this?
OrderID ItemPrice -Type= 1 ItemPrice -Type= 0
1 150 10
2 10 60
3 10
Do you have any idea about the SQL command to use?
I think it is group by order ID and Item type.
What you are doing is a pivot transformation. There are a few ways to do it, but my favorite way is using CASE inside SUM:
SELECT
OrderId,
SUM(CASE WHEN ItemType = 0 THEN ItemPrice ELSE 0 END) AS Type0_Price,
SUM(CASE WHEN ItemType = 1 THEN ItemPrice ELSE 0 END) AS Type1_Price
FROM MyTable
GROUP BY OrderId
This scales nicely if you have more than two types. All you have to do is add another SUM(...) line in your select list without having to change the rest of the query.
I think this will perform well, since the calculations in the SELECT list can be done without incurring additional row scans or lookups. That's the downside of self-joins and sub-selects.
Try this::
Select
DISTINCT(orderId),
(Select SUM(ITEMPRICE) from table where Itemtype=1 group by ORDERID) as ItemType1,
(Select SUM(ITEMPRICE) from table where Itemtype=0 group by ORDERID) as ItemType0
from table
Untested, but this should work for you.
SELECT t1.OrderID,
ItemPrice-Type1 = SUM(t1.ItemPrice),
ItemPrice-Type2 = SUM(t2.ItemPrice)
FROM TableName t1
INNER JOIN TableName t2 on t1.OrderID = t2.OrderID and t1.ItemID = t2.ItemID
WHERE t1.ItemType = 1 AND t2.ItemType = 0
GROUP BY t1.OrderID
Did this work?:
SELECT
OrderID,
SUM(ItemPrice*ItemType) Type1,
SUM(ItemPrice*(1-ItemType)) Type0
FROM
TableName
GROUP BY OrderID