T-SQL Aggregate function subquery - sql-server

I get the following error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
With this code:
SELECT
loc.Location
,COUNT(CASE
WHEN hr.SAC in (SELECT [SAC] FROM dbo.[Titles] WHERE [title] = 'XYZ')
THEN 1
ELSE NULL
END) AS XYZ_Trainee_Count
,COUNT(CASE
WHEN hr.SAC in (SELECT [SAC] FROM dbo.[Titles] WHERE [title] = 'ABC')
THEN 1
ELSE NULL
END) AS ABC_Trainee_Count
FROM
dbo.n_HRODS hr INNER JOIN dbo.Locations loc
ON loc.LocationID = hr.LocationID
INNER JOIN dbo.EmpData dat
ON dat.EmpID = hr.EmpID
WHERE dat.Trainee = 1
GROUP BY loc.Location
dbo.[Titles] is a view that combines two columns from two other tables. I'm basically doing it this way because the programmer before me did something like this:
,COUNT(CASE
WHEN SAC in ( lists about 30 items)
THEN 1
ELSE NULL
END)
Obviously, I don't want to list 30 items in that case statement... and when those items change for whatever reason in 3 years, then who's going to remember to go back in this code and updated those items? Nobody...
Thanks in advance for your help.

You can do this with a couple of extra LEFT OUTER JOINs to that title table:
SELECT
loc.Location
,COUNT(CASE WHEN titles1.[SAC] IS NOT NULL THEN 1 ELSE NULL END) AS XYZ_Trainee_Count
,COUNT(CASE WHEN titles2.[SAC] IS NOT NULL THEN 1 ELSE NULL END) AS ABC_Trainee_Count
FROM
dbo.n_HRODS hr INNER JOIN dbo.Locations loc
ON loc.LocationID = hr.LocationID
INNER JOIN dbo.EmpData dat
ON dat.EmpID = hr.EmpID
LEFT OUTER JOIN dbo.[Titles] titles1
ON titles1.[title]='XYZ' AND
hr.SAC = titles1.[SAC]
LEFT OUTER JOIN dbo.[Titles] titles2
ON titles2.[title]='ABC' AND
hr.sac = titles2.[SAC]
WHERE dat.Trainee = 1
GROUP BY loc.Location
Alternatively, if you are really married to those subqueries in your SELECT statement because the actual query is a big nightmare and the thought of monkeying with the joins is enough to make you faint, then you can just remove the aggregation from this query and shove it all into a subquery before aggregation:
SELECT location, count(XYZ_Trainee) AS XYZ_Trainee_Count, count(ABC_Trainee) as ABC_Trainee
FROM
(
SELECT
loc.Location
,CASE
WHEN hr.SAC in (SELECT [SAC] FROM dbo.[Titles] WHERE [title] = 'XYZ')
THEN 1
ELSE NULL
END AS XYZ_Trainee
,CASE
WHEN hr.SAC in (SELECT [SAC] FROM dbo.[Titles] WHERE [title] = 'ABC')
THEN 1
ELSE NULL
END AS ABC_Trainee
FROM
dbo.n_HRODS hr INNER JOIN dbo.Locations loc
ON loc.LocationID = hr.LocationID
INNER JOIN dbo.EmpData dat
ON dat.EmpID = hr.EmpID
WHERE dat.Trainee = 1
) sub
GROUP BY location
I would aim for the first solution though since it's going to be easier to maintain and probably get a better execution path from your RDBMS and run quicker as a result. Although... that's just a guess.

Very similar to JNevill first answer. But if you join with title once, you can check and count for any number of title you want.
SELECT
loc.Location
,COUNT(CASE WHEN t.[title] = 'XYZ' THEN 1 END) AS XYZ_Trainee_Count
,COUNT(CASE WHEN t.[title] = 'ABC' THEN 1 END) AS ABC_Trainee_Count
FROM
dbo.[n_HRODS] hr
INNER JOIN dbo.[Locations] loc
ON loc.LocationID = hr.LocationID
INNER JOIN dbo.[EmpData] dat
ON dat.EmpID = hr.EmpID
INNER JOIN dbo.[Titles] t
ON hr.SAC = t.[SAC]
WHERE dat.Trainee = 1
GROUP BY loc.Location

