sql case in column and where section without repeating the case - sql

I have a sql statement like this but with a very long case expression in it and I need it in the where as well as in the column. Is there a way to use the case without repeating it in the column and where section.
select * from c.name, (case when c.age = 1 then 0 when c.age = 2 then 1 ... etc else -1 end;)
from customer c
where (case when c.age = 1 then 0 when c.age = 2 then 1 ... etc else -1 end;) > 0

Typically you would use a subquery to get around this problem:
SELECT *
FROM
(
SELECT c.name,
CASE WHEN c.age = 1 THEN 0 WHEN c.age = 2 THEN 1 ELSE -1 END AS val
FROM customer c
INNER JOIN employee e ON c.emp_id = e.id
) t
WHERE val > 0;
But in practice if you had a list of age values which get mapped to positive values, you could just use a single level query with a WHERE clause:
SELECT c.name,
CASE WHEN c.age = 1 THEN 0 WHEN c.age = 2 THEN 1 ELSE -1 END AS val
FROM customer c
WHERE age IN (2, ...);

A simple way of consolidating would be to use a lateral join
select *
from customer c,
lateral (values(case when c.age = 1 then 0 when c.age = 2 then 1 ... etc else -1 end))v(AgeCheck)
where AgeCheck > 0;

Related

In a nested query, check all values for a condition

How can I get rid of nested queries (agree, dis_agreed)? How to rewrite to join - I can not think of it. Maybe there are other optimal solutions?
select *
from (
select
(select count(id) from Agreement a where a.ConclusionCardFile = f.id and a.Status = 1) agreed,
(select count(id) from Agreement a where a.ConclusionCardFile = f.id and (a.Status <> 1 or a.Status is null)) dis_agreed
from ConclusionCard_Files f
) t
where t.agreed > 0 and dis_agreed = 0
You can write the conditions as a where clause:
select *
from conclusionCard_Files
where exists (
select *
from agreement
where agreement.conclusionCardFile = conclusionCard_Files.id
having sum(case when status = 1 then 1 else 0 end) > 0
and sum(case when status = 1 then 0 else 1 end) = 0
)
Maybe you just using sub-queries only to filter? What about to move them to WHERE clause?
SELECT
*
FROM ConclusionCard_Files f
WHERE
EXISTS(select * from Agreement a where (a.ConclusionCardFile = f.id) and a.Status =1)
AND NOT EXISTS(select * from Agreement a where (a.ConclusionCardFile = f.id) and (a.Status != 1 or a.Status is null))
It's performance friendly because SqlServer do not count all Counts
If I understand correctly, you can try to use JOIN with HAVING condition aggregate function.
SELECT COUNT(CASE WHEN a.Status = 1 THEN ID END) agreed,
COUNT(CASE WHEN a.Status <> 1 or a.Status is null THEN ID END) dis_agreed
FROM Agreement a
INNER JOIN ConclusionCard_Files f
ON a.ConclusionCardFile = f.id
HAVING
COUNT(CASE WHEN a.Status = 1 THEN ID END) > 0
AND
COUNT(CASE WHEN a.Status <> 1 or a.Status is null THEN ID END) = 0
EDIT
if you want to get data from ConclusionCard_Files based on your condition. you can try to let condition aggregate function in subquery each ConclusionCardFile from table Agreement then do JOIN
SELECT f.*
FROM (
SELECT COUNT(CASE WHEN a.Status = 1 THEN ID END) agreed,
COUNT(CASE WHEN a.Status <> 1 or a.Status is null THEN ID END) dis_agreed,
a.ConclusionCardFile
FROM Agreement a
GROUP BY a.ConclusionCardFile
) a
INNER JOIN ConclusionCard_Files f
ON a.ConclusionCardFile = f.id
WHERE a.agreed > 0 AND a.dis_agreed = 0

SELECT statement with operators not working

