Month to Month Growth (same column) - mysql

I have looked at my other solutions and none seem to work for me. I have a very simple question.
I have a table named foreclosures. It has 3 columns named: foreclosures_id, period, foreclosures. Period is the unique primary key. Datatype for foreclosures column is Decimal.
My current query is:
SELECT
MONTHNAME(period) AS Reported_Month,
YEAR(period) AS Reported_Year,
FORMAT(foreclosures, 0) AS Foreclosures,
(SELECT FORMAT(SUM(f2.foreclosures),0) FROM foreclosures f2
WHERE f2.period <= f1.period AND f1.foreclosures IS NOT NULL)
AS YTD_Total,
MONTHNAME(DATE_SUB(period, INTERVAL 2 MONTH)) AS Real_Month,
YEAR(DATE_SUB(period, INTERVAL 2 MONTH)) AS Real_Year
FROM foreclosures f1;
I am trying to produce the following "Growth" column. Please help?
+----------------+---------------+--------------+------------+
| Reported_Month | Reported_Year | Foreclosures | Growth % |
+----------------+---------------+--------------+------------+
| January | 2016 | 201 | |
| February | 2016 | 332 | 65.2% |
| March | 2016 | 240 | -27.7% |
| April | 2016 | 369 | 53.8% |
+----------------+---------------+--------------+------------+

You can get the foreclosures for the previous period using similar logic to the cumulative sum:
SELECT MONTHNAME(period) AS Reported_Month,
YEAR(period) AS Reported_Year,
FORMAT(foreclosures, 0) AS Foreclosures,
(SELECT f2.foreclosures
FROM foreclosures AS f2
WHERE f2.period < f.period AND
f.foreclosures IS NOT NULL
ORDER BY f2.period DESC
) as prev_foreclosures
FROM foreclosures f;
For the growth measure, just apply the arithmetic that you would use for that calculation.

Related

Group Rows in group by though it contains NULL value in mysql / postgres

I have a table from where I am getting month names and some quantity measures.
Table Name = Month_Name
SELECT month_name,q1,q2 FROM month_name;
mysql> SELECT * FROM MONTH;
+------------+------+------+
| month_name | q1 | q2 |
+------------+------+------+
| January | 10 | 20 |
| March | 30 | 40 |
| March | 10 | 5 |
+------------+------+------+
Expected Output:
mysql> SELECT month_name ,SUM(q1),SUM(q2) FROM MONTH GROUP BY month_name;
+------------+---------+---------+
| month_name | sum(q1) | sum(q2) |
+------------+---------+---------+
| January | 10 | 20 |
| Febuary | 0 | 0 |
| March | 40 | 45 |
| April | 0 | 0 |
+------------+---------+---------+
Group by month will not print February and April since these 2 months are not present in base table. I do not want to use Union All since there will be performance issues with union All, Is there any other optimised approach to this.
You can use a calendar table which keeps track of all the month names which you want to appear in your report.
SELECT
m1.month_name,
SUM(q1) AS q1_sum,
SUM(q2) AS q2_sum
FROM
(
SELECT 'January' AS month_name UNION ALL
SELECT 'February' UNION ALL
SELECT 'March' UNION ALL
...
SELECT 'December'
) m1
LEFT JOIN month m2
ON m1.month_name = m2.month_name
GROUP BY
m1.month_name;
Note that while this solve your immediate problem, it is still not ideal, because we don't have any easy way to sort the months. A much better table design would be to maintain a date column. The month name is easily derived from the date.

mysql sum group by month and date using a contract start and end date

