mysql subquery for charts - mysql

I have 2 tables with the following data
inviteTable
inviteDate | contractAmount | status
2014-01-01 1500 awarded
2015-01-01 2000 awarded
2015-01-02 4000 closed
2015-02-01 6000 awarded
2015-02-02 8000 awarded
2015-03-01 8500 awarded
quoteTable
quoteDate | quoteAmount | status
2014-03-01 1500 awarded
2015-01-02 2000 awarded
2015-01-03 4000 sent
2015-01-04 6000 awarded
2015-02-03 8000 awarded
2015-02-10 8500 sent
2015-02-11 9000 awarded
2015-03-01 9500 awarded
I want to make ONE query that gives me the following data structure
month | quotedTotal | quotedCount | awardedTotal | awardedCount
January 8000 2 2000 1
February 17000 2 14000 2
March 9500 1 8500 1
currently i have written this code...but it doesn't work
SELECT
MONTHNAME(t1.due_date) as month,
(select sum(`amount`) from quoteTable where awarded= 1 ) as estimatedAmount,
sum(t1.contactAmount) AS sumContactAmount,
COUNT(*) as `noOfAwarded`
FROM inviteTable t1
where
t1.status = "Awarded" and
t1.inviteDate between '2014-11-01' and '2015-10-31'
GROUP BY MONTH(due_date)
;
basically I want to have ONE query that gives me the following:
volume that got awarded - sum of contractAmount and status awarded
count of how many awarded - count of inviteTable.status=awarded
volume that got quoted - sum of quoteAmount and status awarded
count of how many quoted - count of quotedTable.status=awarded
all grouped by month and in a date range
my query doesn't work. Your help would be highly appreciated as I am stuck!

Your schema is unfortunate; it would be easier if you have just one table with an extra column to record what type of record you have. However, if you first collect the two tables into one table via union all, you can use a fairly simple query to produce the results to want:
select
monthname(_date) month,
sum(quoteAwarded * amount) quotedTotal,
sum(quoteAwarded) quotedCount,
sum(contractAwarded * amount) awardedTotal,
sum(contractAwarded) awardedCount
from (select inviteDate _date, contractAmount amount, status = 'awarded' contractAwarded, 0 quoteAwarded from inviteTable
union all
select quoteDate, quoteAmount, 0, status = 'awarded' from quoteTable) x
where _date between '2014-11-01' and '2015-10-31'
group by 1
order by month(_date)
See SQLFiddle using your sample data, and producing you expected output.
At the core of this query is the convenient fact that (in mysql) "true" is 1 and "false" is 0 - allowing the use of sum() instead of count(), and more importantly avoiding the use of case by using sum() over some simple math.

Here is a way you can do it, note that you have dates in both tables and you need to use join preferably left join , so this will ensure that the dates present in the left table is always returned in this case the month name.
select
t1.month,
t1.awardedTotal,
t1.awardedCount,
t2.quotedTotal,
t2.quotedCount
from(
select
monthname(t1.inviteDate) as month,
month(t1.inviteDate) as m,
sum(
case
when t1.status = 'awarded' then t1.contractAmount else 0
end
) as awardedTotal,
sum(
case
when t1.status = 'awarded' then 1 else 0
end
) as awardedCount
from inviteTable t1
where t1.inviteDate between '2014-11-01' and '2015-10-31'
group by month
)t1
left join(
select
monthname(t2.quoteDate) as month,
sum(
case
when t2.status = 'awarded' then t2.quoteAmount else 0
end
) as quotedTotal,
sum(
case
when t2.status = 'awarded' then 1 else 0
end
) as quotedCount
from quoteTable t2
where t2.quoteDate between '2014-11-01' and '2015-10-31'
group by month
)t2 on
t1.month = t2.month
order by t1.m
http://sqlfiddle.com/#!9/a863a/10
UPDATE:
Yes since you want dates present in both tables it can not be done with the above process since it always consider one table as left table and will get all the dates from that table. For getting data for all the dates across both tables you first need to get the dates from both tables and then using left join get the calculation part something as
select
t1.month,
coalesce(t2.awardedTotal,0) as awardedTotal,
coalesce(t2.awardedCount,0) as awardedCount,
coalesce(t3.quotedTotal,0) as quotedTotal,
coalesce(t3.quotedCount,0) as quotedCount
from(
select month(inviteDate) as m,monthname(inviteDate) as month
from inviteTable where inviteDate between '2014-11-01' and '2015-10-31'
union
select month(quoteDate) as m,monthname(quoteDate) as month
from quoteTable where quoteDate between '2014-11-01' and '2015-10-31'
)t1
left join(
select
monthname(inviteDate) as month,
sum(
case
when status = 'awarded' then contractAmount else 0
end
) as awardedTotal,
sum(
case
when status = 'awarded' then 1 else 0
end
) as awardedCount
from inviteTable
group by month
)t2 on t1.month = t2.month
left join(
select
monthname(quoteDate) as month,
sum(
case
when status = 'awarded' then quoteAmount else 0
end
) as quotedTotal,
sum(
case
when status = 'awarded' then 1 else 0
end
) as quotedCount
from quoteTable
group by month
)t3 on t1.month=t3.month
order by t1.m
http://sqlfiddle.com/#!9/ddaac/14

