Multiple conditional counts in the same SQL sentence [duplicate] - mysql

This question already has answers here:
multiple query same table but in different columns mysql
(4 answers)
Closed 4 years ago.
The goal is to get the progress on a list of tasks considering the status of each tasks, ruling out invalid tasks.
Currently, I managed to do this at a very expensive hit on performance and would like to know if it's possible to achieve the same result in one SQL sentence or at least find a more optimized way.
SELECT COUNT(`id`) AS `status` FROM `task` WHERE `status` = 'done'
UNION
SELECT COUNT(`id`) AS `status` FROM `task` WHERE `status` = 'pending';
The above query results in 2 rows, I can sum them up and get the total of 5 which is correct, first row is count of done tasks: from there is easy to get 3/5 or 60% done
status
------
3
2
I know this example is very specific, I tried this query but encountered some problems. Is about the grouping? or I'm completely wrong here?
SELECT
COUNT(CASE WHEN `status` = 'done' THEN 1 ELSE 0 END) AS `done`,
COUNT(CASE WHEN `status` = 'pending' THEN 1 ELSE 0 END) AS `pending`
FROM `task`
GROUP BY `status`;
The above query results in 2 columns and 3 repeated rows:
done pending
---------------
3 3
1 1
2 2
At this example table I'm looking at 3 out of 5 valid tasks or 60% completed.
name status
------------------
task 1 done
task 2 done
task 3 pending
task 4 done
task 5 invalid
task 6 pending
Thanks for any help!

You were counting the false condition as well with else 0. Eliminating it would default to null for a comparison that is false and wouldn't be counted.
SELECT
COUNT(CASE WHEN `status` = 'done' THEN 1 END) AS `done`,
COUNT(CASE WHEN `status` = 'pending' THEN 1 END) AS `pending`
FROM `task`
WHERE `status` in ('done','pending')
Or this can be simplified to
SELECT
SUM(`status` = 'done') AS `done`,
SUM(`status` = 'pending') AS `pending`
FROM `task`
WHERE `status` in ('done','pending')

Related

Create a status column with results based on another table using a case statement

I have 2 tables
orders
order_id
1
2
3
orders_details
order_id
sku
ordered
received
1
abc
10
10
1
xyz
10
10
2
abc
10
6
2
xyz
10
0
3
abc
10
0
4
xyz
10
0
I would like to add to the orders table a column called status which would have the results 'complete', 'partial' and 'open' based on what has been received in the orders_details table
result I am trying to acheive
order_id
status
1
complete
2
partial
3
open
The problem is when a different product from the same order_id has a different received result it creates an additional row with the group by.
result I am getting
order_id
status
1
complete
2
open
2
partial
3
open
https://www.db-fiddle.com/f/bFrYguhmcMJ32iFuUcXfDw/3
SELECT
`orders`.`order_id`,
(CASE
WHEN `received` >= `ordered` THEN 'complete'
WHEN `received` < `ordered` AND `received` != 0 THEN 'partial'
WHEN `received` = 0 THEN 'open'
END) AS `status`
FROM
`orders`
LEFT JOIN `orders_details` ON `orders_details`.`order_id` = `orders`.`order_id`
GROUP BY
`order_id`, `status`
;
I might be missing something, but I think you just want:
SELECT `orders`.`order_id`,
CASE SUM(received)
WHEN 0 THEN 'open'
WHEN SUM(ordered) THEN 'complete'
ELSE 'partial'
END AS `status`
FROM `orders`
LEFT JOIN `orders_details`
ON `orders_details`.`order_id` = `orders`.`order_id`
GROUP BY `order_id`;
Here's your updated DB Fiddle
It is a bit hacky, but you can define the order by giving then names of the status an order the should be choosen, and then take the maximum for this order.
Zcomplete>partial>open
We can thenremove the additional charatcers
Query #1
SELECT
`orders`.`order_id`,
REPLACE (MAX((CASE
WHEN `received` >= `ordered` THEN 'Zcomplete'
WHEN `received` < `ordered` AND `received` != 0 THEN 'partial'
WHEN `received` = 0 THEN 'open'
END)), 'Z', '') AS `status`
FROM
`orders`
LEFT JOIN `orders_details` ON `orders_details`.`order_id` = `orders`.`order_id`
GROUP BY
`order_id`
;
order_id
status
1
complete
2
partial
3
open
View on DB Fiddle

How to select data in mysql that all the joined table is not null on certain column

