MySQL month over month totals compared to previous month - mysql

I'd like to sum the amount column for a given month, as well as the total for the previous month. Right now the first month's total works, but every month thereafter is too high by at least 2 order of magnitude.
SELECT month, year, total, previous_month, previous_year, previous_total FROM (
SELECT MONTH(p1.start_date) AS month,
YEAR(p1.start_date) AS year,
SUM(p1.amount) AS total,
SUM(p2.amount) AS previous_total,
MONTH(DATE_SUB(p1.start_date, INTERVAL 1 MONTH)) AS previous_month,
YEAR(DATE_SUB(p1.start_date, INTERVAL 1 MONTH)) AS previous_year
FROM trackings p1
LEFT JOIN trackings p2 ON EXTRACT(YEAR_MONTH FROM DATE_SUB(p1.start_date, INTERVAL 1 MONTH)) = EXTRACT(YEAR_MONTH FROM p2.start_date)
GROUP BY EXTRACT(YEAR_MONTH FROM p1.start_date)
) AS p3;
Related question: I'm doing the same DATE_SUB 3 times. Is there a way to clean up the date related parts to be more efficient?
Sample: http://sqlfiddle.com/#!2/3013a/1

Think the problem is you are getting rows counted multiple times on the join. Ie, you have a sum of all the amounts for a month, but each row from that month is being joined with all rows for the previous month.
Solution using a couple of subselect:-
SELECT ThisMonth.MONTH, ThisMonth.YEAR, ThisMonth.TOTAL, PrevMonth.MONTH AS PREVIOUS_MONTH, PrevMonth.YEAR AS PREVIOUS_YEAR, PrevMonth.TOTAL AS PREVIOUS_TOTAL
FROM
(
SELECT MONTH(start_date) AS `month`,
YEAR(start_date) AS `year`,
EXTRACT(YEAR_MONTH FROM start_date) AS YearMonth,
SUM(amount) AS total
FROM trackings
GROUP BY `month`, `year`, YearMonth
) ThisMonth
LEFT OUTER JOIN
(
SELECT MONTH(start_date) AS `month`,
YEAR(start_date) AS `year`,
EXTRACT(YEAR_MONTH FROM DATE_ADD(start_date, INTERVAL 1 MONTH)) AS YearMonth,
SUM(amount) AS total
FROM trackings
GROUP BY `month`, `year`, YearMonth
) PrevMonth
ON ThisMonth.YearMonth = PrevMonth.YearMonth

Related

SQL: Getting null values on comparing current values to previous period

I have copied this SQL query from another website and I'm not actually sure why it returns null values for the previous period and the month comparing it with. What should I adjust in the query to get the desired result?
SQL Query:
WITH monthly_metrics AS (
SELECT EXTRACT(year from day) as year,
EXTRACT(month from day) as month,
SUM(revenue) as revenue
FROM daily_metrics
GROUP BY 1,2
)
SELECT year AS current_year,
month AS current_month,
revenue AS revenue_current_month,
LAG(year,12) OVER ( ORDER BY year, month) AS previous_year,
LAG(month,12) OVER ( ORDER BY year, month) AS month_comparing_with,
LAG(revenue,12) OVER ( ORDER BY year, month) AS revenue_12_months_ago,
revenue - LAG(revenue,12) OVER (ORDER BY year, month) AS month_to_month_difference
FROM monthly_metrics
ORDER BY 1,2;
Query Result:
dbfiddle

MySQL Get Orders From Last 12 Weeks Monday to Sunday