Related

How to use inner join and left join for two tables in same query using T-SQL

Given two tables:
tblBase - database table (code_no, empid)
#type_temp is a temporary user defined table which will come from vb.net frontend (code_no, name)
Firstly I need to get code_no which are in #type_temp and not in tblBase.
Query:
select
t.code_no, 'Non-existing' as Remark
from
#type_temp t
left join
tblBase b on t.code_no = b.code_no
where
b.code_no is null
Next I need to get all code_no that have empid = 1.
Query :
select
t.code_no, 'Existing' as Remark
from
#type_temp t
inner join
tblBase b on t.code_no = b.code_no
where
b.empid = 1
I need to use both these queries together.
Currently I'm using union to club the two.
select
t.code_no, 'Non-existing' as Remark
from
#type_temp t
left join
tblBase b on t.code_no = b.code_no
where
b.code_no is null
union
select
t.code_no, 'Existing' as Remark
from
#type_temp t
inner join
tblBase b on t.code_no = b.code_no
where
b.empid = 1
I do not want union here. Any other alternative possible?
Does this work for you?
SELECT type_temp.code_no
, CASE WHEN tblBase.code_no IS NULL THEN 'Non-existing' ELSE 'Existing' END AS Remark
FROM #type_temp AS type_temp
LEFT
JOIN tblBase
ON tblBase.code_no = type_temp.code_no
WHERE tblBase.empid = 1
OR tblBase.code_no IS NULL
;
An even better option than #gvee's answer, is to put the conditions into the ON clause
SELECT
type_temp.code_no,
CASE WHEN tblBase.code_no IS NULL THEN 'Non-existing' ELSE 'Existing' END AS Remark
FROM #type_temp AS type_temp
LEFT JOIN tblBase ON tblBase.code_no = type_temp.code_no
AND tblBase.empid = 1;
This is likely to be more performant, as the join is pre-filtered.

How to divide two queries and then group by?

I need to divide two queries, but I need to save 'group by' categories. With my query I only get values and their cartesian product.
Select m2.regionname, m2.indicatorname CAST( m2.a2Value as float) /
m1.a1Value
from(
select r.name as regionname , ina.name as indicatorname, sum(a.value) as
a1Value
from Region as "r"
left join city_region as "cr" on r.region_id = cr.region_id
left join Office as "o" on cr.city_id = o.city_id
left join Assets as "a" on o.office_id = a.office_id
left join Indicators as "i" on a.indicator_id = i.indicator_id
left join IndicatorNames as "ina" on i.indicator_name_id =
ina.indicator__name_id
where a.month between '01-01-2019' and '31-01-2019'
group by r.name, ina.name
) m1 join (
select r.name as regionname , ina.name as indicatorname, sum(a.value) as
a2Value
from Region as "r"
left join city_region as "cr" on r.region_id = cr.region_id
left join Office as "o" on cr.city_id = o.city_id
left join Assets as "a" on o.office_id = a.office_id
left join Indicators as "i" on a.indicator_id = i.indicator_id
left join IndicatorNames as "ina" on i.indicator_name_id =
ina.indicator__name_id
where a.month between '01-02-2019' and '27-02-2019'
group by r.name, ina.name) m2 on m1.regionname = m2.regionname
I need to get 4 rows and 3 columns, that includes region_name, indicator_name and float value.
But I only cant get table with values
0,0482248520710059
0,0565972222222222
0,0665680473372781
0,078125
0,705627705627706
0,974025974025974
1,01875
1,03550295857988
1,18343195266272
1,21527777777778
1,38888888888889
1,40625
15,1515151515152
17,3160173160173
21,875
25
but that is wrong.
This condition in the ON clause:
on m1.regionname = m2.regionname
will join many unrelated rows.
You must set another condition like:
on m1.regionname = m2.regionname and m1.indicatorname = m2.indicatorname
try Something like this:
select *, case when a1Value=0 then null else cast(a2Value as float) / a1Value end Ratio
from (
select r.name as regionname , ina.name as indicatorname,
sum(case when a.month between '01-01-2019' and '31-01-2019' then a.value else 0 end) as a1Value,
sum(case when a.month between '01-02-2019' and '27-02-2019' then a.value else 0 end) as a2Value
from Region r
left join city_region cr on r.region_id = cr.region_id
left join Office o on cr.city_id = o.city_id
left join Assets a on o.office_id = a.office_id and a.month between '01-01-2019' and '27-02-2019'
left join Indicators i on a.indicator_id = i.indicator_id
left join IndicatorNames ina on i.indicator_name_id = ina.indicator__name_id
group by r.name, ina.name
) tmp