I have these two tables and I need a query, that outputs every member that has the lvnr 050056 AND NOT 050054.
I have these two tables
I have tried it with the following query but it does not work right:
SELECT s.matrnr, s.vorname, s.nachname
FROM student s
INNER JOIN teilgenommen t ON s.matrnr = t.matrnr
WHERE (t.lvnr = 050056) AND (t.lvnr != 050054)
Only Martin Huber with the ID 0111111 should be shown, but I get both..
I would be very thankful for any advie
Use exists and not exists:
select s.*
from student s
where exists (select 1
from teilgenommen t
where t.matrnr = s.matrnr and t.lvnr = '050056'
) and
not exists (select 1
from teilgenommen t
where t.matrnr = s.matrnr and t.lvnr = '050054'
);
The leading zeros suggest that lvnr is really stored as a string. If so, then single quotes should be used for the comparison value.
You can do:
SELECT
s.matrnr,
s.vorname,
s.nachname
FROM
student s
INNER JOIN
(
select
matrnr,
max(case when lvnr='050056' then 1 else 0 end) as a,
max(case when lvnr='050054' then 1 else 0 end) as b
from
teilgenommen
group by
matrnr
having a=1 and b=0
) t
ON s.matrnr = t.matrnr
This can also be solved with aggregation and a having clause for filtering:
select s.matrnr, s.vorname, s.nachname
from student s
inner join teilgenommen t on s.matrnr = t.matrnr
group by s.matrnr, s.vorname, s.nachname
having
max(case when t.lvnr = 050056 then 1 else 0 end) = 1
and max(case when t.lvnr = 050054 then 1 else 0 end) = 0

Join table on conditions, count on conditions

SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
This creates an SQL object for displaying products on a page. The first request grabs items of type datelive = -1, the second of type datelive != -1 but r.count(*) >= 5, and the third of type datelive != -1 and r.count(*) < 5. The reviews table is structured similar to the below:
reviewID | productID | a | b | c | d | approved
-------------------------------------------------
1 1 5 4 5 5 1
2 5 3 2 5 5 0
3 2 5 5 4 3 1
... ... ... ... ... ... ...
I'm trying to work it such that r.count(*) only cares for rows of type approved = 1, since tallying data based on unapproved reviews isn't ideal. How can I join these tables such that the summations of scores and the number of rows is dependent only on approved = 1?
I've tried adding in AND r.approved = 1 in the WHERE conditional for the joins and it doesn't do what I'd like. It does sort it properly, but then it no longer includes items with zero reviews.
You seem to be nearly there.
In your question you talked about adding the AND r.approved = 1 to the join criteria but by the sounds of it you are actually adding it to the WHERE clause.
If you instead properly add it to the join criteria like below then it should work fine:
SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID AND r.approved = 1
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
SQL Fiddle here.
Notice again how I have simply put the AND r.approved = 1 directly after LEFT JOIN reviews r ON r.productID = e.productID which adds an extra criteria to the join.
As I mentioned in my comment, the WHERE clause will filter rows out of the combined record set after the join has been made. In some cases the RDBMS may optimise it out and put it into the join criteria but only where that would make no difference to the result set.
Calculating the non-zero sums and joining it to your result may solve it;
fiddle
SELECT a.productID,
NULL AS score,
'0' AS SortOrder
FROM products a
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) >= 5
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) < 5
ORDER BY SortOrder ASC,
score DESC
You could create a temp table that only contains rows where approved = 1, and then join on the temp table instead of reviews.
create table tt_reviews like reviews;
insert into tt_reviews
select * from reviews
where approved = 1;
alter table tt_reviews add index(productID);
Then replace reviews with tt_reviews in your above query.

How can I add a sub-query to this cursor?

