QOQ growth Mysql - mysql

i have query
select a.`2021`,
b.`2022`,
a.product,
concat(ceil((b.`2022`-a.`2021`)/ a.`2021` * 100), '%') as growth
from ( SELECT SUM(total) as `2021`,
product,
sum
FROM table
WHERE YEAR(month) = 2021
AND case when day(CURRENT_DATE()) > 10
then QUARTER(month) = QUARTER(CURRENT_DATE() - INTERVAL 1 MONTH)
else QUARTER(month) = QUARTER(CURRENT_DATE() - INTERVAL 3 MONTH)
end
GROUP BY Product ,
YEAR(month) )a
JOIN ( SELECT SUM(total) as `2022`,
Product
FROM table
WHERE YEAR(month) = 2022
AND case when day(CURRENT_DATE()) > 10
then QUARTER(month) = QUARTER(CURRENT_DATE() - INTERVAL 1 MONTH)
else QUARTER(month) = QUARTER(CURRENT_DATE() - INTERVAL 3 MONTH)
end
GROUP BY Product ,
YEAR(month) ) b on a.Product=b.Product;
if the current date is not the end of the quarter then the data that appears is the data in the previous quarter period

Related

Value for first available date if no record exisits

i have Items table:
item_id | date | item_price |
---------+-------------+--------------
1 | 2022-12-05 | 15 |
2 | 2022-02-14 | 12 |
1 | 2022-11-12 | 50 |
4 | 2022-01-21 | 13 |
1 | 2021-12-12 | 10 |
6 | 2021-12-27 | 83 |
The query which i use to select price one week ago from today's date:
SELECT
items.item_id AS id,
items.item_price AS weekAgoPrice,
items2.item_price AS monthAgoPrice,
FROM
items
LEFT JOIN
items items2 ON items2.item_id = items.item_id
AND items2.date = DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE
items.item_id = '1'
AND items.date = DATE_SUB(CURDATE(), INTERVAL 7 DAY);
How can i modify query that will return the price from the first available date if there is no entry for a particular date. Those, if for the specified item_id there is no price 7 days ago, then it should return the value of 6 days ago, if not 6 then 5. Additionally, if there is no price 1 month ago, it should return value of 29 days ago etc.
You may try with max window function as the following:
With last_prices As
(
Select *,
Max(Case
When date Between DATE_SUB(CURDATE(), INTERVAL 7 DAY) And CURDATE()
Then date
End) Over (Partition By item_id) As last_price_date_week,
Max(Case
When date Between DATE_SUB(CURDATE(), INTERVAL 1 MONTH) And CURDATE()
Then date
End) Over (Partition By item_id) As last_price_date_month
From items
)
Select item_id, date, item_price, 'last week price' As Price_Type
From last_prices
Where item_id = 1 And date = last_price_date_week
Union All
Select item_id, date, item_price, 'last month price' As Price_Type
From last_prices
Where item_id = 1 And date = last_price_date_month
See demo.
If you are open to using a stored procedure, you could make this task more dynamic, have a look at this procedure:
Create Procedure GetLatesPrice(id INT, period INT, interval_type Varchar(1))
Begin
Select item_id, date, item_price
From
(
Select *,
Case
When interval_type='d' Then
Max(
Case
When date Between DATE_SUB(CURDATE(), INTERVAL period Day) And CURDATE()
Then date
End
) Over (Partition By item_id)
When interval_type='m' Then
Max(
Case
When date Between DATE_SUB(CURDATE(), INTERVAL period Month) And CURDATE()
Then date
End
) Over (Partition By item_id)
When interval_type='d' Then
Max(
Case
When date Between DATE_SUB(CURDATE(), INTERVAL period Year) And CURDATE()
Then date
End
) Over (Partition By item_id)
End As last_price_date
From items
) T
Where date = last_price_date And item_id=id;
END
And to execute that procedure for example for item_id=1, 15 days ago:
Call GetLatesPrice(1, 15, 'd');
-- d for days, m for months, y for years
See demo.
If there is no entry for a particular date, you can use the COALESCE function
SELECT
items.item_id AS id,
COALESCE(
items.item_price,
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 6 DAY)),
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 5 DAY)),
...
) AS price
FROM items
WHERE items.item_id = '1'
SELECT
items.item_id AS id,
COALESCE(
items.item_price,
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 6 DAY)),
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 5 DAY)),
...
) AS price
FROM items
WHERE items.item_id = '1'
If all you need are scalar values then this would suffice:
SET #ItemID = 1;
SELECT (SELECT item_price FROM items WHERE item_id = #ItemID
AND date1 >= DATE_ADD(CURDATE(), INTERVAL -7 DAY) order by date1 LIMIT 1) as WeekAgoPrice,
etc.
Also see the LATERAL documentation.
Without sample output data it's hard to tell what you intend.