How to use multiple values in between clause

Hi all is there any way that i can use multiple values in between clause as
column_name between 0 and 100 or 200 and 300 like this
Any help would be appreciated
here is my query SELECT CASE WHEN ISNUMERIC(value_text) = 1 THEN CAST(value_text AS INT) ELSE -1 END) between 0 and 100
i just want to append multiple values in between clause
This is full query
SELECT ROW_NUMBER() OVER
(
order by Vendor_PrimaryInfo.Vendor_ID asc
)AS RowNumber
, Unit_Table.Unit_title, Vendor_Base_Price.Base_Price, Vendor_Base_Price.showprice, Category_Table.Title, Vendor_Registration.Business_Name,
Vendor_PrimaryInfo.Street_Address, Vendor_PrimaryInfo.Locality, Vendor_PrimaryInfo.Nearest_Landmark, Vendor_PrimaryInfo.City, Vendor_PrimaryInfo.State,
Vendor_PrimaryInfo.Country, Vendor_PrimaryInfo.PostalCode, Vendor_PrimaryInfo.Latitude, Vendor_PrimaryInfo.Longitude, Vendor_PrimaryInfo.ImageUrl,
Vendor_PrimaryInfo.ContactNo, Vendor_PrimaryInfo.Email,Vendor_PrimaryInfo.Vendor_ID
FROM Unit_Table INNER JOIN
Vendor_Base_Price ON Unit_Table.Unit_ID = Vendor_Base_Price.Unit_ID INNER JOIN
Vendor_PrimaryInfo ON Vendor_Base_Price.Vendor_ID = Vendor_PrimaryInfo.Vendor_ID INNER JOIN
Vendor_Registration ON Vendor_Base_Price.Vendor_ID = Vendor_Registration.Vendor_ID AND
Vendor_PrimaryInfo.Vendor_ID = Vendor_Registration.Vendor_ID INNER JOIN
Category_Table ON Vendor_Registration.Category_ID = Category_Table.Category_ID
LEFT JOIN
Vendor_Value_Table ON Vendor_Registration.Vendor_ID = Vendor_Value_Table.Vendor_ID LEFT JOIN
Feature_Table ON Vendor_Value_Table.Feature_ID = Feature_Table.Feature_ID
where Vendor_Registration.Category_ID=5 and Vendor_PrimaryInfo.City='City'
AND(
value_text in('Dhol Wala$Shahnai Wala')
or
(SELECT CASE WHEN ISNUMERIC(value_text) = 1 THEN CAST(value_text AS INT) ELSE -1 END) between 0 and 100
)
You can do this using AND/OR logic
value_text NOT LIKE '%[^0-9]%' and
(
value_text between 0 and 100
Or
value_text between 101 and 200
)
If you don't want to repeat the column name then frame the range in table valued constructor and join with your table
SELECT Row_number()
OVER (
ORDER BY Vendor_PrimaryInfo.Vendor_ID ASC )AS RowNumber,
Unit_Table.Unit_title,
Vendor_Base_Price.Base_Price,
Vendor_Base_Price.showprice,
Category_Table.Title,
Vendor_Registration.Business_Name,
Vendor_PrimaryInfo.Street_Address,
Vendor_PrimaryInfo.Locality,
Vendor_PrimaryInfo.Nearest_Landmark,
Vendor_PrimaryInfo.City,
Vendor_PrimaryInfo.State,
Vendor_PrimaryInfo.Country,
Vendor_PrimaryInfo.PostalCode,
Vendor_PrimaryInfo.Latitude,
Vendor_PrimaryInfo.Longitude,
Vendor_PrimaryInfo.ImageUrl,
Vendor_PrimaryInfo.ContactNo,
Vendor_PrimaryInfo.Email,
Vendor_PrimaryInfo.Vendor_ID
FROM Unit_Table
INNER JOIN Vendor_Base_Price
ON Unit_Table.Unit_ID = Vendor_Base_Price.Unit_ID
INNER JOIN Vendor_PrimaryInfo
ON Vendor_Base_Price.Vendor_ID = Vendor_PrimaryInfo.Vendor_ID
INNER JOIN Vendor_Registration
ON Vendor_Base_Price.Vendor_ID = Vendor_Registration.Vendor_ID
AND Vendor_PrimaryInfo.Vendor_ID = Vendor_Registration.Vendor_ID
INNER JOIN Category_Table
ON Vendor_Registration.Category_ID = Category_Table.Category_ID
LEFT JOIN Vendor_Value_Table
ON Vendor_Registration.Vendor_ID = Vendor_Value_Table.Vendor_ID
LEFT JOIN Feature_Table
ON Vendor_Value_Table.Feature_ID = Feature_Table.Feature_ID
JOIN (VALUES (0, 100),
(101, 200),
(201, 300)) tc (st, ed)
ON Try_cast(value_text AS INT) BETWEEN st AND ed
OR Try_cast(value_text AS VARCHAR(100)) = 'Dhol Wala$Shahnai Wala'
WHERE Vendor_Registration.Category_ID = 5
AND Vendor_PrimaryInfo.City = 'City'
Note : You have stored two different information's in a single column which causes lot of pain when you want to extract the data like this. Consider changing your table structure

