SQL Server : multiple Select queries - sql-server

I am trying to show sum(grand_total) where is_loyal = 1 and sum(grand_total) where is_loyal = 0 together in the same result screen (grand_total column is in the same table for both). I have tried subqueries, join and case when but no luck so far. Is there a way to calculate and show the results together?
The only result I can find by subqueries is below, no matter how I change where clause gives me the same data. If I change my query a bit it throws error message
Msg 512, Level 16, State 1, Line 155
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Result

With conditional aggregation:
select date,
sum(case when loyal = 1 then grand_total end) [Total Loyalty],
sum(case when loyal = 0 then grand_total end) [Total Spent non-loyalty]
from tablename
group by date

use case when like this:
select
sum(case when loyal = 1 then 1 else 0 end) as grand_total_Loyal,
sum(case when loyal = 0 then 1 else 0 end) as grand_total_noneloyal

A simple method uses arithmetic:
select sum(loyal * grandtotal) as loyal_grandtotal,
sum( (1 - loyal) * grandtotal) as notloyal_grandtotal
from t
where loyal in (1, 0);
The more general solution is using a case expression. However, if you have 0/1 flags they fit very easily into arithmetic calculations -- on reason why they are preferred over, say, string flags.

Related

Querying Ratio as a percentage

I am trying to first count characters in the columns and than dividing them to create a percentage ratio column. I get errors and not sure what is causing the issue.
[Report_Graded] is boolean
[Test_Status] is text with (4) options. "Accepted, Completed, Declined, Scheduled"
SELECT Name, Report_Graded, Test_Status,
SUM(CASE WHEN Report_Graded= 'Completed' THEN 1 ELSE 0 END) / SUM(CASE WHEN Test_Status= 'Declined'OR 'Scheduled' THEN 0 ELSE 1 END) * 100 AS "Ratio_Graded"
FROM [database]
WHERE Date_Test BETWEEN '01/01/2021' AND '06/30/2021'
GROUP BY Name
ORDER BY "Ratio_Graded" DESC
WHEN Test_Status= 'Declined'OR 'Scheduled'
is not syntactically correct. You must write
WHEN Test_Status= 'Declined'OR Test_Status= 'Scheduled'
To run the query.
Also beware of O divide. Add a COALESCE.
The query rewrited :
SELECT Name,
Report_Graded,
Test_Status,
SUM(CASE
WHEN Report_Graded = 'Completed'
THEN 1
ELSE 0
END) / COALESCE(SUM(CASE
WHEN Test_Status = 'Declined'
OR Test_Status = 'Scheduled'
THEN 0
ELSE 1
END) * 100 AS "Ratio_Graded"
FROM [database]
WHERE Date_Test BETWEEN '01/01/2021' AND '06/30/2021'
GROUP BY Name
ORDER BY "Ratio_Graded" DESC;
Also date/time format can vary from the session culture. It is why it is recommended to use the ISO SQL Standard :
AAAAMMJJ hh:mm:ss.nnn for DATETIME (call ISO short)
AAAA-MM-JJ hh:mm:ss.nnn for DATETIME2, DATETIMEOFFSET (call ISO
long)
AAAA-MM-JJ for DATE (call ISO long)

Subquery returned more than 1 value with Iif conditions

select *
from employee
where controllingoffice in (
Iif(1=1, (select id from controllingoffice), (select id from controllingoffice where OwnerId=4))
)
Msg 512, Level 16, State 1, Line 1
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Note: Both subquery return more than 1 row
The reason your query fails is because your subqueries return multiple values while the iif() function is expecting scalar results for its arguments. Because iif() is a function, it evaluates to a single value and not a set of rows. You can't use it to branch as you would in a procedural language so you can't make the execution of the subqueries conditional in that sense.
The subquery below allows you to express the driving condition once in one place. That can be handy if this uses dynamic SQL. I'm presuming that your 1 = 1 is a placeholder for that input. The switching condition determines whether all rows are returned or just the ones for the single OwnerId:
select *
from employee
where controllingoffice in (
select id from controllingoffice where
<switch condition> or OwnerId = 4
);
By the way the logic X or (~X)Y reduces to X or Y
An approximation to what you intended to happen would look something like this:
select *
from employee
where
1 = 1 and controllingoffice in (
select id from controllingoffice
)
or 0 = 1 and controllingoffice in (
select id from controllingoffice where OwnerId = 4
);

CAN THE COUNT FUNCTION IN SQL SERVER DB ACCEPT MULTIPLE VALUES

I am querying the very popular AdventureWorks DB in SSMS.
My objective to find the number of males and females under each job title from HumanResources.Employee.
For this my original query was,
SELECT JobTitle,
COUNT(CASE WHEN Gender='M' THEN 1
ELSE 0
END) AS MALE_COUNT,
COUNT(CASE WHEN Gender='F' THEN 1
ELSE 0
END) AS FEMALE_COUNT,Gender
FROM HumanResources.Employee
GROUP BY JobTitle,Gender
ORDER BY JobTitle
GO
However, I am getting incorrect answer with the above query.So by modifying it as below ,I am getting the desired result:
SELECT JobTitle,
COUNT(CASE WHEN Gender='M' THEN 1
END) AS MALE_COUNT,
COUNT(CASE WHEN Gender='F' THEN 1
END) AS FEMALE_COUNT,Gender
FROM HumanResources.Employee
GROUP BY JobTitle,Gender
ORDER BY JobTitle
GO
As can be easily seen, I am just removing the 'ELSE 0' condition for both the CASE statements, but am I unable to figure out as to how '0' is affecting the values returned in the result.
Can someone explain to me the difference between these two? Also I would like to know how the COUNT function is taking multiple values, when normally(say SELECT COUNT(3,3)) it doesn't work.
You want SUM(), not COUNT(): the latter takes in account every non-null value (this includes 0), so your current conditional expressions counts all rows - it is equivalent to COUNT(*)
Also, I suspect that gender should probably not appear in the SELECT and GROUP BY clauses, since that's precisely what you are trying to aggregate
I think that you want:
SELECT
JobTitle,
SUM(CASE WHEN Gender='M' THEN 1 ELSE 0 END) AS MALE_COUNT,
SUM(CASE WHEN Gender='F' THEN 1 ELSE 0 END) AS FEMALE_COUNT
FROM HumanResources.Employee
GROUP BY JobTitle
ORDER BY JobTitle
Documentations say:
COUNT(ALL expression) evaluates expression for each row in a group,
and returns the number of nonnull values.
Read more here: https://learn.microsoft.com/en-us/sql/t-sql/functions/count-transact-sql?view=sql-server-ver15
So we can rewrite your query as:
SELECT
JobTitle,
COUNT(CASE WHEN Gender='M' THEN 1 ELSE NULL END) AS MALE_COUNT,
COUNT(CASE WHEN Gender='F' THEN 1 ELSE NULL END) AS FEMALE_COUNT
FROM HumanResources.Employee
GROUP BY JobTitle
ORDER BY JobTitle
About your queries:
First query:
inside Count function you have a CASE expression which will change the 'M' to 1 and 'F' to 0. Then COUNT function will do the count operations over them. because neither 1 nor 0 are NULL so Count will return total number of records, regardless of 'M' or 'F'
This procedure is same for second CASE too.
Second query:
Inside Count function you have a CASE expression which will change 'M' to 1, but you did not mentioned what to do with other values, so NULL will be returned for non-M values. After that Count function will do the count operations on these records and will return the number of M's.(Second query is equal to the query I have posted, and they both will have the same output. However because of readability I prefer my query over yours :-) )
This procedure is same for second CASE too.
Documentations for CASE expression says:
ELSE else_result_expression Is the expression returned if no
comparison operation evaluates to TRUE. If this argument is omitted
and no comparison operation evaluates to TRUE, CASE returns NULL.
else_result_expression is any valid expression. The data types of
else_result_expression and any result_expression must be the same or
must be an implicit conversion.
Read more here: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/case-transact-sql?view=sql-server-ver15
SELECT Count(3,3)
This is syntactically wrong and will give you an error like this, which is pretty self explanatory:
Msg 174, Level 15, State 1, Line 1 The Count function requires 1
argument(s)
The syntax for Count function based on the aforementioned documentations is like this:
-- Aggregation Function Syntax
COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )
-- Analytic Function Syntax
COUNT ( [ ALL ] { expression | * } ) OVER ( [ <partition_by_clause> ] )