i have 2 table which is one to many
table order
order_id
order_date
1
2021/01/01
2
2021/01/02
3
2021/01/02
table detail order
detail_order_id
order_id
is_finished
1
1
null
2
1
2021/01/03
3
2
2021/01/04
4
2
2021/01/04
5
3
2021/01/05
6
3
2021/01/06
7
3
null
so i wanna data that have condition if some of the detail order rows is_finished column not null, so the status is gonna be not finish.
and if all the detail order rows is_finished column not contain any null value like id 2, so the status is finished
expected result
order_id
status
1
not finish
2
finished
3
not finish
It seems like you don't really need a join since table_detail_order already have order_id and you only want to check is_finished, you might just need a query on 1 table like:
SELECT order_id,
CASE WHEN SUM(is_finished IS NULL)=0
THEN 'Finished' ELSE 'Not finish' END AS 'Status'
FROM table_detail_order GROUP BY order_id;
Demo fiddle
Btw, is_finished IS NULL will return 1 (true) or 0 (false) so in a table it would look like:
order_id
is_finished
is_finished IS NULL
1
null
1
1
2021/01/03
0
2
2021/01/04
0
2
2021/01/04
0
3
2021/01/05
0
3
2021/01/06
0
3
null
1
Therefore SUM(is_finished IS NULL) with GROUP BY order_id; will do something like this:
order_id
SUM(is_finished IS NULL)
1
1+0=1
2
0+0=0
3
0+0+1=1
And that is why CASE WHEN SUM(is_finished IS NULL)=0 ... is considered as finished while otherwise as not finish.
we can solve the problem like this
left join order and order_detail,but has condition order_details.is_finished is null
so we get a result that the joined order_details's is_finished only null
in that case there is no order_details join with order 2
then we regard the result as a temp table,so when joined order_details has data then it is not finished
here is the example data,you can run query in it
select id,
case when order_id>0 then 'not finish' else 'finished' end as status
from (
select o.id,od.order_id from `order` as o
left join order_detail as od
on (o.id=od.order_id and od.is_finished is null)
group by o.id
) as _t
You can try this. This query uses a LEFT JOIN and COUNT. Where the first count counted the NULL values as ZERO and the second count counts all values, then compare the 2 counts, if the first and second count is equal to each other it means that the order details is finished, if not then not finish.
SELECT a.`order_id`,
IF(COUNT(IF(ISNULL(is_finished),0,1))=COUNT(is_finished), 'finished', 'not finish') AS `status` FROM `order` a
LEFT JOIN `detail_order` b ON a.`order_id`=b.`order_id`
GROUP BY a.`order_id` ;
RESULT*
order_id status
-------- ------------
1 not finish
2 finished
3 not finish

Make a calculation with aliases in one (sub)query

I have a table with data and am trying to make the following calculation in a query: IDO / IDP * 100.
**TBL**
Name IDP IDO
ABC 123 231
DEF 124 NULL
GHI 125 NULL
JKL 126 342
KNM 127 NULL
I managed to count up all the values needed to make the calculation and gave each an alias. This is what I have so far, but the calculation part returns an error telling me the references are not supported.
SELECT
SUM(case when IDP is not null then 1 end) as Checked,
SUM(case when IDO is null then 1 end) as NotChecked,
(SELECT Checked / NotChecked) * 100 AS Result
FROM TBL
I've also tried with a subquery, but it results in the same error.
SELECT
SUM(case when IDP is not null then 1 end) as Checked,
SUM(case when IDO is null then 1 end) as NotChecked,
(
SELECT Checked / NotChecked * 100 AS Result FROM TBL
)
FROM TBL
What would be the right or a better way to do this?
I don't think you will be able to reference aliases created in a SELECT clause in that same SELECT clause.
As you probably know, you could just repeat the logic, like the following (although I assume the purpose of your question is to avoid writing the logic twice).
SELECT
SUM(case when IDP is not null then 1 end) as Checked,
SUM(case when IDO is null then 1 end) as NotChecked,
(SUM(case when IDP is not null then 1 end) / SUM(case when IDO is null then 1 end)) * 100 AS Result
FROM TBL
A subquery version, where you don't repeat the logic, would be the following. Note that the aliases are created in the subquery so the outer query has no trouble referencing them.
SELECT
Checked, NotChecked, (Checked / NotChecked) * 100 as Result
FROM
(
SELECT
SUM(case when IDP is not null then 1 end) as Checked,
SUM(case when IDO is null then 1 end) as NotChecked
FROM TBL
) tt
Since you don't seem to be summing, just counting null/non-null values, I personally find the sum/case statements a bit confusing and might prefer something like this:
SELECT
Checked, NotChecked, (Checked / NotChecked) * 100 as Result
FROM
(
SELECT
count(IDP) as Checked,
count(*)-count(IDO) as NotChecked
FROM TBL
) tt

How to display partial pending in status column when column contains both pending and completed in mysql

I want to display partial pending in my status column when status column contains both pending and completed.
Table:
Enquiry Name Part Number Status
------------------------------------------
Enq1 aar-12332 Pending
Enq1 aar-12555 Completed
Enq2 aar-12666 Pending
Expected Result:
Enquiry Name Status
----------------------------------
Enq1 Partial Pending
Enq2 Pending
I wrote and tried many query but I did not get as expected.
Please Help me with this.
By using the approach below, you can achieve your expected result:
-- Get 'Partial Pending'
SELECT EnquiryName, 'Partial Pending' AS `Status`
FROM EnquiryTable
WHERE `Status` IN ('Pending', 'Completed' )
GROUP BY EnquiryName
HAVING COUNT(DISTINCT `Status`) = 2
UNION
-- Get the remaining Status
SELECT EnquiryName, `Status`
FROM EnquiryTable
WHERE `Status` IN ('Pending', 'Completed' )
GROUP BY EnquiryName
HAVING COUNT(DISTINCT `Status`) <> 2
Result:
EnquiryName Status
Enq1 Partial Pending
Enq2 Pending
Reference from this post
SQL Fiddle DEMO for the same.

