MYSQL Select priority data if exist - mysql

My current data :
Month Price A Price B Status Approval
January 1000 2000 1 0
February 1000 2000 1 0
March 1000 2000 1 0
April 1000 2000 1 0
May 1000 2000 1 0
June 1000 2000 1 0
July 1000 2000 1 0
August 1000 2000 1 0
September 1000 2000 1 0
October 1000 3000 1 0
October 2000 2000 2 0
October 3000 2000 2 1
November 1000 2000 1 0
December 1000 2000 1 0
*Status 1 = Not Changed, Status 2 = Changed, Approval 1 = Approved
What i'm trying to show,is when month > 1 (my case is October)
,IF STATUS = 2 AND APPROVAL = 1 IS NOT EXIST,Show data with
STATUS = 1 AND APPROVAL = 0ELSE SHOW STATUS = 2 AND APPROVAL =
1 Using group by, doesn't work,it shows first data if month more
than 1
My Query :
SELECT A.NAME AS MONTH,
IFNULL( B.PRICE_A, 0 ) PRICE_A,
IFNULL( B.PRICE_B, 0 ) PRICE_B,
STATUS,
APPROVAL,
FROM
REF_MONTH A
LEFT OUTER JOIN (
SELECT
SUBSTR( PERIOD, '5,2' ) MONTH,
ROUND( PRICE_A_FIX, 2 ) PRICE_A,
ROUND( PRICE_B_FIX, 2 ) PRICE_B,
A.STATUS,
A.APPROVAL,
FROM
PRICE_MONTH_LIST A
WHERE
SUBSTR( PERIOD, 1, 4 ) = 2018
) B ON B.MONRH= A.MONTH
WHERE DATE_FORMAT( STR_TO_DATE( CONCAT( 2018, month), '%Y%m' ), '%Y%m' ) <=
DATE_FORMAT( SYSDATE( ), '%Y%m' )
GROUP BY A.MONTH
ORDER BY A.ID;
My Expected Result :
Month Price A Price B Status Approval
January 1000 2000 1 0
February 1000 2000 1 0
March 1000 2000 1 0
April 1000 2000 1 0
May 1000 2000 1 0
June 1000 2000 1 0
July 1000 2000 1 0
August 1000 2000 1 0
September 1000 2000 1 0
October 3000 2000 2 1
November 1000 2000 1 0
December 1000 2000 1 0

Below would be the query -
with table1 as
(
select 'January' as month, 1000 as priceA, 2000 as priceB, 1 as status, 0 as approval from dual
union all
select 'October' as month, 1000 as priceA, 3000 as priceB, 1 as status, 0 as approval from dual
union all
select 'October' as month, 2000 as priceA, 2000 as priceB, 2 as status, 0 as approval from dual
union all
select 'October' as month, 3000 as priceA, 2000 as priceB, 2 as status, 1 as approval from dual
)
,t1 as
(
select month, count(*) as cnt, min(status) as min_status,
max(status) max_status, min(approval) min_app, max(approval) max_app from table1
group by month
)
,t2 as
(
select
month,
case when cnt>1 and max_status<>2 and max_app<>1 then min_status else max_status end as status1,
case when cnt>1 and max_status<>2 and max_app<>1 then min_app else max_app end as approval1
from t1
)
select
t.month,
t.priceA,
t.priceB,
t.status,
t.approval
from table1 t
inner join t2 on t.month=t2.month and t.status=t2.status1 and t.approval=t2.approval1;
result -
January 1000 2000 1 0
October 3000 2000 2 1
As there were no insert scripts, I have use CTE with oralce db.
Hope this helps.

Related

Using count(*) .. Over(*) in mysql