Same column multiple times in query

Question for SQL Server experts. In the below query I would like to have an additional column which also SUMs Quantity but does so based on a different Requirement Type. I have tried a few ideas - CASE and adding a subquery in the select list but all return far too many results. What I would like to see is MATERIAL, MATERIAL_DESCRIPTION,SIZE_LITERAL,SUM OF QUANTITY WHEN REQUIREMENT TYPE = 'PB', SUM OF QUANTITY WHEN REQUIREMENT TYPE = '01' Not sure how to add the quantity twice on two different conditions. Thanks in advance
SELECT MATERIAL,
MATERIAL_DESCRIPTION,
SIZE_LITERAL,
SUM(QUANTITY) AS 'SUM_FCST'
FROM VW_MRP_ALLOCATION
WHERE REQUIREMENT_CATEGORY = 'A60381002'
AND MATERIAL_AVAILABILITY_DATE >= GETDATE()
AND REQUIREMENT_TYPE ='PB'
GROUP BY MATERIAL,
MATERIAL_DESCRIPTION,
SIZE_LITERAL
ORDER BY MATERIAL,
SIZE_LITERAL
You would use a CASE expression inside of the SUM(). This is conditional aggregation:
SELECT MATERIAL,
MATERIAL_DESCRIPTION,
SIZE_LITERAL,
SUM(case when REQUIREMENT_TYPE ='PB' then QUANTITY else 0 end) AS 'SUM_FCST_PB',
SUM(case when REQUIREMENT_TYPE ='01' then QUANTITY else 0 end) AS 'SUM_FCST_01'
FROM VW_MRP_ALLOCATION
WHERE REQUIREMENT_CATEGORY = 'A60381002'
AND MATERIAL_AVAILABILITY_DATE >= GETDATE()
GROUP BY MATERIAL,
MATERIAL_DESCRIPTION,
SIZE_LITERAL
ORDER BY MATERIAL,
SIZE_LITERAL
You can use case inside sum as below:
SELECT MATERIAL,
MATERIAL_DESCRIPTION,
SIZE_LITERAL,
SUM(CASE WHEN [Requirement_Type] = 'PB' then QUANTITY else 0 end) AS 'SUM_FCST'
FROM VW_MRP_ALLOCATION
WHERE REQUIREMENT_CATEGORY = 'A60381002'
AND MATERIAL_AVAILABILITY_DATE >= GETDATE()
AND REQUIREMENT_TYPE ='PB'
GROUP BY MATERIAL,
MATERIAL_DESCRIPTION,
SIZE_LITERAL
ORDER BY MATERIAL,
SIZE_LITERAL