I have a table full of monthly contracts. There is a monthly price, a start date, and an end date for each. I am trying to graph each month's total revenue and am wondering if it's possible to do this in one query (vs. a query for each month).
I know how to group by month and year in mysql, but this requires a more complex solution that "understands" whether to include in the sum for a given month/year based on the start and end date of the contract.
Shorthand example
| contract_id | price | start_date | end_date |
| 1 | 299 | 1546318800 (1/1/19) | 1554004800 (3/31/19) |
| 2 | 799 | 1551416400 (3/1/19) | 1559275200 (5/31/19) |
With this example, there's an overlap in March. Both contracts are running in March, so the sum returned for that month should be 1098.
I'd like to be able to produce a report that includes every month between two dates, so in this case I'd send 1/1/19 - 12/31/19, the full year of 2019 and would hope to see 0 results as well.
| month | year | price_sum |
| 1 | 2019 | 299 |
| 2 | 2019 | 299 |
| 3 | 2019 | 1098 |
| 4 | 2019 | 799 |
| 5 | 2019 | 799 |
| 6 | 2019 | 0 |
| 7 | 2019 | 0 |
| 8 | 2019 | 0 |
| 9 | 2019 | 0 |
| 10 | 2019 | 0 |
| 11 | 2019 | 0 |
| 12 | 2019 | 0 |
Here is a full working script for your problem, which uses a calendar table approach to represent every month in 2019. Specifically, we represent each month using the first of that month. Then, a given price from your table is applicable to that month if there is overlap with the start and end range.
WITH yourTable AS (
SELECT 1 AS contract_id, 299 AS price, '2019-01-01' AS start_date, '2019-03-31' AS end_date UNION ALL
SELECT 2, 799, '2019-03-01', '2019-05-31'
),
dates AS (
SELECT '2019-01-01' AS dt UNION ALL
SELECT '2019-02-01' UNION ALL
SELECT '2019-03-01' UNION ALL
SELECT '2019-04-01' UNION ALL
SELECT '2019-05-01' UNION ALL
SELECT '2019-06-01' UNION ALL
SELECT '2019-07-01' UNION ALL
SELECT '2019-08-01' UNION ALL
SELECT '2019-09-01' UNION ALL
SELECT '2019-10-01' UNION ALL
SELECT '2019-11-01' UNION ALL
SELECT '2019-12-01'
)
SELECT
d.dt,
SUM(t.price) AS price_sum
FROM dates d
LEFT JOIN yourTable t
ON d.dt < t.end_date
AND DATE_ADD(d.dt, INTERVAL 1 MONTH) > t.start_date
GROUP BY
d.dt;
Demo
Notes:
If your dates are actually stored as UNIX timestamps, then just call FROM_UNIXTIME(your_date) to convert them to dates, and use the same approach I gave above.
I had to use the overlapping date range formula here, because the criteria for overlap in a given month is that the range of that month intersects the range given by a start and end date. Have a look at this SO question for more information on that.
My code is for MySQL 8+, though in practice you may wish to create a bona fide calendar table (the CTE version of which I called dates above), which contains the range of months/years which you want to cover your data set.
I understand that you will be given a range of dates for which you will need to report. My solution requires you to initialize a temporary table, such as date_table with the first day of each month for which you want to report on:
create temporary table date_table (
d date,
primary key(d)
);
set #start_date = '2019-01-01';
set #end_date = '2019-12-01';
set #months = -1;
insert into date_table(d)
select DATE_FORMAT(date_range,'%Y-%c-%d') AS result_date from (
select (date_add(#start_date, INTERVAL (#months := #months +1 ) month)) as date_range
from mysql.help_topic a limit 0,1000) a
where a.date_range between #start_date and last_day(#end_date);
Then this should do it:
select month(dt.d) as month, year(dt.d) as year, ifnull(sum(c.price), 0) as price_sum
from date_table dt left join contract c on
dt.d >= date(from_unixtime(c.start_date)) and dt.d < date(from_unixtime(c.end_date))
group by dt.d
order by dt.d
;
Resulting in:
+-------+------+-----------+
| month | year | price_sum |
+-------+------+-----------+
| 1 | 2019 | 299 |
| 2 | 2019 | 299 |
| 3 | 2019 | 1098 |
| 4 | 2019 | 799 |
| 5 | 2019 | 799 |
| 6 | 2019 | 0 |
| 7 | 2019 | 0 |
| 8 | 2019 | 0 |
| 9 | 2019 | 0 |
| 10 | 2019 | 0 |
| 11 | 2019 | 0 |
| 12 | 2019 | 0 |
+-------+------+-----------+
See demo
I am not sure about the semantics of the column end_date. Right now I am comparing the first a follows: start_date <= first_of_month < end_date. Perhaps the test should be start_date <= first_of_month <= end_date, in which case:
dt.d >= date(from_unixtime(c.start_date)) and dt.d < date(from_unixtime(c.end_date))
becomes:
dt.d between date(from_unixtime(c.start_date)) and date(from_unixtime(c.end_date))
With end_date being the last day of the month, it would not matter either way.

multi sql select statement

i have a table of this sort:
| name | salary | day | month |
| james | 200.00 | 2 | january |
| marie | 400.00 | 4 | january |
| jimmy | 300.00 | 7 | january |
| Fredd | 700.00 | 3 | february |
| rosieli | 500.00 | 5 | february |
| rosela | 800.00 | 6 | february |
if the table name is 'db_table', how do I write an sql select query to select records from 4th January to 5th February.
something like:
select * from db_table between day='4',month='january' and day='5' and month='february'";
please how do I write a proper sql statement to get the desired results.so that the table looks like this:
| name | salary | day | month |
| marie | 400.00 | 4 | january |
| jimmy | 300.00 | 7 | january |
| Fredd | 700.00 | 3 | february |
| rosieli | 500.00 | 5 | february |
thank you
You'll need to make the day a number but this is it:
SELECT *
FROM db_table
WHERE (day >= 4 and month = 'January')
OR (day <= 5 and month = 'February')
For e.g. January to April:
SELECT *
FROM db_table
WHERE (day >= 4 and month = 'January')
OR (day <= 5 and month = 'April')
OR month IN ('February','March')
You really should do this using dates.
select t.*
from t
where str_to_date(concat_ws(2020, month, day), '%Y %M %d') between '2020-01-04' and '2020-02-05';
When possible, date comparisons should be made using dates.
I used 2020 because it is a leap year, so it will handle February 29th.
Once you've solved this, you should fix your data model to contain an actual date rather than a month/day combination.
how do I write an sql select query to select records from 4th January to 5th February.
select *
from db_table
where (month = 'january' and 4 <= cast(day as int)) or
(month = 'february' and cast(day as int) <= 5)
Note that a table design with separate month and day columns makes querying hard. It would get even harder at year boundaries. A better design would make use of your database's native datetime column type. Then you can query like:
select *
from db_table
where dt_col between '2019-01-04' and '2019-02-05'
You must create a comparable string out of month and day to use in a between statement:
select * from db_table where
concat(case month
when 'january' then '01'
when 'february' then '02'
........................
when 'december' then '12'
end, case when day < 10 then '0' else '' end, day) between '0104' and '0205'
Like this you can compare any date range by modifying only the starting and ending dates.

UPDATE + SET + WHERE - Dynamic minimum value

This is a follow-up to:
Dynamic minimum value for specfic range (mysql)
I do have the query to fetch the third column (lowest of the last 3 days) "Low_3_days" via SELECT command:
-----------------------------------------
| Date | Unit_ | Lowest_in_last_|
| | price | 3_days |
|----------------------------------------
| 2015-01-01 | 15 | 15 |
| 2015-01-02 | 17 | 15 |
| 2015-01-03 | 21 | 15 |
| 2015-01-04 | 18 | 17 |
| 2015-01-05 | 12 | 12 |
| 2015-01-06 | 14 | 12 |
| 2015-01-07 | 16 | 12 |
|----------------------------------------
select S.Date,Unit_price,
(select S.Date, Unit_price,
(SELECT min(s2.Unit_Price)
FROM table s2
WHERE s2.DATE BETWEEN s.DATE - interval 3 day and
s.DATE - interval 1 day
) as min_price_3_days
FROM table S;
My new challenge is - what is the best way to use UPDATE-SET-WHERE so I could add the ("Lowest_in_last_3_days") values to a new column in a table (instead of having temporary results displayed to me via SELECT).
By following the UPDATE-SET-WHERE syntax, the query would be:
UPDATE table
SET min_price_3_days =
(select S.Date, Unit_price,
(SELECT min(s2.Unit_Price)
FROM table s2
WHERE s2.DATE BETWEEN s.DATE - interval 3 day and
s.DATE - interval 1 day
) as min_price_3_days
but I have difficulties constructing the correct query.
What would be the correct approach to this? I do recognize this one is a tough one to solve.
Your UPDATE should look like:
update table set low_3_days=
(SELECT min(Unit_Price)
FROM (select unit_price, date as date2 from table) as s2
WHERE s2.date2 BETWEEN date - interval 3 day and date - interval 1 day
);
You can check it in SQLFiddle
In Fiddle I used different names for table and column. I prefer not to use SQL keywords as names

total user count monthwise

mySQL total user count
Grouping by month
I want to list the total count of registered users grouped by month
Well, the difficulty about this is that I don't want the count per month,
but the the total count of users up to (and including) the month.
User table structure
+---------------+--------------+------+-------------------+----------------+
| Field | Type | Null | Default | Extra |
+---------------+--------------+------+-------------------+----------------+
| ID | int(11) | NO | NULL | auto_increment |
| email | varchar(225) | NO | NULL | |
................................-CUT-.......................................
| registered | timestamp | NO | CURRENT_TIMESTAMP | |
+---------------+--------------+------+-------------------+----------------+
Example data
1 example1#mail 2012-04-04 xx:xx:xx
2 example2#mail 2012-05-04 xx:xx:xx
3 example3#mail 2012-05-04 xx:xx:xx
Preferred output
+------+-------+-------+
| Year | Month | Count |
+------+-------+-------+
| 2012 | 01 | 0 |
| 2012 | 02 | 0 |
| 2012 | 03 | 0 |
| 2012 | 04 | 1 |
| 2012 | 05 | 3 |
+------+-------+-------+
The NULL results aren't necessary.
How could I achieve that result in pure mySQL?
I have not tried this but something along these lines should work -
SELECT tots.*, #var := #var + tots.`count`
FROM (
SELECT
YEAR(registered) AS `year`,
MONTH(registered) AS `month`,
COUNT(*) AS `count`
FROM user
GROUP BY `year`, `month`
) AS tots, (SELECT #var := 0) AS inc
You can do it with a couple of user variables:
set #c = 0;
set #d = 0;
select y, m, #d := #d + Count as Count from
(select year(registered) as y,
month(registered) as m,
#c := #c + count(*) as Count
from user
group by y,m) as t;
gives you
+------+------+-------+
| y | m | Count |
+------+------+-------+
| 2011 | 1 | 2455 |
| 2011 | 2 | 14253 |
| 2011 | 3 | 42311 |
This approach first gets the first day of the month for all months in which any registration occurred. It then joins to every user that had a registration greater than the first day of the month, and then counts the number of users.
SELECT
YEAR(dates.first_day_of_month) AS registration_year,
MONTH(dates.first_day_of_month) AS registration_month,
COUNT(u.ID)
FROM (
SELECT DISTINCT
DATE_SUB(
DATE_ADD(
DATE_SUB(registered,INTERVAL (DAY(registered)-1) DAY),
INTERVAL 1 MONTH),
INTERVAL 1 SECOND) first_day_of_month
FROM user
) dates
LEFT JOIN user u ON u.registered <= dates.first_day_of_month
GROUP BY dates.first_day_of_month
If you want to avoid the gaps in months where no registrations occurred, you could substitute the sub-query with another that used a pre-existing "numbers" table to get a list of all possible months.