COUNT(DISTINCT Column1) for Only One Column of Multiple Columns

Admittedly, I've seen this question on here a few times -- but all the answers seem to solve their problems by using a GROUP BY or a WHERE, so I was curious how to get around this if your query is getting too large where that wouldn't work.
For example, I'm writing something that uses two left joins to my main table, bringing the overlaps over into the results. As I'm still relatively new to SQL, I'm not exactly sure what's doing it -- but I know that I'm getting an extra thousand or so people when I run the counts; I'm imagining this is the case because there are duplicate IDs for each person (purposefully) in the two tables I'm joining.
All my queries populating the results I want to get for this project is using COUNT() or SUM() pending on the column. Is there a way that I can use DISTINCT to make only one column at a time treat my IDs only as one? Based on what I've done so far, I've noticed that whenever you set DISTINCT it works beyond just the one column you're trying to attribute it to. Any suggestions? It'd be very appreciated!
Here's an example of my code so far that includes duplicate IDs:
SELECT
targeted.person AS "Person",
targeted.work AS "Occu",
(COUNT(targeted.id)) AS "Targeted",
(COALESCE(SUM(targeted.signed="Yes"),0)) AS "Signed",
(COALESCE(SUM(targeted.signed="Yes"),0))/COUNT(targeted.id)*100 AS "Signed %",
(COALESCE(COUNT(question.questionid="96766"),0)) AS "Donated",
(COALESCE(COUNT(question.questionid="96766"),0))/(COALESCE(SUM(targeted.signed="Yes"),0))*100 AS "Donated %",
(COALESCE(SUM(question.surveyresponsename),0)) AS "Donation $",
ROUND((COALESCE(SUM(question.surveyresponsename),0))/(COALESCE(COUNT(question.questionid="96766"),0)),2) AS "Avg Donation",
(CASE WHEN (left(targeted.datesigned,1)="5" AND right(question.datecontacted,2)="13") THEN (COALESCE(SUM(targeted.signed="Yes"),0)) ELSE 0 END) AS "Signed This Month",
(CASE WHEN (left(question.datecontacted,1)="5" AND right(question.datecontacted,2)="13") THEN (COALESCE(COUNT(question.questionid="96766"),0)) ELSE 0 END) AS "Donated This Month",
(CASE WHEN question.ContactType="House Visit" THEN COUNT(question.id) ELSE 0 END) AS "At Home",
(CASE WHEN question.ContactType="Worksite" THEN COUNT(question.id) ELSE 0 END) AS "At Work",
(CASE WHEN (left(events.day,1)="5" AND right(events.day,2)="13") THEN COUNT(events.id) ELSE 0 END) AS "Events This Month"
FROM targeted
LEFT JOIN question ON targeted.id=question.id
LEFT JOIN events ON targeted.id=events.id
GROUP BY targeted.person, targeted.work;
Here are the basics of the table structures:
Targeted:
Field Type Null Key Default
ID bigint(11) YES Primary NO
Work varchar(255) YES NULL
Person varchar(255) YES NULL
Signed varchar(255) YES NULL
DateSigned varchar(255) YES NULL
Question:
Field Type Null Key Default
ID bigint(11) YES Primary NO
QuestionID int(11) YES NULL
SurveyResponseId int(11) YES NULL
SurveyResponseName varchar(255) YES NULL
DateContacted varchar(255) YES NULL
ContactType varchar(255) YES NULL
Events:
Field Type Null Key Default
ID bigint(11) NO Primary NO
Day varchar(255) YES NULL
EventType varchar(255) YES NULL
And the results would are intended to look something like:
Person Occu Targeted Signed Signed % ...
1 Job 1 1413 765 54.14 ...
2 Job 2 111 80 72.072 ...
2 Job 3 931 715 76.7991 ...
3 Job 4 2720 1435 52.7573 ...
4 Job 5 401 218 54.364 ...
Thanks for the help!
The proper way to solve this problem is by doing the aggregation in the subqueries. To aggregate questions and events to the right level, you need to join in the targeted table. Then, you will not need the aggregation at the outermost level:
select . . .
from (select t.name, t.work,
count(t.id) as Targeted,
. . .
from targets t
group by t.name, t.work
) t left join
(select t.name, t.work,
sum(case when question_id = 96766 then 1 else 0 end) as Donated,
. . .
from question q join
targeted t
on t.id = t.id
group by t.name, t.work
) q
on t.name = q.name and t.work = q.work left join
(select t.name, t.work,
sum(CASE WHEN (left(events.day,1)="5" AND right(events.day,2)="13") THEN 1 ELSE 0 END
) AS "Events This Month"
from events e join
targeted t
on e.id = t.id
) e
on e.name = t.name and e.work = t.work