Aggregate Function Error on an Expression

What could be wrong with this query:
SELECT
SUM(CASE
WHEN (SELECT TOP 1 ISNULL(StartDate,'01-01-1900')
FROM TestingTable
ORDER BY StartDate Asc) <> '01-01-1900' THEN 1 ELSE 0 END) AS Testingvalue.
The get the error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
As koppinjo stated what your current (broken) query is doing is checking if you have a NULL-value (or StartDate = '01-01-1900') in your table, return either a 1 or a 0 depending on which, and then attempting to SUM that single value.
There are 2 different logical things you want.
Either getting the amount of rows that has a StartDate or checking if any row is missing StartDate.
SELECT --Checking if there is a NULL-value in table
(
CASE WHEN
(SELECT TOP 1 ISNULL(StartDate,'01-01-1900')
FROM TestingTable
ORDER BY StartDate Asc) <> '01-01-1900' THEN 1
ELSE 0
END
) AS TestingValue
SELECT SUM(TestingValue) TestingValue --Give the count of how many non-NULLs there is
FROM
(
SELECT
CASE WHEN
ISNULL(StartDate,'01-01-1900') <> '01-01-1900' THEN 1
ELSE 0
END AS TestingValue
FROM TestingTable
) T
Here is a SQL Fiddle showing both outputs side by side.
Hard to say, but you probably want something like this:
SELECT
SUM(TestingValue)
FROM
(SELECT
CASE
WHEN ISNULL(StartDate,'01-01-1900') <> '01-01-1900'
THEN 1
ELSE 0
END AS TestingValue
FROM TestingTable) t
As your original query is written now, your subquery will return 1 value overall, so your sum would be 1 or 0 always, not to mention it is illegal. To get around that, this SQL will apply the case statement to every row in the TestingTable and insert the result into a derived table (t), then the 'outer' select will sum the results. Hope this helps!

Resources