Is their an SQL function that can give me both a negative & positive value using the query below?

So effectively what im trying to do is display a value by counting all new users from the current month minus new users from last month then divide the increase by last months number then times by 100. This will get the percentage increase but never displays a negative number, I realise that using abs() converts negative numbers to positive, is their a function that would allow me to do this?
Thanks.
select round(abs
((select count(id) from users where
month(created_at) = month(current_date())
and
YEAR(created_at) = year(current_date()))
-
(select count(id) from users where
month(created_at) = month(current_date - interval 1 MONTH)
and
YEAR(created_at) = year(current_date - interval 1 MONTH)))
/
(select count(id) from users where
month(created_at) = month(current_date())
and
YEAR(created_at) = year(current_date()))
*100, 0)
as abs_diff
from users
limit 1
;
You can get the number of the new users of the current month with:
count(case when last_day(created_at) = last_day(current_date) then 1 end)
and the number of the new users of the previous month with:
count(case when last_day(created_at) = last_day(current_date - interval 1 month) then 1 end)
So divide these numbers and subtract 1 before you multiply by 100:
select
100 * (
count(case when last_day(created_at) = last_day(current_date) then 1 end) /
nullif(count(case when last_day(created_at) = last_day(current_date - interval 1 month) then 1 end), 0)
- 1
) abs_diff
from users
where date(created_at) > last_day(current_date - interval 2 month)
The function nullif() will return null in case the number of the new users of the previous month is 0 to avoid division by 0.
See a simplified demo.
I am thinking something like this:
select ym.*,
(this_month - last_month) * 100.0 / last_month
from (select year(created_at) as yyyy, month(created_at) as mm, count(*) as this_month,
lag(count(*)) over (order by min(created_at)) as prev_month
from users
group by yyyy, mm
) ym;
Window functions are much simpler method to do month-over-month comparisons.
If you only want this for the current month, you can add:
order by yyyy desc, mm desc
limit 1

MYSQL Subtracting two SELECT Queries

