AND? OR? IF? count the orders complying 2 conditions - mysql

I have 2 tables:
1st t "ORDERS" - columns: customer nr, order nr, payment method
2nd t "STATUSES" - columns: order nr, the order status
I want to count for specific customers, the amount of:
'shipped' orders paid by credit card (CC)
plus amount of 'canceled' orders paid another way.
I would like to have the result in one column.
select c_id, count(*)
from orders o
join statuses s on o.o_nr = s.o_nr
where o.c_id in (1, 2)
?
group by o.c_id
In details:
ORDERS
c_id | o_nr | pm
1 | 0004 | CC
1 | 0009 | CC
2 | 0011 | CC
2 | 0018 | installment
2 | 0020 | gift ;)
STATUSES
o_nr | status
0004 | shipped
0009 | in_progress
0011 | shipped
0018 | canceled
0020 | canceled
The result should be:
c_id | count (*)
1 | 1
2 | 3

Here's one approach:
SELECT o.c_id
, COUNT(*)
FROM orders o
JOIN statuses s
ON s.o_nr = o.o_nr
AND ( (s.status = 'shipped' AND o.pm = 'CC')
OR (s.status = 'canceled' AND o.pm <> 'CC')
)
WHERE o.c_id in (1, 2)
GROUP BY o.c_id
For this type of query, I sometimes find it advantageous to move the conditional tests into expressions in the SELECT list; this is useful when I want to return 0 counts, and I want to include the same rows in more than one "count"...
For example:
SELECT o.c_id
, SUM(s.status = 'shipped' AND o.pm = 'CC') AS shipped_and_cc
, SUM(s.status = 'canceled' AND o.pm <> 'CC') AS canceled_not_cc
, SUM((s.status = 'shipped' AND o.pm = 'CC')
OR (s.status = 'canceled' AND o.pm <> 'CC')) AS sac_and_cnc
, SUM(1) AS total_orders
FROM orders o
JOIN statuses s
ON s.o_nr = o.o_nr
WHERE o.c_id in (1, 2)
GROUP BY o.c_id

Related

SUM from 2 different table based on the ID group by from third table returning double value than expected value

