case within case using mysql - mysql

I have 3 columns doc_date, chem_date, st_date
all 3 columns belong to different tables:
doc
doc_date count(x)
01-02-2012 2
02-02-2012 3
chem
chem_date count(x)
04-02-2012 1
06-02-2012 0
stock
st_date count(x)
01-02-2012 1
03-02-2012 5
I want to write select clause like this
case doc_date
when '01' then count(x),
case chem_date
when '01' then count(x),
case st_date
when '01' then count(x)
end as '01',
case doc_date
when '02' then count(x),
case chem_date
when '02' then count(x),
case st_date
when '02' then count(x)
end as '02',
....up to 31 days
If some case statements have an entry on same date e.g if doc_date and st_date both exist on '01-02-2012' then their respective count should be added to the final count(x) means count(x) + count(x)
So, the answer should be:
01 02 03 04 05 06 07 08 09 10 11 12.. up to 31 days
3 3 5 1 0 count(x) value for particular date
Explanation of the output: Here, the starting value for date 01 is 2 for doc table and for stock table the value is 1. So, the final value will become addition of them which is 3
The same rule will be applicable for others.
Any suggestions how can I achieve this output in SQL? Or any other way?

Here's a pseudo code showing how it can be done:
select
case when date_format(doc_date,"%d") = '01' then sum_x else null end as '01',
case when date_format(doc_date,"%d") = '02' then sum_x else null end as '02',
--and etc
from (
select doc_date, sum(doc_x) as sum_x --sum the x value
from
(
select doc_date,doc_x --combine all 3 table stuff
from doc a
union all
select chem_date,chem_x
from chem a
union all
select st_date,st_x
from st a
) h
group by doc_date --use first col name as the union's col name
) g
limit 1,1
If the doc, chem and st are combined using joins and have different schema, you may need to do pivot, either using multiple join to itself or use case... when...:
select
case when date_format(doc_date,"%d") = '01' then sum_x else null end as '01',
case when date_format(doc_date,"%d") = '02' then sum_x else null end as '02',
--and etc
from (
select doc_date,
a_x + b_x + c_x as sum_x --sum them according to date
from (select ... from doc where ...) a
left join (select ... from chem where ...) b on a.doc_date=b.doc_date and --some join condition
left join (select ... from st where ...) c on a.doc_date=c.doc_date and --some join condition
) g
limit 1,1
I am working with an ambiguous question here, so clarify as needed,

Related

I want to query the no of transaction done by a customer in a particular year, but the output should come year wise for each customer in table format

Output should be in below format, but I am getting wrong output:
Where 2019,2020,2021 column contains transaction done by customer in respectively 2019, 2020, 2021. Also if transactions in 2019,2020,2021 is equal Max_transaction is populated with first non-zero transaction year .
customer_name 2019 2020 2021 Max_transaction_year total_transaction
pug 2 1 0 2019 4
hari 0 1 1 2020 2
adh 0 0 1 2021 1
Sample table and data :
Also note that the first two digits in "tid" represent the year of transaction. Eg: 19597 -'19' represents 2019 and so on for 2020 and 2021.
create table client (cid int,cname char(10));
create table trans (tid int,cid int);
insert into client values(102,'pug'),(107,'ravi'),(109,'hari'),(105,'pon'),(106,'adh'),(104,'bav'),(101,'kat');
insert into trans values(19597,102),(19567,102),(20325,109),(21789,106),(17432,106),(21786,109),(20302,102),(17301,103);
Thanks in advance
Schema (MySQL v8.0)
create table client (cid int,cname char(10));
create table trans (tid int,cid int);
insert into client values(102,'pug'),(107,'ravi'),(109,'hari'),(105,'pon'),(106,'adh'),(104,'bav'),(101,'kat');
insert into trans values(19597,102),(19567,102),(20325,109),(21789,106),(17432,106),(21786,109),(20302,102),(17301,103);
Query #1
SELECT
customer_name,
SUM(
CASE WHEN year=2019 THEN no_transactions ELSE 0 END
) as '2019',
SUM(
CASE WHEN year=2020 THEN no_transactions ELSE 0 END
) as '2020',
SUM(
CASE WHEN year=2021 THEN no_transactions ELSE 0 END
) as '2021',
MAX(
CASE WHEN rn=1 THEN year ELSE 0 END
) as Max_transaction_year,
SUM(no_transactions) as total_transaction
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY customer_name ORDER BY no_transactions DESC) rn
FROM (
SELECT
c.cname as customer_name,
2000+FLOOR(tid / 1000) as year ,
COUNT(1) as no_transactions
FROM
trans t
INNER JOIN
client c ON t.cid = c.cid
WHERE
FLOOR(tid / 1000) BETWEEN 19 and 21
GROUP BY
c.cname, 2000+FLOOR(tid / 1000)
) p1
) p2
GROUP BY customer_name;
customer_name
2019
2020
2021
Max_transaction_year
total_transaction
adh
0
0
1
2021
1
hari
0
1
1
2020
2
pug
2
1
0
2019
3
View on DB Fiddle
I think a somewhat simpler method just uses conditional aggregation:
select cname, cnt_2019, cnt_2020, cnt_2021,
(case greatest(cnt_2019, cnt_2020, cnt_2021)
when cnt_2019 then '2019'
when cnt_2020 then '2020'
when cnt_2021 then '2021'
end) as max_year,
total_transactions
from (select c.cname, c.cid,
sum(year = '2019') as cnt_2019,
sum(year = '2020') as cnt_2020,
sum(year = '2021') as cnt_2021,
count(*) as total_transactions
from client c join
(select t.*, concat('20', left(t.tid, 2)) as year
from trans t
) t
on c.cid = t.cid
where year >= '2019' and year <= '2021'
group by c.cname, c.cid
) ct
order by cname, cid;
Here is a db<>fiddle.