Related

A query for getting results separated by a date gap

ID
TIMESTAMP
1
2020-01-01 12:00:00
2
2020-02-01 12:00:00
3
2020-05-01 12:00:00
4
2020-06-01 12:00:00
5
2020-07-01 12:00:00
I am looking for a way to get records in a MySQL database that are within a certain range of each other. In the above example, notice that there is a month between the first two records, then a three month gap, before we see another three records with a month between.
What is a way to group these into two result sets, so I will get Ids 1, 2 and 3, 4, 5 A solution using days would be probably work the best as thats easier to modify.
You can use lag() and then logic to see where a gap is big enough to start a new set of records. A cumulative sum gives you the groups you want:
select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t;
If you want to summarize this with a start and end date:
select min(timestamp), max(timestamp)
from (select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t
) t
group by grouping;
For example, the following query:
select group_concat(ID)
from (
select w1.ID,w1.TS,w2.ID flag
from work1 w1 left outer join work1 w2
on timestampdiff(month,w2.TS,w1.TS)=1
order by w1.ID
) w
group by
case when flag is null then #str:=ID else #str end
See db fiddle

Combining data from multiple rows into one

First question on here, so i will try my best to be clear.
I have 2 tables:
"TABLE1" which contains a record for each stock code and a list of attributes.
In TABLE 1 there is just one record for each stock_code
"TABLE2" which contains a log of changes to attributes of products, over time.
"TABLE2" contains the following fields:.
stock_code
stock_attribute
old_value
new_value
change_date
change_time
TABLE 2 has multiple entries ofr each stock_code.
Every time a stock item is change, another entry is made in Table2, with the attribute that has changed, the change date, time, old value and new value.
I want to create a query which will result in a table that has one record for each stock_code (from TABLE 1), and a column for each week over past year, with the value in each field being the last recorded "new_val" for that week (From TABLE 2)
I have tried
SELECT a.`stcode`, b.`week1`, b.`week2`, b.`week3`, b.`week4` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) THEN newval END)week1,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 356 DAY) AND DATE_SUB(CURDATE(),INTERVAL 350 DAY) THEN newval END)week2,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 349 DAY) AND DATE_SUB(CURDATE(),INTERVAL 343 DAY) THEN newval END)week3,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 342 DAY) AND DATE_SUB(CURDATE(),INTERVAL 336 DAY) THEN newval END)week4,
(etc
etc
etc
FROM (SELECT * from TABLE 2 ORDER BY "chngdate" DESC, "chngtime" DESC )as sub) as b ON b.stcode = s.stcode
ORDER BY stcode ASC
The trouble is with this, i get multiple lines for a stock_code which has mutliple entries....
for example, for stock_code abc123 the result i get is
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 null null 4 null null null
abc123 2 null null null null null
abc123 null null null null 3 null
what i WANT is this:
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 2 null 4 null 3 null
I have also tried the following, but teh query took so long, it never finished (there were 52 derived tables!)
SELECT a.`stcode`, w1.`new_value`, w2.`new_value`, w3.`new_value`, w4.`new_value` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
LEFT JOIN (SELECT stcode, depot, fieldname, chngdate, chngtime, newval from STDepotAmendmentsLog WHERE chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) ORDER BY "chngdate" DESC, "chngtime" DESC)as w1 on s.stcode = w1.stcode
etc for week 2, 3, 4 etc etc
You could do the following:
Find the greatest date for each "week"
Find the rows corresponding to those dates
Use conditional aggregation to convert rows into columns
Here is a rough outline of the code. It assumes that e.g. if today is 2020-03-03 then week 52 is from 2020-02-26 to 2020-03-03. Adjust if necessary:
SELECT t.stock_code
, MAX(CASE WHEN weeknum = 51 THEN new_value END) AS week01
, MAX(CASE WHEN weeknum = 50 THEN new_value END) AS week02
, MAX(CASE WHEN weeknum = 1 THEN new_value END) AS week51
, MAX(CASE WHEN weeknum = 0 THEN new_value END) AS week52
FROM table2 AS t
JOIN (
SELECT stock_code
, DATEDIFF(CURRENT_DATE, change_date) div 7 AS weeknum -- count multiples of 7
, MAX(change_date) AS greatest_date
GROUP BY stock_code, weeknum
FROM table2
) AS a ON t.stock_code = a.stock_code AND t.change_date = a.greatest_date
GROUP BY t.stock_code