I am having below Tables
1. Material Unit:
id | material_unit
1 | Nos.
2 | lts
2. Material Table:
id | Material_name
1 | bricks
2 | Cement
3. Grn Table:
id | material_id | qty | unit
1 | 1 | 100 | 1
2 | 2 | 500 | 1
3 | 2 | 100 | 1
4 | 1 | 200 | 1
4. Consumption table:
id | material_id | qty | unit
1 | 1 | 50 | 1
2 | 1 | 50 | 1
3 | 2 | 100 | 1
4 | 2 | 200 | 1
Results expected is as below:
Material Name | Unit | Total Qty | Total Consumed Qty | Stock
Bricks | Nos. | 300 | 100 | 200
Cement | Nos. | 600 | 300 | 300
So on above results Total Qty is to be fetched from Grn Table and Total Consumed Qty from Consumption Table and Stock is difference of both and should be Group By Material_id.
Below query returns SUM of the values from GRN and Consumption table but it multiples the SUM with the No. of entries in the consumption table.
What mistake i am doing, can someone please help to figure out my mistake.
SELECT sm.material_name as 'Material Name', mu.material_unit as 'Material Unit', sum(g.qty) as 'GRN Qty', sum(c.qty) as 'Consumed Qty', SUM(g.qty) - SUM(c.qty) as 'Stock' from grn g
JOIN material_table sm ON g.material_id = sm.id
JOIN material_unit_table mu ON g.unit_id = mu.id
JOIN consumption c ON c.material_id = g.material_id
group by g.material_id
You consumption doubles the number of rows, so by summing the quantidy before joining corrects the numbers.
SELECT
MAX(sm.material_name) as 'Material Name'
, MAX(mu.material_unit) as 'Material Unit'
, sum(g.qty) as 'GRN Qty'
, sum(c.qty) as 'Consumed Qty'
, SUM(g.qty) - SUM(c.qty) as 'Stock'
from grn g
inner JOIN material_table sm ON g.material_id = sm.id
inner JOIN material_unit_table mu ON g.unit = mu.id
INNER JOIN (SELECT `material_id`, SUM(`qty`) qty, `unit` FROM consumption GROUP BY `material_id`,`unit`) c ON c.material_id = g.material_id
GROUP BY g.material_id
example https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=1a19b0f5a7ba08c5e9dbb0cbf85d2a27
I think your problem is due to grn table is your origin table, so the select gets a line for each row in the table. Make material_table your first table and join the rest of them to this, using its ID to make the join with the rest of tables.
Pls, try this version and let me know if it works properly (consider it may contain an error, i've written it into a notepad)
SELECT sm.material_name as 'Material Name', mu.material_unit as 'Material Unit', sum(g.qty) as 'GRN Qty', sum(c.qty) as 'Consumed Qty', SUM(g.qty) - SUM(c.qty) as 'Stock'
FROM material_table sm
JOIN grn g ON g.material_id = sm.id
JOIN material_unit_table mu ON g.unit_id = mu.id
JOIN consumption c ON c.material_id = sm.material_id
GROUP BY by sm.material_id
Hope it helps!
WITH Grn AS (
SELECT
material_id,
unit,
SUM(qty) AS 'Total Qty'
FROM grn
GROUP BY
material_id,
unit
),
Consumption AS (
SELECT
material_id,
SUM(qty) AS 'Consumed Qty'
FROM consumption
GROUP BY
material_id
)
SELECT
m.Material_name AS 'Material Name',
u.Material_unit AS Unit,
g.[Total Qty],
c.[Consumed Qty],
g.[Total Qty] - c.[Consumed Qty] AS Stock
FROM Grn g
INNER JOIN Consumption c ON g.material_id = c.material_id
INNER JOIN material_unit_table u ON g.unit_id = u.Id
INNER JOIN material_table m ON g.material_id = m.Id
This query will give desired result.
Bottom line is to remove material_id duplicates in grn and consumption tables. This way joining table consumption and grn by material_id won't give duplicates.
Note: In order for this to work, in grn table every material needs to have only one unit.

How to get the result in single row when using sub queries in SQL Server 2008?

I am trying to get a table with stage_name and its count in respective loan product. Like in below example stage_name is RCO and there are three loan product, Auto loan, Consumer loan and Credit card. Though I have used the logic and getting the right output, but in the output, I am getting the separate row for each stage_name and loan product case. I want only one row with all the three result. Please look at my code below, actual output and desired output:
SELECT
'RCO',
CASE
WHEN sq2.loan_type = 'Consumer loan'
THEN SUM(ISNULL(sq2.user_count, 0))
END AS Consumer_Loan,
CASE
WHEN sq2.loan_type = 'Auto Loan'
THEN SUM(ISNULL(sq2.user_count, 0))
END AS Auto_Loan,
CASE
WHEN sq2.loan_type = 'Credit Card'
THEN SUM(ISNULL(sq2.user_count, 0))
END AS Credit_Card
FROM
(SELECT
'RC0' AS ws_name, 'Consumer loan' AS loan_type,
COUNT(DISTINCT a.bpm_referenceno) AS user_count,
a.takenby AS user_id
FROM
BM_RLOS_DecisionHistoryForm a
INNER JOIN
(SELECT
m.bpm_referenceno
FROM
BM_RLOS_EXTTABLE m
WHERE
m.loan_type = 'Consumer Loan') sq1 ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE
a.winame='RCO'
GROUP BY
a.takenby
UNION
SELECT 'RC0','Auto loan',
count (DISTINCT a.bpm_referenceno), a.takenby
from
BM_RLOS_DecisionHistoryForm a
INNER JOIN
(SELECT
m.bpm_referenceno
FROM BM_RLOS_EXTTABLE m
WHERE m.loan_type='Auto Loan')sq1
ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE a.winame='RCO'
GROUP BY a.takenby
UNION
SELECT 'RC0','Credit Card',
count (DISTINCT a.bpm_referenceno), a.takenby
from
BM_RLOS_DecisionHistoryForm a
INNER JOIN
(SELECT
m.bpm_referenceno
FROM BM_RLOS_EXTTABLE m
WHERE m.loan_type='Credit Card')sq1
ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE a.winame='RCO'
GROUP BY a.takenby) sq2
GROUP BY sq2.ws_name,sq2.loan_type
Actual output:
|--------------|-------------|-------------|-------------|
| Stg_nm | Cons_ln | Auto_lan | Credit_card |
|--------------|-------------|-------------|-------------|
| RCO | NULL | NULL | 8 |
|--------------|-------------|-------------|-------------|
| RCO | NULL | 55 | NULL |
|--------------|-------------|-------------|-------------|
| RCO | 81 | NULL | NULL |
|--------------|-------------|-------------|-------------|
Required Output
|--------------|-------------|-------------|-------------|
| Stg_nm | Cons_ln | Auto_lan | Credit_card |
|--------------|-------------|-------------|-------------|
| RCO | 81 | 55 | 8 |
|--------------|-------------|-------------|-------------|
The top half should be like this - reverse the usage of SUM and CASE, and remove the last GROUP BY altogether
SELECT
'RCO',
SUM(CASE
WHEN sq2.loan_type = 'Consumer loan'
THEN sq2.user_count
ELSE 0 END
)
AS Consumer_Loan,
SUM(CASE
WHEN sq2.loan_type = 'Auto loan'
THEN sq2.user_count
ELSE 0 END
)
AS Auto_Loan,
SUM(CASE
WHEN sq2.loan_type = 'Credit Card'
THEN sq2.user_count
ELSE 0 END
)
AS Credit_Card
FROM
<your existing query, with the final GROUP BY removed>
But you need to remove the GROUP BY from the end altogether

Get all rows from table - with the latest row from another table, with another table based on the latest row

I need to get all the details from the orders table, with the latest status ID in the orders statuses table, and then the name of that status from the states table.
orders
id | customer | product
-----------------------
1 | David | Cardboard Box
Order_to_statuses
id | order | status | updated_at
--------------------------------
1 | 1 | 1 | 2017-05-30 00:00:00
2 | 1 | 3 | 2017-05-28 00:00:00
3 | 1 | 4 | 2017-05-29 00:00:00
4 | 1 | 2 | 2017-05-26 00:00:00
5 | 1 | 5 | 2017-05-05 00:00:00
order_states
id | name
---------
1 | Pending
2 | Paid
3 | Shipped
4 | Refunded
In this instance, I would need to get the customer and product, with the latest status ID from the order statuses table, and then the name of that state.
How can I do this?
I'd break this down by first getting the max(updated_at) for each order, then work to everything else you need. You can get the max date for each order by using subquery:
select
s.`order`,
s.`status`,
s.updated_at
from order_to_statuses s
inner join
(
select
`order`,
max(updated_at) as updated_at
from order_to_statuses
group by `order`
) m
on s.`order` = m.`order`
and s.updated_at = m.updated_at
Once you get this you now have the order, the status id, and the most recent date. Using this you can then JOIN to the other tables, making your full query:
select
o.customer,
o.product,
ots.updated_at,
os.name
from orders o
inner join
(
select
s.`order`,
s.`status`,
s.updated_at
from order_to_statuses s
inner join
(
select
`order`,
max(updated_at) as updated_at
from order_to_statuses
group by `order`
) m
on s.`order` = m.`order`
and s.updated_at = m.updated_at
) ots
on o.Id = ots.`order`
inner join order_states os
on ots.`status` = os.id;
See a demo
It may have some typo, but the idea of the query should be something like this:
select orders.id, orders.customer, orders.product,
order_to_status.status, staus.name
from orders, order_to_status, status
where orders.id = order_to_status.order
and order_to_status.status = status.id
and order_to_status.updated_at in (
SELECT MAX(order_to_status.updated_at)
FROM order_to_status
where order_to_status.order = orders.id
group by order_to_status.order
);
I ussually don't use joins but with joins it should be like this:
select orders.id, orders.customer, orders.product,
order_to_status.status, staus.name
from orders
JOIN order_to_status ON orders.id = order_to_status.order
JOIN status ON order_to_status.status = status.id
where
order_to_status.updated_at in (
SELECT MAX(order_to_status.updated_at)
FROM order_to_status
where order_to_status.order = orders.id
group by order_to_status.order
);
Note I added a group by I had missed.
EDIT 2
I had an error in the subquery condition.
changed to where order_to_status.order = orders.id
also moved the group by after the where clause.

get result where one column value is identical in two following rows

I want to select failed employees from employeeExam table where status column equals 0 for two following rows.
Result should be like this:
ID COURSE_ID EMPLOYEE_ID DEGREE DATE STATUS NUMOFTAKINGEXAMS
4 2 4 17 January, 15 2013 00:00:00+0000 0 2
Here is what I did:
SQL Fiddle
To clarify more: when ordered by id, result should contains only exams' data which have the same course_id and employee_id and status = 0 under each other directly.
Please try this out and comment:
SQLFIDDLE DEMO
set #sum:=0;
set #id:=0;
select distinct x.empid, x.degree, x.date, x.status
from (
select #sum:= (case when status=0
and #id = employee_id then #sum+1
else 1 end)
as sm, #id:=employee_id as empid, degree, date, status
from employeeexam
order by employee_id)x
where x.sm >= 2
;
| EMPID | DEGREE | DATE | STATUS |
------------------------------------------------------------
| 2 | 5 | January, 16 2013 00:00:00+0000 | 0 |
| 3 | 6 | January, 16 2013 00:00:00+0000 | 0 |
| 4 | 15 | January, 14 2013 00:00:00+0000 | 0 |
Just a portion of the SQL-code, to show the principle, although I doubt it fully extends your requirements. For example, this code doesn't check for consecutive failed attempts, only all attempts in general. It doesn't show any any rows of which the applicant has already passed an exam.
So for example: should one take an exam and get status 0, 0, 1, 0, 0; it would not show up, because applicant has passed at least one exam.
SELECT course_id, employee_id, MAX(degree), status, COUNT(id) NumExamsTaken
FROM employeeExam
GROUP BY course_id, employee_id
HAVING COUNT(id) >= 2 AND SUM(status) = 0;
SQL Fiddle
SELECT a.*
FROM
( SELECT x.*
, COUNT(*) rank
FROM employeeExam x
JOIN employeeExam y
ON y.course_id = x.course_id
AND y.employee_id = x.employee_id
AND y.date <= x.date
GROUP
BY id
) a
JOIN
( SELECT x.*
, COUNT(*) rank
FROM employeeExam x
JOIN employeeExam y
ON y.course_id = x.course_id
AND y.employee_id = x.employee_id
AND y.date <= x.date
GROUP
BY id
) b
ON b.course_id = a.course_id
AND b.employee_id = a.employee_id
AND b.status = a.status
AND b.rank = a.rank - 1
WHERE a.status = 0;