I tried to add a sub query (StoCount) to the following cursor:
DECLARE trans_cur CURSOR FOR
SELECT b.TransportNumber,
SUM(CASE WHEN a.DeliveryItemStatus = 'C' OR a.DeliveryItemStatus = 'V' THEN 1 ELSE 0 END) AS Completed,
COUNT(*) AS Total,
SUM(CASE WHEN a.DeliveryItemStatus = 'F' THEN 1 ELSE 0 END) AS Missing,
(SELECT COUNT(*) FROM StorageTransportOrderItem WHERE DeliveryNumber = a.DeliveryNumber AND DeliveryItemNumber = a.DeliveryItemNumber) As StoCount
FROM DeliveryItem a
INNER JOIN TransportItem b on a.DeliveryNumber = b.DeliveryNumber
INNER JOIN Material c on a.MaterialNumber = c.MaterialNumber
INNER JOIN Transport d on b.TransportNumber = d.TransportNumber
WHERE a.StorageLocationNumber IS NOT NULL
AND a.Deleted <> 1
AND c.CommissioningArea LIKE #commissioningArea
AND d.TransportStatus < 70
GROUP BY b.TransportNumber
but when I always get the error message:
Msg 8120, Level 16, State 1, Procedure sp_CalculateTransportProgress,
Line 41 Column 'DeliveryItem.DeliveryNumber' is invalid in the select
list because it is not contained in either an aggregate function or
the GROUP BY clause. Msg 8120, Level 16, State 1, Procedure
sp_CalculateTransportProgress, Line 41 Column
'DeliveryItem.DeliveryItemNumber' is invalid in the select list
because it is not contained in either an aggregate function or the
GROUP BY clause.
My goal is to add this single column (StoCount) to the cursor without modifying the query too much.
Is that possible?
You could move the correlated subquery to a cross apply
SELECT b.TransportNumber,
SUM(CASE WHEN a.DeliveryItemStatus = 'C' OR a.DeliveryItemStatus = 'V' THEN 1 ELSE 0 END) AS Completed,
COUNT(*) AS Total,
SUM(CASE WHEN a.DeliveryItemStatus = 'F' THEN 1 ELSE 0 END) AS Missing,
MAX(e.Freq) AS StoCount
FROM DeliveryItem a
INNER JOIN TransportItem b on a.DeliveryNumber = b.DeliveryNumber
INNER JOIN Material c on a.MaterialNumber = c.MaterialNumber
INNER JOIN Transport d on b.TransportNumber = d.TransportNumber
CROSS APPLY (
SELECT COUNT(*) freq FROM StorageTransportOrderItem s
WHERE s.DeliveryNumber = a.DeliveryNumber AND s.DeliveryItemNumber = a.DeliveryItemNumber
) e
WHERE a.StorageLocationNumber IS NOT NULL
AND a.Deleted <> 1
AND c.CommissioningArea LIKE #commissioningArea
AND d.TransportStatus < 70
GROUP BY b.TransportNumber
Edit by xsl:
I had to modify the query a bit, so that it returned the correct results for my database:
SELECT b.TransportNumber,
SUM(CASE WHEN a.DeliveryItemStatus = 'C' OR a.DeliveryItemStatus = 'V' THEN 1 ELSE 0 END) AS Completed,
COUNT(*) AS Total,
SUM(CASE WHEN a.DeliveryItemStatus = 'F' THEN 1 ELSE 0 END) AS Missing,
SUM(e.Freq) AS StoCount
FROM DeliveryItem a
INNER JOIN TransportItem b on a.DeliveryNumber = b.DeliveryNumber
INNER JOIN Material c on a.MaterialNumber = c.MaterialNumber
INNER JOIN Transport d on b.TransportNumber = d.TransportNumber
CROSS APPLY (
SELECT COUNT(1) freq FROM StorageTransportOrderItem s
WHERE
s.DeliveryNumber = a.DeliveryNumber
AND s.DeliveryItemNumber = a.DeliveryItemNumber
AND s.MaterialNumber = a.MaterialNumber
) e
WHERE a.StorageLocationNumber IS NOT NULL
AND a.Deleted <> 1
AND c.CommissioningArea LIKE #commissioningArea
AND d.TransportStatus < 70
GROUP BY b.TransportNumber

How to add 2 numbers in sql Select clause?

I have this query:
Select
Count(DocumentID)
From
Documents d
Inner Join
Accounts a on a.AccountID = d.CreatedByAccountID
Where
a.FeeExempt = 1
Or
(Select
Case
When a.OrganizationTypeID = 1 Or a.OrganizationTypeID = 2
Then 0
Else EFileFee
End
From ITypes
Where ITypeID = d.ITypeID
+
Select 100 --just to test
) <= 0
How can I make this query compile? Basically I want to do 2 selects and check if addition of both of them is less than 0 or not. But obviously at the moment the query is incorrect and won't compile.
Do you want this:
. . ..
(select sum(val)
from ((Select (Case When a.OrganizationTypeID = 1 Or a.OrganizationTypeID = 2 Then 0 Else EFileFee End) as val
From ITypes
Where ITypeID = d.ITypeID
) union all
(Select 100 --just to test
)
) t
) <= 0