My data looks like the following,
requestedDate Status
2020-04-21 APPROVED
2020-04-23 APPROVED
2020-04-27 PENDING
2020-05-21 PENDING
2020-06-01 APPROVED
I would like to extarct a report that looks like the following where the count is by status and month.
Status StatusCount Month MonthCount CountTotal
APPROVED 2 APR 3 5
PENDING 1 MAY 1 5
APPROVED 1 JUN 1 5
My sql looks like the following,
select distinct
status,
count(status) over (partition by status) as total_by_status,
CASE
WHEN Month(requestedDate) = 1 THEN 'JAN'
WHEN Month(requestedDate) = 2 THEN 'FEB'
WHEN Month(requestedDate) = 3 THEN 'MAR'
WHEN Month(requestedDate) = 4 THEN 'APR'
WHEN Month(requestedDate) = 5 THEN 'MAY'
WHEN Month(requestedDate) = 6 THEN 'JUN'
WHEN Month(requestedDate) = 7 THEN 'JUL'
WHEN Month(requestedDate) = 8 THEN 'AUG'
WHEN Month(requestedDate) = 9 THEN 'SEP'
WHEN Month(requestedDate) = 10 THEN 'OCT'
WHEN Month(requestedDate) = 11 THEN 'NOV'
WHEN Month(requestedDate) = 12 THEN 'DEC'
END AS myMONTH,
count(Month(requestedDate)) over (partition by Month(requestedDate)) as total_by_month,
count(*) over () as Totals
from Reports
where
requestedDate between DATE_SUB(CURDATE(), INTERVAL 120 DAY) and date(CURDATE())
order by 1;
The output for that looks like,
status total_by_status myMONTH total_by_month Totals
APPROVED 3 APR 3 5
APPROVED 3 JUN 1 5
PENDING 2 APR 3 5
PENDING 2 MAY 1 5
dbfiddle
First you need a valid aggregation query. Then you can use window functions on top of it (here, you would typically compute window sums of the counts).
I would write this as:
select
status,
count(*) status_count,
date_format(requestedDate, '%b') requested_month
sum(count(*)) over(partition by year(requestedDate), month(requestedDate)) month_count,
sum(count(*)) over() total_count
from reports
where requestedDate between current_date - interval 120 day and current_date
group by status, year(requestedDate), month(requestedDate), date_format(requestedDate, '%b')
Since it is just for last 120 days (last years same month wouldnt occur) so we can also use distinct instead of group by), something like below:
select distinct status,
count(*) over (partition by status) as total_by_status,
date_format(requestedDate, '%b') mymonth,
count(Month(requestedDate)) over (partition by Month(requestedDate)) as total_by_month,
count(*) over () as total_by_month
from reports
where requestedDate between current_date - interval 120 day and current_date
order by status, mymonth
Demo

SQL printing all in one row

I want to print data in two columns, like:
Count Category
But my code prints all in one row, like:
Count Category Count Category
Any tips?
Select sum(Case when population >= 1000000 and population < 5000000 then 1 else 0 end) as Count, '1 000 000 - 4 999 999' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, '100 000 - 499 999' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, '500 000 - 999 999' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, 'Under 100 000' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, 'Over 5 million' as Category
from cities
Select sum(Case when population >= 1000000 and population < 5000000 then 1 else 0 end) as Count, '1 000 000 - 4 999 999' as Category FROM cities
UNION
SELECT sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, '100 000 - 499 999' as Category FROM cities
and so on...
Concat all single resuts in one result table with UNION keyword
your else in case part should be everywhere 0 not 1 if you just want to count the cities...
A Better solution anyway would be
SELECT COUNT(cities.primKey) as Count, '1 000 000 - 4 999 999' as Category FROM cities WHERE population >= 1000000 and population < 5000000
UNION
SELECT COUNT(cities.primKey) as Count, '1 000 000 - 4 999 999' as Category FROM cities WHERE population >= 500000 and population < 100000
UNION ...
SELECT CASE WHEN Population >= 5000000
THEN 'Over 5 million'
WHEN Population >= 1000000 AND Population < 5000000
THEN '1 000 000 - 4 999 999'
WHEN Population >= 500000 AND Population < 1000000
THEN '500 000 - 999 999'
WHEN Population >= 100000 AND Population < 500000
THEN '100 000 - 499 999'
WHEN Population < 100000
THEN 'Under 100 000'
END [Category]
,COUNT(*) [Count]
FROM Cities
If you want to get all categories even if there are no cities that belong to it, you can do:
select sum(if(ci.population is null, 0, 1)) as 'Count', cat.Category
from (
select 0 as 'low', 100000 as 'high', 'Under 100 000' as Category, 1 as 'ord'
union
select 100000, 499999, '100 000 - 499 999', 2
union
select 500000, 999999, '500 000 - 999 999', 3
union
select 1000000, 4999999, '1 000 000 - 4 999 999', 4
union
select 5000000, null, 'Over 5 million' , 5
) as cat
left join cities ci on ci.population between cat.low and ifnull(cat.high, ci.population)
group by cat.Category, cat.ord
order by cat.ord