MySQL Join and Subqueries

I currently have the following tables:
Case_Workflows
case_id (PK) | work_id (PK) | date
1 | 1 | 2011-12-12
1 | 4 | 2011-12-13
2 | 6 | 2011-12-18
Workflows
work_id (PK) | status_id
1 | 1
2 | 1
3 | 1
4 | 2
5 | 2
6 | 3
Statuses
status_id (PK) | title
1 | abc
2 | def
3 | ghi
What I am attempting to do is pull a count of the total number of cases with a specific status such as 'abc'. The snag is that each case can have multiple workflows and I only want the single most recent one for each case.
The end result should be:
Status: abc - Count: 2
This is what I have so far:
SELECT COUNT(cases.case_id) as countNum
FROM $this->_caseTable
JOIN case_workflows
ON cases.case_id = cases_workflows.case_id
JOIN workflows
ON cases_workflows.workflow_id = workflows.workflow_id
JOIN statuses
ON workflow.status_id = statuses.status_id
WHERE cases.date > '2011-12-12'
AND cases.date <= '2011-12-18'
What I am unsure on is how to first select the latest work_id for each case, and then grabbing its status_id to match it to a WHERE clause such as WHERE statuses.title = 'abc'
SELECT COUNT(*) as countNum
FROM $this->_caseTable
JOIN workflows
ON workflows.workflow_id =
( SELECT workflow_id
FROM cases_workflows AS mcwf
WHERE mcwf.case_id = cases.case_id
ORDER BY date DESC
LIMIT 1
)
JOIN statuses
ON workflow.status_id = statuses.status_id
WHERE cases.date > '2011-12-12'
AND cases.date <= '2011-12-18'
AND statuses.title = 'abc'
From what I'm understanding here, you need to add statuses.title to your SELECT clause, and then add a GROUP BY clause:
SELECT statuses.title, COUNT(cases.case_id) as countNum
FROM $this->_caseTable
JOIN (SELECT case_id, work_id, max(date)
FROM case_workflows
GROUP BY work_id
WHERE case_id = cases.case_id) cw
ON cases.case_id = cw.case_id
JOIN workflows
ON cw.workflow_id = workflows.workflow_id
JOIN statuses
ON workflow.status_id = statuses.status_id
GROUP BY statuses.title
WHERE cases.date > '2011-12-12'
AND cases.date <= '2011-12-18'