Using MYSQL, I have written two big SELECT queries combined by a UNION, to get 2 rows, where the first row is the count for the current month, and the second row is the count for the previous month. The Query is as follows:
select * from
(select count(*) as type1 from table_x where nationality_id = 23 and month(START_DATE) = month(now())) as t1,
(select count(*) as type2 from table_x where nationality_id = 24 and month(START_DATE) = month(now())) as t2,
(select count(*) as type3 from table_x where nationality_id = 25 and month(START_DATE) = month(now())) as t3,
(select count(*) as type4 from table_x where nationality_id = 26 and month(START_DATE) = month(now())) as t4
UNION
select * from
(select count(*) as type1 from table_x where nationality_id = 23 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t1,
(select count(*) as type2 from table_x where nationality_id = 24 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t2,
(select count(*) as type3 from table_x where nationality_id = 25 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t3,
(select count(*) as type4 from table_x where nationality_id = 26 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t4
I want to add a third row, which is the difference between row 2 and row 1.
How can I do this with my current query?
You are obviously doing a compare between current and prior month. So, I would start with my inner pre-query aggregate getting only those transactions >= the first of the prior month AND the records within the nationality IDs you are looking for.
The inner date_sub() of DAYOFMONTH() -1 day gives you the first of the CURRENT month. By subtracting one more month, gives you the first of the LAST month.
Now you can aggregate the totals per nationality compared to current month or not. That inner query gives you all begin and end counts. Now that is wrapped to the outer and you can get all the counts in addition to the differences... Obviously you can change the column names respectively.
select
PQ.*,
PQ.ThisMonth23 - PQ.LastMonth23 = Diff23,
PQ.ThisMonth24 - PQ.LastMonth24 = Diff24,
PQ.ThisMonth25 - PQ.LastMonth25 = Diff25,
PQ.ThisMonth26 - PQ.LastMonth26 = Diff26
from
( select
sum( case when t.Nationality_id = 23 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth23,
sum( case when t.Nationality_id = 24 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth24,
sum( case when t.Nationality_id = 25 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth25,
sum( case when t.Nationality_id = 26 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth26,
sum( case when t.Nationality_id = 23 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth23,
sum( case when t.Nationality_id = 24 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth24,
sum( case when t.Nationality_id = 25 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth25,
sum( case when t.Nationality_id = 26 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth26
from
table_x t
where
t.StartDate >= date_sub( date_sub( t.StartDate, interval DAYOFMONTH( t.StartDate ) -1 DAY ), interval 1 MONTH )
AND t.Nationality_id IN ( 23, 24, 25, 26 )
) PQ
I would just add that your query might be getting more than you think... You are asking for ALL records Ex: January REGARDLESS of the year, compared to ALL records December REGARDLESS of the year because all you are qualifying is based on the MONTH() and no YEAR() consideration. I am explicitly querying back only current and prior month.

How to select same column of a table twice as two different columns seperated date wise

i have the following mysql query , but its taking approx 5 minutes to execute, how can i decrease its execution time
select
revenue_center_group.description as rc_group,
revenue_center.description as revenue_center,
count(ytd.members) as total_ytd_members,
count(lytd.members) as total_lytd_members
from revenue_center
left join revenue_center_group on revenue_center_group.id = revenue_center.revenue_center_group_id
left join (
select membership.id as members,
membership.rc_id as rc_id
from membership
where membership.status =1
and DATE(membership.join_date)>= (SELECT DATE_FORMAT((SELECT DATE_FORMAT(date(now()) - INTERVAL 1 MONTH, '%Y-%m-%d')) ,'%Y-01-01 00:00:00'))
and DATE(membership.join_date)<= (SELECT DATE_FORMAT(LAST_DAY(date(now()) - INTERVAL 1 MONTH), '%Y-%m-%d 23:59:59'))ytd on revenue_center.id = ytd.rc_id
left join (
select
membership.id as members,
membership.rc_id as rc_id
from membership
where membership.status =1
and DATE(membership.join_date)>= (SELECT DATE_FORMAT((SELECT DATE_FORMAT((SELECT DATE_FORMAT(date(now()) - INTERVAL 1 MONTH, '%Y-%m-%d'))
- INTERVAL 1 YEAR, '%Y-%m-%d')) ,'%Y-01-01 00:00:00'))
and DATE(membership.join_date)<= (SELECT DATE_FORMAT((select DATE_FORMAT(LAST_DAY(date(now()) - INTERVAL 1 MONTH), '%Y-%m-%d')
- INTERVAL 1 YEAR),'%Y-%m-%d 23:59:59'))lytd on revenue_center.id = lytd.rc_id
group by revenue_center_group.description,revenue_center.description with rollup;

run multiple mysql queries as 1 query

im trying to take 3 sql queries and insert them into 1 table without getting the null value's and using a group by number as to not get duplicate numbers in the same column.
I have the issue where running query 1 leaves me with a bunch of null data values
and running query 2 doesnt group the numbers resulting in thousands of rows numbers only go up to 100
QUERY 1
insert into table ( number)
select number as 1day from table where date = CURDATE() - interval 1day group by number
insert into table ( number)
select number as 2day from table where date = CURDATE() - interval 1day group by number
insert into table ( number)
select number as 7day from table where date = CURDATE() - interval 1day group by number
so i try to run
QUERY 2
insert into table (number,number,number)
select
*
from
(select number as 1day from test.test where date = curdate() - interval 1 day group by
number) as 1day,
(select number as 2day from test.test where date > curdate() - interval 2 day group by
number) as 2day,
(select number as 7day from test.test where date > curdate() - interval 7 day group
by number) as 7day;
try the below:
insert into table (number,number,number)
select
table.1day,table.2day,table.7day
from
((select number from test.test where date = curdate() - interval 1 day group by
number) as 1day,
(select numberfrom test.test where date > curdate() - interval 2 day group by
number) as 2day,
(select number from test.test where date > curdate() - interval 7 day group
by number) as 7day) as table
select (case one.number when two.number then null else one.number end) as '1day',(case two.number <= third.number when true then (case one.number = two.number when true then null else two.number end) else (case one.number = two.number when false then null else two.number end) end) as '2day',(case (third.number < one.number and third.number = two.number) when true then null else third.number end) as '7day'
from (
(select x.number
from (
(select number,'1day' as 'type' from testtable where date = curdate() - interval 1 day group by number)
union all
(select number,'2day' as 'type' from testtable where date > curdate() - interval 2 day group by number)
union all
(select number,'7day' as 'type' from testtable where date > curdate() - interval 7 day group by number)) as x
where x.type='2day' order by x.number) as two,
(select x.number
from (
(select number,'1day' as 'type' from testtable where date = curdate() - interval 1 day group by number)
union all
(select number,'2day' as 'type' from testtable where date > curdate() - interval 2 day group by number)
union all
(select number,'7day' as 'type' from testtable where date > curdate() - interval 7 day group by number)) as x
where x.type='1day' order by x.number) as one,
(select x.number
from (
(select number,'1day' as 'type' from testtable where date = curdate() - interval 1 day group by number)
union all
(select number,'2day' as 'type' from testtable where date > curdate() - interval 2 day group by number)
union all
(select number,'7day' as 'type' from testtable where date > curdate() - interval 7 day group by number)) as x
where x.type='7day' order by x.number) as third
)
where ((one.number = two.number) or (one.number is null or two.number is null)) or
((third.number = two.number) or (two.number is null or third.number is null))