MySQL - How to populate weekly data in a month

How to select data from following table group by weeks in a month
Date Project Value Week
+----------+--------------+-------+------+
2018-11-07 A 2 45
2018-11-08 B 4 45
2018-11-09 C 3 45
2018-11-12 B 6 46
2018-11-13 A 5 46
2018-11-14 C 6 46
(First week is end on sunday or week number in a month)
So my result should look like this.
Project 1st Week 2nd Week 3rd Week 4th Week 5th Week
+----------+--------+--------+--------+--------+--------
A 0 2 5 0 0
B 0 4 6 0 0
C 0 3 6 0 0
I try this one :
SELECT project, value, week, date
FROM module_progress
WHERE
created_at BETWEEN '2018-11-01 00:00:00' AND '2018-11-31
AND date > DATE_SUB(NOW(), INTERVAL 1 WEEK) 23:59:59'
GROUP BY week
Thank you
Just use a sub query to get first week no for the month, and sum case statements for each week:
select year(date) as y, month(date) as m, project,
sum(case when week=w0 then value else 0 end) as w1,
sum(case when week=w0+1 then value else 0 end) as w2,
sum(case when week=w0+2 then value else 0 end) as w3,
sum(case when week=w0+3 then value else 0 end) as w4,
sum(case when week=w0+4 then value else 0 end) as w5
from #date d
join (select year(date) as y, month(date) as m, min(week) as w0 from #date group by year(date), month(date))
as d0 on d0.y=year(date) and d0.m=month(date)
group by year(date), month(date), project

Calculating Cumulative values using MySQL

I have this table:
Month_Year MV MI
----------------------------------------------------------
August 2016 3 100
October 2016 2 150
September 2016 1 100
January 2017 4 200
I need to create the next output table
Month_Year MV AMV MI AMI
----------------------------------------------------------
January 2016 0 0 0 0
...
July 2016 0 0 0 0
August 2016 3 3 100 100
September 2016 1 4 100 200
October 2016 2 6 150 350
November 2016 0 6 0 350
December 2016 0 6 0 350
January 2017 4 10 200 550
At month M, for column AMV, AMV is the ccumulative value of all the AV ones before that month and month M. For example, AMV is 4 on 'September 2016' because in 'August 16' AV is 3 and 1 for 'September 2016'. Similarly for AMI. How can this be done?. Notice that the Month_Year column is not necessarily ordered. Thanks
ADDITIONAL INFO
After using the DATE_FORMAT(original_Date, '%Y %m') I was able to convert the first table to:
Month_Year MV MI
----------------------------------------------------------
2016 08 3 100
2016 10 2 150
2016 09 1 100
2017 01 4 200
Could this simplify the problem? Why not to use DATE_FORMAT(original_Date, '%m %Y')?
The month-year format is a big big NO, and you have to somehow overcome it. For this example I create a MEMORY table months, and I use several joins with this table.
create table months (month_year_int int, month_year varchar(30)) engine=memory;
insert months
select 201601 as month_year_int, 'January 2016' as month_year
union all select 201602, 'February 2016'
union all select 201603, 'March 2016'
union all select 201604, 'April 2016'
union all select 201605, 'May 2016'
union all select 201606, 'June 2016'
union all select 201607, 'July 2016'
union all select 201608, 'August 2016'
union all select 201609, 'September 2016'
union all select 201610, 'October 2016'
union all select 201611, 'November 2016'
union all select 201612, 'December 2016'
union all select 201701, 'January 2017'
;
All these would have been avoided if you had used a proper model for your data. Anyway, this is a solution whis does not use the My-Sql proprietary variable pattern (a rextester demo here):
select
x.month_year,
coalesce(t.mv, 0) MV,
sum(y.mv) as AMV,
coalesce(t.mi, 0) AMV,
sum(y.mi) as AMI from
(
select
m.month_year_int,
m.month_year,
coalesce(t.mv, 0) as mv,
coalesce(t.mi) as mi
from months m left join test t on m.month_year = t.month_year
) x
left join
(
select
m.month_year_int,
m.month_year,
coalesce(t.mv, 0) as mv,
coalesce(t.mi) as mi
from months m
left join test t on m.month_year = t.month_year
) y on x.month_year_int > y.month_year_int - 1
left join test t on x.month_year = t.month_year
group by x.month_year_int
order by x.month_year_int
;

Running total per id within sliding time window in MySql

I need a query that can tell me number of idObj for the given idTest for Yesterday, 2 day before and 3 day before.
Number of idObj flagged 2 day before should include idObj flagged Yesterday i.e. It only should display number of idObj flagged yesterday as well as 2 day before.
Number of idOjb flagged 3 day before should include idObj flagged Yesterday, 2 day before and 3 day before.
I also need the name of the column as date instead of 'DayBeforeYest' and 'TwoDayBefore'.
Below is what I am looking for:
idTest 2013-06-29 2013-06-28 2013-06-27
104 9 7 5
105 7 6 2
106 5 3 0
Here, on 2013-06-29, idObj counts includes those idObj that has been flagged on only 2013-06-29. On 2013-06-28, idObj count includes those idObj that has been flagged on 2013-06-29 and on 2013-06-28. On 2013-06-27, idObj count includes those idObj that has been flagged on
2013-06-29, 2013-06-28 and 2013-06-27. Therefore, 3 days ago column will have less idObj counts compared to yesterday.
Query
create table tblTest (dateFact date, idTest int, idObj int);
insert into tblTest values
('2013-06-29', 104, 4433), ('2013-06-29', 105, 3345), ('2013-06-29', 106, 5543),
('2013-06-28', 104, 4433), ('2013-06-28', 105, 3345), ('2013-06-28', 106, 4356),
('2013-06-27', 104, 3439), ('2013-06-07', 105, 3345), ('2013-06-07', 106, 8955);
Below is the query I came up with but it just counts number of idObj flagged on 2nd and 3rd day per idTest. It does not take into account of idObj flagged in prevous days. It also doesn't display column name in the date format.
select idTest, max(Yest) as Yest, max(DayBeforeYest) as DayBeforeYest, max(TwoDayBefore) as TwoDayBefore from
(
(select idTest, count(idObj) as Yest, 0 as DayBeforeYest, 0 as TwoDayBefore from tblTest
where dateFact =date_sub(curdate(), interval 1 day) group by idTest)
union
(select idTest, 0 as Yest, count(idObj) DayBeforeYest, 0 as TwoDayBefore from tblTest
where dateFact = date_Sub(curdate(), interval 2 day) group by idTest)
union
(select idTest, 0 as Yest, 0 as DayBeforeYest , count(idObj) TwoDayBefore from tblTest
where dateFact = date_sub(curdate(), interval 3 day) group by idTest) )x
group by idTest;
Thank you!
====================================
Edited:
create table tblTest (dateFact date, idTest int, idObj int);
INSERT INTO tblTest
select CURDATE() - INTERVAL 1 DAY, 104, 4433 UNION ALL
SELECT CURDATE() - INTERVAL 1 DAY, 105, 3345 UNION ALL
SELECT CURDATE() - INTERVAL 1 DAY, 106, 5543 UNION ALL
SELECT CURDATE() - INTERVAL 2 DAY, 104, 4433 UNION ALL
SELECT CURDATE() - INTERVAL 2 DAY, 105, 3345 UNION ALL
SELECT CURDATE() - INTERVAL 2 DAY, 106, 4356 UNION ALL
SELECT CURDATE() - INTERVAL 3 DAY, 104, 3439 UNION ALL
SELECT CURDATE() - INTERVAL 3 DAY, 105, 3345 UNION ALL
SELECT CURDATE() - INTERVAL 3 DAY, 106, 8955;
For the given example, output should be as below:
idTest 2013-06-30 2013-06-29 2013-06-28
104 1 1 0
105 1 1 1
106 1 0 0
On 2013-06-30 for idTest 104, we have 1 idObj 4433. On 2013-06-29 for idTest 104 we have 1 idObj 4433 which is also in 2013-06-30 for idTest 104.
On 2013-06-28 for idTest 104 we have 1 idObj 3439 which is not in either 2013-06-30 or 2013-06-29 for idTest 104. Therefore, you will see row values for 104 to be 1 1 0.
On 2013-06-30 for idTest 105, we have 1 idObj 3345. On 2013-06-29 for idTest 105 we have 1 idObj 3345 which is also in 2013-06-30 idTest 105.
On 2013-06-28 for idTest 105, we have 1 idObj 3345 which is also in 2013-06-30 and 2013-06-29. Therefore, you will see row values to be 1 1 1.
And so on...
On 2013-06-28, to count the idObj, it should be present in 2013-06-28, 213-06-29, 2013-06-30.
On 2013-06-29, to count the idObj, it should be present in 2013-06-29 and 2013-06-30.
On 2013-06-30, to count the idObj, it should be present in 2013-06-30.
UPDATED To make column names to be dates you have to use dynamic SQL.
SET #sql = CONCAT(
'SELECT idTest
, SUM(d1) `', DATE_FORMAT(CURDATE() - INTERVAL 1 DAY, '%Y-%m-%d'),
'`, SUM(d2 = 1 AND d1 = 1) `', DATE_FORMAT(CURDATE() - INTERVAL 2 DAY, '%Y-%m-%d'),
'`, SUM(d3 = 1 AND d2 = 1 AND d1 = 1) `', DATE_FORMAT(CURDATE() - INTERVAL 3 DAY, '%Y-%m-%d'),
'` FROM
(
SELECT idTest
,idObj
,SUM(CASE WHEN dateFact = CURDATE() - INTERVAL 1 DAY THEN 1 ELSE 0 END) d1
,SUM(CASE WHEN dateFact = CURDATE() - INTERVAL 2 DAY THEN 1 ELSE 0 END) d2
,SUM(CASE WHEN dateFact = CURDATE() - INTERVAL 3 DAY THEN 1 ELSE 0 END) d3
FROM tblTest
WHERE dateFact BETWEEN CURDATE() - INTERVAL 3 DAY AND CURDATE() - INTERVAL 1 DAY
GROUP BY idTest, idObj
) q
GROUP BY idTest');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Sample output:
| IDTEST | 2013-07-02 | 2013-07-01 | 2013-06-30 |
-------------------------------------------------
| 104 | 1 | 1 | 0 |
| 105 | 1 | 1 | 1 |
| 106 | 1 | 0 | 0 |
Here is SQLFiddle demo
To make life easier on client (calling) side you can wrap this code into a stored procedure
DELIMITER $$
CREATE PROCEDURE sp_test_report()
BEGIN
-- put above mentioned code here
END$$
DELIMITER ;
And then use it
CALL sp_test_report();
Here is SQLFiddle demo