I have a table that stores each order made by a user, recording the date it was made , the amount and the user id. I am trying to create a query that returns the weekly transactions from Monday to Sunday for the last 12 weeks for a particular user. I am using the following query:
SELECT COUNT(*) AS Orders,
SUM(amount) AS Total,
DATE_FORMAT(transaction_date,'%m/%Y') AS Week
FROM shop_orders
WHERE user_id = 123
AND transaction_date >= now()-interval 3 month
GROUP BY YEAR(transaction_date), WEEKOFYEAR(transaction_date)
ORDER BY DATE_FORMAT(transaction_date,'%m/%Y') ASC
This produces the following result:
This however does not return the weeks where the user has made 0 orders, does not sum the orders from Monday to Sunday and does not return the weeks ordered from 1 to 12. Is there a way to achieve these things?
One way to accomplish this is with an self outer join (in this case, I use a right outer join, but of course a left outer join would work as well).
To start your weeks on Monday, subtract the result of WEEKDAY from your column transaction_date with DATE_SUB, as proposed in the most upvoted answer here.
SELECT
COALESCE(t1.Orders, 0) AS `Orders`,
COALESCE(t1.Total, 0) AS `Total`,
t2.Week AS `Week`
FROM
(
SELECT
COUNT(*) AS `Orders`,
SUM(amount) AS `Total`,
DATE(DATE_SUB(transaction_date, INTERVAL(WEEKDAY(transaction_date)) DAY)) AS `Week`
FROM
shop_orders
WHERE 1=1
AND user_id = 123
AND transaction_date >= NOW() - INTERVAL 12 WEEK
GROUP BY
3
) t1 RIGHT JOIN (
SELECT
DATE(DATE_SUB(transaction_date, INTERVAL(WEEKDAY(transaction_date)) DAY)) AS `Week`
FROM
shop_orders
WHERE
transaction_date >= NOW() - INTERVAL 12 WEEK
GROUP BY
1
ORDER BY
1
) t2 USING (Week)
To return the weeks with no Orders you have to create a table with all the weeks.
For the order order by the same fields in the group by

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;

Combine two select statements grouped by date returning two columns of data