How to Group Counted Data

I have 3 categories (Below SLA, Near SLA, Over SLA) that has different conditions, I try to count the data but the result is not summarized by their category
This is my query:
SELECT
B.province AS 'PROVINCE',
CASE
WHEN TIMEDIFF(A.deli_time, A.create_time) < '20:00:00' THEN COUNT(TIMEDIFF(A.deli_time, A.create_time))
END AS 'Below SLA',
CASE
WHEN (TIMEDIFF(A.deli_time, A.create_time) > '20:00:00') AND (TIMEDIFF(A.deli_time, A.create_time) < '24:00:00') THEN COUNT(TIMEDIFF(A.deli_time, A.create_time))
END AS 'NEAR SLA',
CASE
WHEN TIMEDIFF(A.deli_time, A.create_time) > '24:00:00' THEN COUNT(TIMEDIFF(A.deli_time, A.create_time))
END AS 'OVER SLA'
FROM
deli_order A
INNER JOIN
deli_order_delivery B on A.id = B.order_id
WHERE
(DATE(A.plat_create_time) BETWEEN '2019-03-30' AND'2019-04-07') AND (TIMEDIFF(A.deli_time, A.create_time) IS NOT NULL)
GROUP BY B.province;
and this is the result that i got:
Province | Below SLA | Near SLA | Over SLA
------------------------------------------------
Bali 30 Null Null
30 is the total of all the records of 'Bali', but its actually divided into 19 Below SLAs, 5 Near SLAs, and 6 Over SLAs.
What should i change in my query?
SELECT
B.province AS 'PROVINCE',
SUM(CASE
WHEN TIMEDIFF(A.deli_time, A.create_time) < '20:00:00' THEN 1
END) AS 'Below SLA',
Put an aggregate function for each case,OUTSIDE of it.I did it for just one case,it`s all the same.

How to get only latest record from different ranges?

I am looking at a case in which we have a number of tanks filled with liquid. The amount of liquid is measured and information is stored in a database. This update is done every 5 minutes. Here the following information is stored:
tankId
FillLevel
TimeStamp
Each tank is categorized in one of the following 'fill-level' ranges:
Range A: 0 - 40%
Range B: 40 - 75%
Range C: 75 - 100%
Per range I count the amount of events per tankId.
SELECT sum(
CASE
WHEN filllevel>=0 and filllevel<40
THEN 1
ELSE 0
END) AS 'Range A',
sum(
CASE
WHEN filllevel>=40 and filllevel<=79
THEN 1
ELSE 0
END) AS 'Range B',
sum(
CASE
WHEN filllevel>79 and filllevel<=100
THEN 1
ELSE 0
END) AS 'Range C'
FROM TEST ;
The challenge is to ONLY count the latest record for each tank. So for each tankId there is only one count (and that must be the record with the latest time stamp).
For the following data:
insert into tank_db1.`TEST` (ts, tankId, fill_level) values
('2017-08-11 03:31:18', 'tank1', 10),
('2017-08-11 03:41:18', 'tank1', 45),
('2017-08-11 03:51:18', 'tank1', 95),
('2017-08-11 03:31:18', 'tank2', 20),
('2017-08-11 03:41:18', 'tank2', 30),
('2017-08-11 03:51:18', 'tank2', 80),
('2017-08-11 03:31:18', 'tank3', 30),
('2017-08-11 03:41:18', 'tank3', 45),
('2017-08-11 03:51:18', 'tank4', 55);
I would expect the outcome to be (only the records with the latest timestamp per tankId are counted):
- RANGE A: 0
- RANGE B: 1 (tankdId 3)
- RANGE C: 2 (tankId 1 and tankId2)
Probably easy if you are an expert, but for me it is real hard to see what the options are.
Thanks
You can use the following query to get the latest per group timestamp value:
select tankId, max(ts) as max_ts
from test
group by tankId;
Output:
tankId max_ts
--------------------------------
1 tank1 11.08.2017 03:51:18
2 tank2 11.08.2017 03:51:18
3 tank3 11.08.2017 03:41:18
4 tank4 11.08.2017 03:51:18
Using the above query as a derived table you can extract the latest per group fill_level value. This way you can apply the logic that computes each range level:
select sum(
CASE
WHEN t1.fill_level>=0 and t1.fill_level<40
THEN 1
ELSE 0
END) AS 'Range A',
sum(
CASE
WHEN t1.fill_level>=40 and t1.fill_level<=79
THEN 1
ELSE 0
END) AS 'Range B',
sum(
CASE
WHEN t1.fill_level>79 and t1.fill_level<=100
THEN 1
ELSE 0
END) AS 'Range C'
from test as t1
join (
select tankId, max(ts) as max_ts
from test
group by tankId
) as t2 on t1.tankId = t2.tankId and t1.ts = t2.max_ts
Output:
Range A Range B Range C
---------------------------
1 0 2 2
Demo here
I get a different result (oh, well, same result as GB):
SELECT GROUP_CONCAT(CASE WHEN fill_level < 40 THEN x.tankid END) range_a
, GROUP_CONCAT(CASE WHEN fill_level BETWEEN 40 AND 75 THEN x.tankid END) range_b
, GROUP_CONCAT(CASE WHEN fill_level > 75 THEN x.tankid END) range_c
FROM test x
JOIN (SELECT tankid,MAX(ts) ts FROM test GROUP BY tankid) y
ON y.tankid = x.tankid AND y.ts = x.ts;
+---------+-------------+-------------+
| range_a | range_b | range_c |
+---------+-------------+-------------+
| NULL | tank3,tank4 | tank1,tank2 |
+---------+-------------+-------------+
EDIT:
If I was solving this problem, and wanted to include the tank names in the result, then I'd probably execute the following...
SELECT x.*
FROM test x
JOIN
( SELECT tankid,MAX(ts) ts FROM test GROUP BY tankid) y
ON y.tankid = x.tankid
AND y.ts = x.ts
...and handle all the other problems, concerning counts, ranges, and missing/'0' values in application code.

Nested SQL Query for count of months

Locked. There are disputes about this question’s content being resolved at this time. It is not currently accepting new answers or interactions.
I am new to SQL and would like to know how to approach writing a query for this question.
Lets say we have these fields:
date_created date_unsubscribed subscriberid
How to write a SQL query that lists, by month, how many people subscribed to the list, unsubscribed from the list, and how many net subscribers there were (new subscribers minus unsubscribers).
All in a single query...
Here's one option using conditional aggregation and union all:
select month(dt),
count(case when subscribe = 1 then 1 end) subscribecount,
count(case when subscribe = -1 then 1 end) unsubscribecountt,
sum(subscribe) overallcount
from (
select date_created as dt, 1 as subscribe
from yourtable
union all
select date_unsubscribed, -1
from yourtable
where date_unsubscribed is not null
) t
group by month(dt)
The subquery creates a list of dates with a flag for subscribe or unsubscribe. Then you can use count with case to determine the appropriate number of subscribers/unsubscribers.
SQL Fiddle Demo
You could write a sum(case) (a sum with conditions) to aggregate - assuming the date_created column is never null. For instance:
ORACLE:
SELECT
TO_CHAR(DATE_CREATED,'MM-YYYY') CREATE_MONTH
,SUM(CASE WHEN date_unsubscribed is not null then 1 else 0 end) unsubscribed
,SUM(CASE WHEN date_unsubscribed is null then 1 else 0 end) subscribed
,COUNT(SUBSCRIBER_ID)
FROM
--YOURTABLENAME
--WHERE
--WHATEVER OTHER CONDITIONS YOU HAVE APPLY
GROUP BY TO_CHAR(DATE_CREATED,'MM-YYYY')
MYSQL:
SELECT
DATE_FORMAT(DATE_CREATED,'%m-%Y') CREATE_MONTH
,SUM(CASE WHEN date_unsubscribed is not null then 1 else 0 end) unsubscribed
,SUM(CASE WHEN date_unsubscribed is null then 1 else 0 end) subscribed
,COUNT(SUBSCRIBER_ID)
FROM
--YOURTABLENAME
--WHERE
--WHATEVER OTHER CONDITIONS YOU HAVE APPLY
GROUP BY DATE_FORMAT(DATE_CREATED,'%m-%Y')
Oracle solution
Here is a query using the PIVOT operator, which was created exactly for this kind of work, and ROLLUP to get the net number. This is just for illustration; I assume the year is a user or application input (bind variable :year, set to 2015 for the output), and I show the summary for January through June.
with
test_data ( date_created, date_unsubscribed, subscriber_id ) as (
select date '2015-05-10', null , 330053448 from dual union all
select date '2015-04-28', null , 330053457 from dual union all
select date '2015-05-10', null , 330053466 from dual union all
select date '2015-04-28', null , 220053475 from dual union all
select date '2015-04-28', date '2015-05-10', 330053484 from dual
),
prep ( type, val, mth ) as (
select 'Subscribed' , 1, extract(month from date_created) from test_data
where extract(year from date_created) = :year
union all
select 'Unsubscribed', -1, extract(month from date_unsubscribed) from test_data
where extract(year from date_unsubscribed) = :year
)
select nvl(type, 'Net Subscr') as description,
nvl(sum(jan), 0) as jan, nvl(sum(feb), 0) as feb, nvl(sum(mar), 0) as mar,
nvl(sum(apr), 0) as apr, nvl(sum(may), 0) as may, nvl(sum(jun), 0) as jun
from prep
pivot (
sum(val)
for mth in (1 as jan, 2 as feb, 3 as mar, 4 as apr, 5 as may, 6 as jun)
)
group by rollup(type)
order by case type when 'Subscribed' then 1 when 'Unsubscribed' then 2 else 3 end
;
DESCRIPTION JAN FEB MAR APR MAY JUN
------------ ---------- ---------- ---------- ---------- ---------- ----------
Subscribed 0 0 0 3 2 0
Unsubscribed 0 0 0 0 -1 0
Net Subscr 0 0 0 3 1 0
3 rows selected.

Get Column Name from table where value is zero

I have a query which returns this data back from lets say DefaultersTable
Select CustomerID, RoleID FROM DefaultersTable Where DefaulterValue = 1
CustomerID, RoleID
10034 34
15481 37
Now I have got another Table "DefaultersDetails" which have individual monthly values of them,
so I do
Select * from DefaultersDetails Where CustomerID = 10034 AND RoleID = 34.
and get the data
CustomerID, RoleID, ValueForJan, ValueForFeb, ValueforMar
10034 34 45 0 32
Please note that I got this Entry in the first case only because one of the Value was 0.
Now How Can I get both the data in single Query, I want something like this
CustomerID, RoleID, ZeroValueForMonth
10034 34 ValueForFeb
15481 37 ValueForJan
I guess it can be done via temprory Tables but I am not sure how to do this
You can use COALESCE function. Try this :
SELECT d.CustomerID, d.RoleID,
COALESCE(detail.JAN,detail.FEB,detail.MAR) AS ZeroValueForMonth
FROM DefaultersTable d
INNER JOIN
(
SELECT CustomerID, RoleID,
(CASE WHEN ValueForJan <> 0 THEN NULL ELSE 'ValueForJan' END) JAN,
(CASE WHEN ValueForFeb <> 0 THEN NULL ELSE 'ValueForFeb' END) FEB,
(CASE WHEN ValueForMar <> 0 THEN NULL ELSE 'ValueForMar' END) MAR
FROM Defaultersdetail
) AS detail
ON detail.CustomerID = d.CustomerID AND detail.RoleID = d.RoleId
where d.DefaulterValue = 1
You can add other month like MAY, JUN etc on subquery like this :
(CASE WHEN ValueForApr <> 0 THEN NULL ELSE 'ValueForApr' END) APR