How can one calculate percentage from previous week in aggregate query?

I have a challenge I set out to do that seemed initially trivial. Not so for my developper brain.
Consider the following simple view, used to validate a cron that queries a subset of 200 000 statements every saturday.
It goes as follows:
mysql> SELECT
-> DATE_FORMAT(s.created, "%Y-%m-%d") as "Date",
-> count(s.id) AS "Accounts credited",
-> sum(s.withdrawal) "Total Credited"
-> -- 100 * (sum(s.withdrawal) - sum(prev.withdrawal))
-- / sum(prev.withdrawal) "Difference in %"
-> FROM statements s
-> -- LEFT JOIN prev
-> -- s.created - interval 7 DAY
-> -- ON prev.created = s.created - interval 7 DAY
-- AND (prev.status_id = 'OPEN'
-- OR prev.status_id = 'PENDING')
-> WHERE (s.status_id = 'OPEN' OR s.status_id = 'PENDING')
-> GROUP BY YEAR(s.created), MONTH(s.created), DAY(s.created)
-> ORDER BY s.created DESC
-> LIMIT 8;
+------------+-------------------+----------------+
| Date | Accounts credited | Total Credited |
+------------+-------------------+----------------+
| 2019-01-19 | 18175 | 3173.68 |
| 2019-01-12 | 18135 | 4768.43 |
| 2019-01-05 | 17588 | 6968.49 |
| 2018-12-29 | 17893 | 5404.18 |
| 2018-12-22 | 17353 | 7048.18 |
| 2018-12-15 | 16893 | 7181.34 |
| 2018-12-08 | 16220 | 9547.09 |
| 2018-12-01 | 15476 | 7699.59 |
+------------+-------------------+----------------+
8 rows in set (0.79 sec)
As is, the query is efficient and practical. I merely would like to add a column, difference in percentage, from previous week's total, as seen with the -- commented out code.
I have tried various approaches, but because of the GROUP BY, adding an inline column to get the sum(withdrawal) of previous week makes the query run ... forever.
I then tried the LEFT JOIN approach, but this has the same problem, Obviously. I think the added JOIN has to fetch the sum of previous week for every row of the outer select.
I then had the (not so smart) idea of querying my view, even but then it seems I would have the same issue.
I assume there are much more optimal approaches out there to this simple task.
Is there an elegant way to calculate a percentage from such a query?
Would a stored procedure or some other 'non-plain-sql' approach be more optimal?
I used this query in SQL Server:
SELECT TOP 8
DATE_FORMAT(s.created, "%Y-%m-%d") as "Date",
count(s.id) AS "Accounts credited",
sum(s.withdrawal) "Total Credited",
100 * (sum(s.withdrawal) - sum(s1.withdrawal)) / sum(s1.withdrawal) "Difference in %"
FROM statements s
LEFT JOIN statements s1 ON s1.created = s.created - 7
AND (s1.status_id = 'OPEN' OR s1.status_id = 'PENDING')
WHERE (s.status_id = 'OPEN' OR s.status_id = 'PENDING')
GROUP BY YEAR(s.created), MONTH(s.created), DAY(s.created)
ORDER BY s.created DESC
Your just handle null or zero s1.withdrawal.
I wish it work.
If you are happy with your original query then a correlated sub query like so may be all you need
select t.*,
(select totalcredited from t t1 where t1.dt < t.dt order by t1.dt desc limit 1) prev,
(
totalcredited / (select totalcredited from t t1 where t1.dt < t.dt order by t1.dt desc limit 1) * 100
) -100 as chg
from (your query) as t;
I've noticed a mistake in my previous example so here's an update.
NOTE: the query compares the current week with the previous one.
I hope that this is what you need.
SELECT
Date,
SUM(CASE week WHEN 0 THEN accounts_credited ELSE 0 END) AS 'Accounts credited',
SUM(CASE week WHEN 0 THEN total_credited ELSE 0 END) AS 'Total Credited',
100 * (
SUM(CASE week WHEN 0 THEN total_credited ELSE 0 END) - SUM(CASE week WHEN 1 THEN total_credited ELSE 0 END)
) / SUM(CASE week WHEN 1 THEN total_credited ELSE 0 END) AS 'Difference in %'
FROM
(SELECT
DATE_FORMAT(created, '%Y-%m-%d') as 'Date',
COUNT(id) AS 'accounts_credited',
SUM(withdrawal) 'total_credited',
0 AS 'week'
FROM
statements
WHERE
status_id IN ('OPEN','PENDING')
AND
YEARWEEK(created, 1) = YEARWEEK(CURDATE(), 1)
GROUP BY
DATE(created)
UNION
SELECT
DATE_FORMAT(created, '%Y-%m-%d') as 'Date',
COUNT(id) AS 'accounts_credited',
SUM(withdrawal) 'total_credited',
1 AS 'week'
FROM
statements
WHERE
status_id IN ('OPEN','PENDING')
AND
(
DATE(created) >= CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY
AND
DATE(created) < CURDATE() - INTERVAL DAYOFWEEK(CURDATE())-1 DAY
)
GROUP BY
DATE(created)
) AS tmp
ORDER BY Date
GROUP BY Date
This is your query:
select date_format(s.created, '%Y-%m-%d') as "Date",
count(*) AS "Accounts credited",
sum(s.withdrawal) "Total Credited"
from statements s
where s.status_id in ('OPEN', 'PENDING')
group by date_format(s.created, '%Y-%m-%d')
order by s.created desc
limit 8;
In MySQL, perhaps the simplest solution is variables. However, because of the rules around MySQL variables, this is a bit complicated:
select s.*,
(case when (#new_prev := #prev) = NULL then NULL -- never gets here
when (#prev := Total_Credited) = NULL then NULL -- never gets here
else #new_prev
end) as previous_week_Total_Credited
from (select date_format(s.created, '%Y-%m-%d') as "Date",
count(*) AS Accounts_credited,
sum(s.withdrawal) as Total_Credited
from statements s
where s.status_id in ('OPEN', 'PENDING')
group by date_format(s.created, '%Y-%m-%d')
order by "Date" desc
) s cross join
(select #prev := NULL) params
limit 8;
You can then just use this as a subquery for your final calculation.

SQL Query to find rows that didn't occur this month

I am trying to find the number of sellers that made a sale last month but didn't make a sale this month.
I have a query that works but I don't think its efficient and I haven't figured out how to do this for all months.
SELECT count(distinct user_id) as users
FROM transactions
WHERE MONTH(date) = 12
AND YEAR(date) = 2015
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
AND transactions.user_id NOT IN
(
SELECT distinct user_id
FROM transactions
WHERE MONTH(date) = 1
AND YEAR(date) = 2016
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
)
The structure of the table is:
+---------+------------+-------------+--------+
| user_id | date | status | amount |
+---------+------------+-------------+--------+
| 1 | 2016-01-01 | 'COMPLETED' | 1.00 |
| 2 | 2015-12-01 | 'COMPLETED' | 1.00 |
| 3 | 2015-12-01 | 'COMPLETED' | 2.00 |
| 1 | 2015-12-01 | 'COMPLETED' | 3.00 |
+---------+------------+-------------+--------+
So in this case, users with ID 2 and 3, didn't make a sale this month.
Use conditional aggregation:
SELECT count(*) as users
FROM
(
SELECT user_id
FROM transactions
-- 1st of previous month
WHERE date BETWEEN SUBDATE(SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1), interval 1 month)
-- end of current month
AND LAST_DAY(CURRENT_DATE)
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
GROUP BY user_id
-- any row from previous month
HAVING MAX(CASE WHEN date < SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1)
THEN date
END) IS NOT NULL
-- no row in current month
AND MAX(CASE WHEN date >= SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1)
THEN date
END) IS NULL
) AS dt
SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1) = first day of current month
SUBDATE(first day of current month, interval 1 month) = first day of previous month
LAST_DAY(CURRENT_DATE) = end of current month
if you want to generify it, you can use curdate() to get current month, and DATE_SUB(curdate(), INTERVAL 1 MONTH) to get last month (you will need to do some if clause for January/December though):
SELECT count(distinct user_id) as users
FROM transactions
WHERE MONTH(date) = MONTH(DATE_SUB(curdate(), INTERVAL 1 MONTH))
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
AND transactions.user_id NOT IN
(
SELECT distinct user_id
FROM transactions
WHERE MONTH(date) = MONTH(curdate())
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
)
as far as efficiency goes, I don't see a problem with this one
The following should be pretty efficient. In order to make it even more so, you would need to provide the table definition and and the EXPLAIN.
SELECT COUNT(DISTINCT user_id) users
FROM transactions t
LEFT
JOIN transactions x
ON x.user_id = t.user_id
AND x.date BETWEEN '2016-01-01' AND '2016-01-31'
AND x.status = 'COMPLETED'
AND x.amount > 0
WHERE t.date BETWEEN '2015-12-01' AND '2015-12-31'
AND t.status = 'COMPLETED'
AND t.amount > 0
AND x.user_id IS NULL;
Just some input for thought:
You could create aggregated lists of user-IDs per month, representing all the unique buyers in that month. In your application, you would then simply have to subtract the two months in question in order to get all user-IDs that have only made a sale in one of the two months.
See below for query- and post-processing-examples.
In order to make your query efficient, I would recommend at least a 2-column index for table transactions on [status, amount]. However, in order to prevent the query from having to look up data in the actual table, you could even create a 4-column index [status, amount, date, user_id], which should further improve the performance of your query.
Postgres (v9.0+, tested)
SELECT (DATE_PART('year', t.date) || '-' || DATE_PART('month', t.date)) AS d,
STRING_AGG( DISTINCT t.user_id::TEXT, ',' ) AS buyers
FROM transactions t
WHERE t.status = 'COMPLETED'
AND t.amount > 0
GROUP BY DATE_PART('year', t.date),
DATE_PART('month', t.date)
ORDER BY DATE_PART('year', t.date),
DATE_PART('month', t.date)
;
MySQL (not tested)
SELECT (YEAR(t.date) || '-' || MONTH(t.date)) AS d,
GROUP_CONCAT( DISTINCT t.user_id ) AS buyers
FROM transactions t
WHERE t.status = 'COMPLETED'
AND t.amount > 0
GROUP BY YEAR(t.date), MONTH(t.date)
ORDER BY YEAR(t.date), MONTH(t.date)
;
Ruby (example for post-processing)
db_result = ActiveRecord::Base.connection_pool.with_connection { |con| con.execute( db_query ) }
unique_buyers = db_result.map{|e|[e['d'],e['buyers'].split(',')]}.to_h
buyers_dec15_but_not_jan16 = unique_buyers['2015-12'] - unique_buyers['2016-1']
buyers_nov15_but_not_dec16 = unique_buyers['2015-11']||[] - unique_buyers['2015-12']
...(and so on)...