This seems like an easy task but my basic sql knowledge is failing me as I'm still learning.
Basically, I'm trying to combine:
SELECT DATE(created) DATE, COUNT(DISTINCT created) newpost FROM surveys
WHERE created >= Last_day(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND created < last_day(CURRENT_DATE) + INTERVAL 1 DAY GROUP BY DATE(created);
and
SELECT DATE(TIMESTAMP) DATE,subs FROM trafficstats
WHERE TIMESTAMP >= LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND TIMESTAMP < LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY;
into one query that will return data, grouped by date, into two additional columns - newposts and subs.
I've tried using UNION, which doesn't seem to be giving me the output I want. It combined the data into one column (newpost), and also didn't group by date.
I'm still fairly new to writing MySQL queries, and I've tried searching for answers to no avail. Hoping to seek the knowledge of those smarter than me here.
You could use JOIN
select t1.DATE, t1.newpost, t2.subs
from (
SELECT DATE(created) DATE, COUNT(DISTINCT created) newpost
FROM surveys
WHERE created >= Last_day(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND created < last_day(CURRENT_DATE) + INTERVAL 1 DAY
GROUP BY DATE(created)
) t1
left join (
SELECT DATE(TIMESTAMP) DATE, subs
FROM trafficstats
WHERE TIMESTAMP >= LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND TIMESTAMP < LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY
) t2 on t1.DATE = t2.DATE
I guess you want one row per distinct date, with two different count values shown.
This kind of query is slightly tricker than it seems at first glance, because the two summary queries might have different sets of dates.
So you need to start with a subquery that yields all possible dates of interest. You then need to LEFT JOIN each summary query to it. You must use LEFT JOIN instead of the ordinary inner JOIN, because LEFT JOIN doesn't suppress rows from the right side of the join when they don't match any rows from the left side.
Here goes:
All your dates. Notice the UNION operation is a setwise (duplicate-removing) union operation.
SELECT DISTINCT DATE(created) DATE FROM newpost
WHERE created >= Last_day(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND created < last_day(CURRENT_DATE) + INTERVAL 1 DAY
UNION
SELECT DISTINCT DATE(TIMESTAMP) DATE FROM trafficstats
WHERE TIMESTAMP >= LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND TIMESTAMP < LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY
Then you need your two summary subqueries. The first one is this. Notice that I changed COUNT(DISTINCT created) to COUNT(*) because I don't understand the logic behind the DISTINCT there. Can you have more than one row for a single post; do you tell them apart by timestamp? If you have a row for each post you should COUNT(*).
SELECT DATE(created), COUNT(*) newposts
FROM newpost
GROUP BY DATE(created)
The second summary is this. Again, I counted rows.
SELECT DATE(TIMESTAMP), COUNT(*) subs
FROM trafficstats
GROUP BY DATE(TIMESTAMP)
Finally, join those three subqueries like so. You get the dates from the first subquery, and the summary-by-date information from the second two subqueries.
SELECT dates.DATE, posts.newposts, subs.subs
FROM ( /* date subquery */ ) dates
LEFT JOIN ( /* posts subquery */ ) posts ON dates.DATE = posts.DATE
LEFT JOIN ( /* subs subquery */ ) subs ON dates.DATE = subs.DATE
ORDER BY dates.DATE
Putting it all together:
SELECT dates.DATE, posts.newposts, subs.subs
FROM (
SELECT DISTINCT DATE(created) DATE FROM newpost
WHERE created >= Last_day(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND created < last_day(CURRENT_DATE) + INTERVAL 1 DAY
UNION
SELECT DATE(TIMESTAMP) DATE FROM trafficstats
WHERE TIMESTAMP >= LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY - INTERVAL 1 MONTH
AND TIMESTAMP < LAST_DAY(CURRENT_DATE) + INTERVAL 1 DAY
) dates
LEFT JOIN (
SELECT DATE(created), COUNT(*) newposts
FROM newpost
GROUP BY DATE(created)
) posts ON dates.DATE = posts.DATE
LEFT JOIN (
SELECT DATE(TIMESTAMP), COUNT(*) subs
FROM trafficstats
GROUP BY DATE(TIMESTAMP)
) subs ON dates.DATE = subs.DATE
ORDER BY dates.DATE

getting multi row data into columns in mysql

SELECT
Day,
month,
year,
GROUP_CONCAT(total),
GROUP_CONCAT(SP_ID)
FROM
(
SELECT
DATE_FORMAT(l.act_date, '%d') AS DAY,
DATE_FORMAT(l.act_date, '%M') AS MONTH,
EXTRACT(YEAR FROM l.act_date) AS YEAR,
COUNT(*) as total,l.sp_id
FROM lead_activity2 as l
right outer join salesperson as s on l.sp_id=s.sp_id
WHERE l.act_name='scb'
AND ((l.act_date>='2012-09-07 13:03:27' )
AND (l.act_date<= '2012-11-07 13:03:27'))
GROUP BY MONTH, YEAR, DAY, l.sp_id
ORDER BY YEAR DESC, MONTH DESC, DAY DESC, l.sp_id DESC
) t GROUP BY day, month, year
http://sqlfiddle.com/#!2/1514d/3 - you can view the scheme and the query,
what i would like to get is
18 | october | 2012 | 0,0,1,1 | 6,5,4,3
spid 6 and spid 5 have no data for 18 october but still should be shown tried doing right join and right outer join both dont seem to work...
Use GROUP_CONCAT like so:
SELECT
Day,
month,
year,
GROUP_CONCAT(total),
GROUP_CONCAT(SP_ID)
FROM
(
SELECT
DATE_FORMAT(l.act_date, '%d') AS DAY,
DATE_FORMAT(l.act_date, '%M') AS MONTH,
EXTRACT(YEAR FROM l.act_date) AS YEAR,
COUNT(*) as total,l.sp_id
FROM lead_activity2 as l
WHERE l.act_name='scb'
AND ((l.act_date>='2012-09-07 13:03:27' )
AND (l.act_date<= '2012-11-07 13:03:27'))
GROUP BY MONTH, YEAR, DAY, l.sp_id
ORDER BY YEAR DESC, MONTH DESC, DAY DESC, l.sp_id DESC
) t GROUP BY day, month, year
Updated SQL Fiddle
Update: Yes you can do this, but use LEFT JOIN to include non matching sp_id. These non matching ids will have a value of NULL use IFNULL to display it with zeros like so:
SELECT
Day,
month,
year,
GROUP_CONCAT(total) Total,
GROUP_CONCAT(SP_ID) 'List of sp_ids'
FROM
(
SELECT
DATE_FORMAT(l.act_date, '%d') AS DAY,
DATE_FORMAT(l.act_date, '%M') AS MONTH,
EXTRACT(YEAR FROM l.act_date) AS YEAR,
COUNT(*) as total,
IFNULL(s.sp_id , 0) sp_id
FROM lead_activity2 as l
LEFT JOIN salesperson s ON l.sp_id = s.sp_id
WHERE l.act_name='scb'
AND ((l.act_date>='2012-09-07 13:03:27' )
AND (l.act_date<= '2012-11-07 13:03:27'))
GROUP BY MONTH, YEAR,DAY,s.sp_id
) t
ORDER BY YEAR DESC,
MONTH DESC,
DAY DESC,
sp_id DESC
Updates SQL Fiddle Demo