Get distinct rows when filtering and using dynamic order by

My first version of the question was confusing, I need to make smaller chunks.
If a user can filter products from a website, one product should occur only once in the list.
Because of joins this code gives me two same products, how do I solve that?
I think I need a solution without using distinct because it will give me headache later on.
code from AW2012:
declare #safetystocklevel int
set #safetystocklevel = 1000
declare #status int
set #status = 2
select * from Production.Product p
inner join Purchasing.ProductVendor pv on p.ProductID = pv.ProductID
inner join Purchasing.Vendor v on v.BusinessEntityID = pv.BusinessEntityID
inner join Production.ProductDocument pd on p.ProductID = pd.ProductID
inner join Production.Document d on d.DocumentNode = pd.DocumentNode
WHERE
(#safetystocklevel = '' or p.SafetyStockLevel = #safetystocklevel)
and (#status = '' or d.Status = #status)
output:
ProductId Name
506 Reflector
506 Reflector
Edit:
Thanks, I now use Group by to get distinct rows.
Yeah, maybe using group by works for me, Im gonna do some testing now.....
Hi again
I want all products to be searchable, so I guess I need left outer joins to achieve that.
When I add dynamic order by I get into trouble, more rows are added.
Probably because I must add poh.Status to the group by.
There are 504 rows in the product table, this query returns 776 rows.
(I have removed the filtering in WHERE since it is not interesting now, and Im joining to other tables now just to get more rows to play with)
Code:
declare #sortType nvarchar(50)
set #sortType = 'Status'
select p.ProductID,
CASE WHEN #sortType = 'Status' THEN poh.Status END as Status,
CASE WHEN #sortType = 'ProductId' THEN p.ProductID END as ProductId
from Production.Product p
left outer join Purchasing.PurchaseOrderDetail pod on p.ProductID = pod.ProductID
left outer join Purchasing.PurchaseOrderHeader poh on poh.PurchaseOrderID = pod.PurchaseOrderID
left outer join Production.ProductDocument ppd on ppd.ProductID = p.ProductID
left outer join Production.Document pd on pd.DocumentNode = ppd.DocumentNode
group by p.ProductID, poh.Status
ORDER BY
CASE WHEN #sortType = 'Status' THEN poh.Status END ASC,
CASE WHEN #sortType = 'ProductId' THEN p.ProductID END ASC
You can use Group By ProductId, Name, to select the single row, if you are not planning to include distinct. But I'll prefer "distinct" if you are not using any aggregate value in select clause.
select p.ProductId, p.Name from Production.Product p
inner join Purchasing.ProductVendor pv on p.ProductID = pv.ProductID
inner join Purchasing.Vendor v on v.BusinessEntityID = pv.BusinessEntityID
inner join Production.ProductDocument pd on p.ProductID = pd.ProductID
inner join Production.Document d on d.DocumentNode = pd.DocumentNode
WHERE
(#safetystocklevel = '' or p.SafetyStockLevel = #safetystocklevel)
and (#status = '' or d.Status = #status)
GROUP BY p.ProductId, p.Name

LEFT JOIN EXISTS

I have two tables, Main and Details - they have a one-to-many relationship, with one row in Main potentially having multiple rows in Details.
I am trying to create a query that returns all the information from Main, plus the whether the associated rows in Details contain one of a set of codes. Of course, since it's a one-to-many, there may be several of those codes present in Details - and I don't want to double count rows. I need to do it a couple of times over, too.
What I kind of want is something like this:
SELECT m.*, CASE WHEN x.ID IS NOT NULL THEN 1 ELSE 0 END AS Codes1, CASE WHEN y.ID IS NOT NULL THEN 1 ELSE 0 END AS Codes2
FROM [Main] m
LEFT JOIN EXISTS(SELECT d.ID FROM [Details] d WHERE m.ID = d.ID AND d.Code IN (<<Codes1>>)) x
LEFT JOIN EXISTS(SELECT d.ID FROM [Details] d WHERE m.ID = d.ID AND d.Code IN (<<Codes2>>)) y
Is there some way to do this? (This seems something that should be obvious and I'm overcomplicating to the nth degree, but I'm genuinely drawing a blank...
Remove the EXISTS from your query and add a DISTINCT
SELECT DISTINCT
m.*,
CASE WHEN x.ID IS NOT NULL THEN 1 ELSE 0 END AS Codes1,
CASE WHEN y.ID IS NOT NULL THEN 1 ELSE 0 END AS Codes2
FROM [Main] m
LEFT JOIN (SELECT d.ID FROM [Details] d WHERE m.ID = d.ID AND d.Code IN (<<Codes1>>)) x
LEFT JOIN (SELECT d.ID FROM [Details] d WHERE m.ID = d.ID AND d.Code IN (<<Codes2>>)) y
I think this will work:
SELECT M.*,
CASE WHEN C1.Codes IS NULL THEN 0 ELSE 1 END as Codes1,
CASE WHEN C2.Codes IS NULL THEN 0 ELSE 1 END as Codes2
FROM Main M
LEFT
JOIN
(
SELECT d.Id,
COUNT(d.Code) as Codes
FROM Details d
WHERE d.Code in (<<Codes1>>)
GROUP
BY d.Id
) C1
ON C1.Id = M.Id
LEFT
JOIN
(
SELECT d.Id,
COUNT(d.Code) as Codes
FROM Details d
WHERE d.Code in (<<Codes2>>)
GROUP
BY d.Id
) C2
ON C2.Id = M.Id
If you don't need the M.* then this might be better:
SELECT M.Id,
SUM(CASE WHEN D1.Id IS NULL THEN 0 ELSE 1 END) AS Codes1Count,
SUM(CASE WHEN D2.Id IS NULL THEN 0 ELSE 1 END) AS Codes2Count
FROM Main M
LEFT
JOIN Details D1
ON D1.Id = M.Id
AND D1.Code in (<<Codes1>>)
LEFT
JOIN Details D2
ON D2.Id = M.Id
AND D2.Code in (<<Codes2>>)
GROUP
BY M.Id
Here's a slim version of code that will get you what you want
SELECT abb1.*, abb2.mycount
FROM Main AS abb1
JOIN (SELECT Main.ID, COUNT(Details.ID) AS mycount
FROM Main
JOIN Details on Details.ID = Main.ID
GROUP BY Main.ID) AS abb2

Resources