I am using the max() windows function to get the maximum money raised for each year considering there are three categories of campaign length. I need to see which campaign length i.e '<=30 days', '36 - 60 days' and '>60 days' is able to raise more money year on year.
I wrote
select
year,
campaign_length_category,
money_raised,
max(money_raised) over (partition by year ) as max_money_raised
FROM
(
SELECT
year,
campaign_length_category,
concat('$', format(sum(pledged),2,'en_US')) as money_raised
FROM
(
select
extract(year from launched) Year,
case
when campaign_length >= 1 and campaign_length <= 30 then '<=30 days'
when campaign_length > 35 and campaign_length <= 60 then '36 - 60 days'
else '>60 days'
end as campaign_length_category,
pledged
from
(
select
launched,
outcome,
datediff(cast(deadline as date),cast(launched as date)) campaign_length,
pledged
from
campaign
) t1
)t2
group by 1, 2
)t3
order by 1
However, it's not showing the maximum value for each year in the output. For the year 2011 and 2012 it fetches wrong values.
year
campaign_length_category
money_raised
max_money_raised
2009
<=30 days
$25,852.12
$64,088.48
2009
>60 days
$64,088.48
$64,088.48
2009
36 - 60 days
$31,978.64
$64,088.48
2010
<=30 days
$201,063.08
$467,862.95
2010
>60 days
$467,862.95
$467,862.95
2010
36 - 60 days
$432,416.84
$467,862.95
2011
<=30 days
$1,634,463.10
$742,348.24
2011
36 - 60 days
$1,710,892.85
$742,348.24
2011
>60 days
$742,348.24
$742,348.24
2012
36 - 60 days
$2,492,257.73
$5,410,974.02
2012
<=30 days
$5,410,974.02
$5,410,974.02
2012
>60 days
$1,434,506.99
$5,410,974.02
I am not sure why the max() window function picks up incorrect max values for some years as given.
Please enlighten me if I am doing something wrong
You need to move your formatting to only the first, outer select. By doing the formatting in the inner select, you are making max(money_raised) do string maximum of the formatted string, and "$742,348.24" is indeed greater than "$1,710,892.85", because the first characters are equal, and for the second characters, '7' is greater than '1'.
So:
select
year,
campaign_length_category,
concat('$', format(pledged,2,'en_US')) as money_raised
concat('$', format(max(pledged) over (partition by year),2,'en_US')) as max_money_raised
FROM
(
SELECT
year,
campaign_length_category,
sum(pledged) as pledged
Related
I have data with start date and end date (Say 20th Feb 2018 to 20th Feb 2020), I want to find out the total days in every year inside this range.
For example:
2018 - x days
, 2019 - 365 days
, 2020 - y days etc.
Is there a way I can do in SQL without hardcoding year values?
I tried hardcoding the values and it worked well. But I want a solution without hardcoding year values
I'm not familiar enough with MySql to know if this will port, however here is a tested and confirmed SQL Server solution.
The fiddle link is here for your use.
Given start dates 02/20/2018 and 02/20/2020, the result set is as follows:
Year
periodStart
periodEnd
DaysInPeriod
2018
2018-02-20
2018-12-31
314
2019
2019-01-01
2019-12-31
365
2020
2020-01-01
2020-02-20
51
Declare #StartDate date = '2018-02-20', #EndDate date = '2020-02-20';
WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n)),
Years AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Year
FROM x ones, x tens, x hundreds, x thousands)
SELECT Years.Year,
CASE
WHEN Year(#StartDate) = Years.year THEN #StartDate
ELSE DATEFROMPARTS(years.year, 01, 01)
END AS periodStart,
CASE
WHEN Year(#EndDate) = Years.year THEN #EndDate
ELSE DATEFROMPARTS(years.year, 12, 31)
END AS periodEnd,
DATEDIFF(day,
CASE
WHEN Year(#StartDate) = Years.year THEN #StartDate
ELSE DATEFROMPARTS(years.year, 01, 01)
END,
CASE
WHEN Year(#EndDate) = Years.year THEN #EndDate
ELSE DATEFROMPARTS(years.year, 12, 31)
END
) + 1 AS DaysInPeriod
FROM Years
WHERE Years.Year >= Year(#StartDate)
AND Years.Year <= Year(#EndDate)
Using WITH RECURSIVE to create range of dates then we can easly count the number of days for each year using DATEDIFF
WITH RECURSIVE dates AS
(
SELECT min(start_date) as start_date, DATE_FORMAT(min(start_date),'%Y-12-31') as last_day FROM mytable
UNION ALL
SELECT DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-01-01'),
DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-12-31')
FROM dates
WHERE DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-01-01') <= (SELECT MAX(end_date) FROM mytable)
),
cte2 as (
SELECT d.start_date as start_day, if(YEAR(d.start_date) = YEAR(m.end_date), m.end_date, d.last_day) as last_day
FROM dates d, mytable m
)
select *, DATEDIFF(last_day, start_day)+1 as total_days
from cte2;
Demo here
You are looking for the DATEDIFF function.
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff
DATEDIFF() returns expr1 − expr2 expressed as a value in days from one date to the other. expr1 and expr2 are date or date-and-time expressions.
You are free to specify e.g. "2019-01-01" or "2020-01-01"
as input arguments to DATEDIFF.
You may find it convenient to store several January 1st
dates in a calendar reporting table, if you want SELECT to loop
over several years and report on number of days in each year.
I have a table where it has some name and dateofbirth.
Refence data:
ABC, 1990-11-23
BCD, 1998-10-21
CDE, 1997-05-02
DEF, 2000-10-15
EFG, 1999-01-10
FGH, 1987-01-15
GHI, 1989-12-19
HIJ, 1986-12-09
I need a SQL query where I need to get the birthday celebration dates that is going to happen during the next 60 days ordered by celebration dates.
This is the query that I used till now.
SELECT *
FROM `friends`
WHERE ( DATE_FORMAT(`dob`, '%m%d') >= DATE_FORMAT(CURDATE(), '%m%d')
AND DATE_FORMAT(`dob`, '%m%d') <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 60 DAY), '%m%d')
ORDER BY DATE_FORMAT(`dob`, '%m%d');
It works ok if it runs during Jan to Oct. During November and December, the condition DATE_FORMAT(dob, '%m%d') <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 60 DAY), '%m%d') cannot apply. For example, the resulting comparison will be like 1209 < 0131 and fails.
The result that I expect to get when executed on Dec 2, 2022 is
HIJ, 1986-12-09
GHI, 1989-12-19
EFG, 1999-01-10
FGH, 1987-01-15
How do I do this in one single query?
The thread mentioned in the comment to your question uses things like adding 365.25 days to get this to work. I think this solution might be more reliable.
You can construct this years' birthday by extracting the month and day from the date of birth, and concatenating the current year to it using STR_TO_DATE.
Then you can check using a CASE statement if this years' birthday has already passed, in which case you add a year to that birthday, because that will be the next birthday for name. Then you can check if the result of that CASE statement is BETWEEN today and 60 days from now.
I used a CTE to make it clearer to read. DBfiddle here.
WITH cte as (
SELECT
-- First determine this years (year of current date) birthday
-- by constructing it from the current year, month of birth and day of birth
STR_TO_DATE(
CONCAT(YEAR(CURDATE()),'-', MONTH(dob), '-', DAY(dob)),
'%Y-%m-%d') AS this_years_birthday,
name,
dob
FROM friends
)
SELECT cte.name, cte.dob
FROM cte
WHERE
-- If the birthday is still in this year
-- Use this years' birthday
-- else add a year to this years' birthday
-- Then filter it to be between today and 60 days from now
CASE WHEN this_years_birthday >= CURDATE()
THEN this_years_birthday
ELSE DATE_ADD(this_years_birthday, INTERVAL 1 YEAR) END
BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 60 DAY)
ORDER BY MONTH(cte.dob) DESC
name
dob
GHI
1989-12-19
HIJ
1986-12-09
EFG
1999-01-10
FGH
1987-01-15
I'm trying to calculate the duration in days for different years where:
startdate enddate duration (days)
2016-09-20 2018-09-20 730
Where i want the following outcome in one row as duration per year,
with the year as column name and the days as result:
2016 = 103 days - 2017 = 365 days - 2018 = 263 days
I couldn't find a specific solution except using DATEDIFF and entering the enddate of each year but this doesn't calculate the remaining days over next years. Anyone who can help me out finding a solution with MySQL?
Assuming (hoping) that you have a numbers table you can the following calculation:
SELECT startdate,
enddate,
number AS year_num,
DATEDIFF(
LEAST(DATE(CONCAT(number, '-12-31')), enddate),
GREATEST(DATE(CONCAT(number, '-01-01')), startdate)
) + 1 AS num_days
FROM t
INNER JOIN (
SELECT 2010 AS number UNION ALL
SELECT 2011 UNION ALL
SELECT 2012 UNION ALL
SELECT 2013 UNION ALL
SELECT 2014 UNION ALL
SELECT 2015 UNION ALL
SELECT 2016 UNION ALL
SELECT 2017 UNION ALL
SELECT 2018 UNION ALL
SELECT 2019
) AS numbers ON number BETWEEN YEAR(startdate) AND YEAR(enddate)
I have a MySQL database containing discounts. A simplified version looks like this:
id | start (UNIX timestamp) | end (UNIX timestamp)
45 | 1384693200 | 1398992400
68 | 1386018000 | 1386277200
263 | 1388530800 | 1391209200
A discount can last a few days, a few months, or even a few years. I'm looking for a way to select a unique list of months where (future) discounts are valid.
If there is:
a discount which starts in november 2013 and ends in april 2014
a discount which starts in december 2013 and ends in the same month
a discount which starts in january 2014 and ends one month later
a discount which starts in june 2014 and ends the same month
The output should be:
- December (2013)
- January (2014)
- February (2014)
- March (2014)
- April (2014)
- June (2014)
November 2013 is not shown because it is in the past. May 2014 is not shown because there is no discount in that month.
Can somebody help?
Thanks in advance!
Create a table containing a sequence of numbers from 0 to a number of month you could ever require, and join this table to your table.
This is example how to get a list of years+months separately for each id
SELECT id,
year( start + interval x month ) year,
month( start + interval x month ) month
FROM
numbers n
JOIN
(
SELECT id,
from_unixtime( start ) start,
from_unixtime( end ) end
FROM Table1
) q
ON n.x <= period_diff( date_format( q.end, '%Y%m' ),date_format( q.start, '%Y%m' ))
ORDER BY id, year, month ;
Demo --> http://www.sqlfiddle.com/#!9/d7cfc/4
If you want to combine years+months for all id, skip id column and use GROUP BY
SELECT year( start + interval x month ) year,
month( start + interval x month ) month
FROM
numbers n
JOIN
(
SELECT id,
from_unixtime( start ) start,
from_unixtime( end ) end
FROM Table1
) q
ON n.x <= period_diff( date_format( q.end, '%Y%m' ),date_format( q.start, '%Y%m' ))
GROUP BY year, month
ORDER BY year, month ;
If you want to skip past years and months, add WHERE year >= current year AND month >= current month, this is a trivial change. Also add another WHERE end < current-unix-time in the subquery to filter out unwanted past rows.
I have the following relevant columns in my 'orders' table:
Date_Day (is a range from 1 to 31 with no trailing 0)
Date_Month (is a range from January to December, not numerical)
Date_Year (is the year in 4 digit format, ex: 2005)
Total (number with 2 decimal places)
I know the way of storing date is absolutely awful, but this was the database I was given. I am trying to find a few things and I'm not sure if there is a way to do it in SQL instead of doing the math in PHP:
The SUM of each day of each year.
The SUM of this day last year
(where this day is the nth weekday of the month. So for today, it
would be the 1st Tuesday of October in 2012)
The highest grossing
day in history
MySQL is not my forte, and while I can figure it out in PHP, I would love to see it done in MySQL so I can start to learn it more.
If you want to keep your database structured as it is, you could use these queries:
The SUM of each day of each year:
SELECT Date_Year, Date_Month, Date_Day, SUM(Total)
FROM tablename
GROUP BY Date_Year, Date_Month, Date_Day
The SUM of this day last year:
SELECT SUM(Total)
FROM tablename
WHERE
Date_Year = YEAR(CURDATE())-1
AND Date_Month = MONTHNAME(CURDATE())
AND Date_Day = DAY(CURDATE())
The highest grossing day in history:
SELECT Date_Year, Date_Month, Date_Day, SUM(Total)
FROM tablename
GROUP BY Date_Year, Date_Month, Date_Day
ORDER BY SUM(Total) DESC
LIMIT 1