showing previous and current month data in table using mysql

I am trying to show three different figures of the same column In a mysql query, I would like to keep one month static: April, so it would be a case like this I want to show The current month, the previous month and the static month of the year I'm working with, in this case let us stick with 2012
Example
Tablename:payment
id , pay_date, amount
1 2012-02-12 1000
2 2012-03-11 780
3 2012-04-15 890
4 2012-05-12 1200
5 2012-06-12 1890
6 2012-07-12 1350
7 2012-08-12 1450
So what I want to do is show the column amount for the month of April as I said I want to keep that row static: 890, the current month lets say the current month is August:1450 and the previous month amount which would be July:1350: so the final result would be something like this:
april_amount current_month_amount previous_month_amount
890 1450 1350
However I'm stuck here:
select amount as april_amount
from payment
where monthname(pay_date) LIKE 'April'
and year(pay_date) LIKE 2012
I hope the question is written clear enough, and thanks alot for the help much appreciated.
If the results can be rows instead of columns:
SELECT MONTHNAME(pay_date), amount FROM payment
WHERE pay_date BETWEEN '2012-04-01'
AND '2012-04-30'
OR pay_date BETWEEN CURRENT_DATE
- INTERVAL DAYOFMONTH(CURRENT_DATE) - 1 DAY
AND LAST_DAY(CURRENT_DATE)
OR pay_date BETWEEN CURRENT_DATE
- INTERVAL DAYOFMONTH(CURRENT_DATE) - 1 DAY
- INTERVAL 1 MONTH
AND LAST_DAY(CURRENT_DATE - INTERVAL 1 MONTH)
See it on sqlfiddle.
I might be way off here. But try:
select top 1
p.amount, c.amount, n.amount
from payment c
inner join payment p ON p.pay_date < c.pay_date
inner join payment n ON n.pay_date > c.pay_date
where monthname(c.paydate) LIKE 'April'
and year(c.pay_date) LIKE 2012
order by p.pay_date DESC, n.pay_date ASC
EDIT, I didnt read your question properly. I was going for previous, current, and next month. 1 minute and I'll try again.
select top 1
p.amount AS april_amount, c.amount AS current_month_amount, n.amount AS previous_month_amount
from payment c
inner join payment p ON monthname(p.pay_date) = 'April' AND year(p.pay_date) = 2012
inner join payment n ON n.pay_date > c.pay_date
where monthname(c.paydate) = monthname(curdate())
and year(c.pay_date) = year(curdate())
order by n.pay_date ASC
This assumes there is only 1 entry per month.
Ok, so i haven't written in mysql for a while. here is what worked for your example data:
select
p.amount AS april_amount, c.amount AS current_month_amount, n.amount AS previous_month_amount
from payment AS c
inner join payment AS p ON monthname(p.pay_date) LIKE 'April' AND year(p.pay_date) LIKE 2012
inner join payment AS n ON n.pay_date < c.pay_date
where monthname(c.pay_date) LIKE monthname(curdate())
and year(c.pay_date) LIKE year(curdate())
order by n.pay_date DESC
limit 1
the previous month table joined is counterintuitively named n, but this works. I verified it in a WAMP install.
To handle aggregates per month you can use subselects. Performance may suffer on very large tables (millions of rows or more).
SELECT SUM( a.amount ) AS april_amount,
(
SELECT SUM( c.amount )
FROM payment c
WHERE MONTH( c.pay_date ) = MONTH( CURDATE( ) )
) AS current_month_amount,
(
SELECT SUM( p.amount )
FROM payment p
WHERE MONTH( p.pay_date ) = MONTH( CURDATE( ) - INTERVAL 1
MONTH )
) AS previous_month_amount
FROM payment a
WHERE MONTHNAME( a.pay_date ) = 'April'
AND YEAR( a.pay_date ) =2012