Say I have this table below:
month
num_of_fruits
gain
2022-01-01
5
4
2022-02-01
10
5
2022-03-01
9
8
I'm looking to create a query based on a calculation. Is it possible to put a WHERE condition inside a CASE expression? The calculation for it is:
Set the 'test' column as num_of_fruits / this month's gain + previous month's gain.
Now the calculation is a bit weird for each month:
January will be num_of_fruits / this month's gain + previous month's (Dec) gain
February will be num_of_fruits / this month's gain + previous month's (Jan) gain + December of last year's gain
March will num_of_fruits / this month's gain + previous month's (Feb) gain + 2 months ago's (Jan) gain + December of last year's gain
The rest follows the same format where it sums the gains of the current month until December of last year's
I tried to come up with a query in PostgreSQL for just the month of January:
CASE
WHEN "month" = '2022-01-01' THEN num_of_fruits / ("gain" + "gain" WHERE (date_trunc('month', "month" - interval '1 month')))
END "test"
I know it's possible to have a calculation after the THEN clause, but is it possible to have a condition there (i.e. getting the gain value of last month's or any previous month's)?
Are there other solutions? Any tips, advice, or suggestions are greatly appreciated!
Thank you so much in advance!
Related
Using tableau, I am trying to analyze data on a weekly basis but with some modifications.
I want the first week of any given month to end on a Saturday with a minimum of one week.
Middle weeks are 7 days long (Sunday to Saturday).
The last week of a month Should be from Sunday to the last day of the month.
This question is very similar to the solution of this question but with minor differences: https://community.tableau.com/thread/230894
Here is an example for April
Week 1 has April 1 start date
Week 2 has April 12 start date
Week 3 has April 19 start date
Week 4 has April 26 start date
This seems to work:
IF DAY([Update Time])<[ISO Time]
THEN IF [ISO Time] <=6
THEN DATEADD('day',7-[ISO Time],[Update Time])
ELSE DATEADD('day', 7-[ISO Time],[Update Time])
END
ELSE IF [ISO Time]<=6
THEN DATEADD('day',-[ISO Time],[Update Time])
ELSE DATEADD('day',7-[ISO Time],[Update Time])
END
END
It's commonplace even outside of software to communicate time or date intervals in a truncated manner. For example: 1h10m translates to "One hour and ten minutes."
This could be abstracted to a set of rules. For instance: A date interval is represented as a combination of _h, _m, (and so on), where _ characters represent non-negative integers or floats, which are summed into one date interval object.
Mixing days, hours, minutes are allowed. For example, 0.5d1h60m would be a synonym for 14h.
Is there a standard defined out there anywhere that resembles this?
The standard for this is a Duration, defined by ISO 8601.
Note that an Interval is a different concept (also defined by the same ISO), although both are closely related:
A Duration defines an amount of time (like "1 hour and 10 minutes" or "2 years, 3 months and 4 days"). But it doesn't tell you when it starts or ends ("1 hours and 10 minutes" relative to what?). It's just the amount of time, by itself.
An Interval (quoting wikipedia) is "the intervening time between two time points". It has a defined start and end dates, but you can use a Duration to define it, as it can have 4 different formats:
Start and end, such as 2007-03-01T13:00:00Z/2008-05-11T15:30:00Z
Start and duration, such as 2007-03-01T13:00:00Z/P1Y2M10DT2H30M
Duration and end, such as P1Y2M10DT2H30M/2008-05-11T15:30:00Z
Duration only, such as P1Y2M10DT2H30M, with additional context information
Cases 1, 2 and 3 are equivalent (all have the same start and end dates). The only difference is that in cases 2 and 3, the duration P1Y2M10DT2H30M is used to calculate the other date (in case 2, you add it to the start date, and in case 3 you subtract it from the end date).
As you can notice above, the standard format for a Duration is P[n]Y[n]M[n]DT[n]H[n]M[n]S, where:
P is the duration designator (for period) placed at the start of the duration representation.
Y is the year designator that follows the value for the number of years.
M is the month designator that follows the value for the number of months.
W is the week designator that follows the value for the number of weeks.
D is the day designator that follows the value for the number of days.
T is the time designator that precedes the time components of the representation.
H is the hour designator that follows the value for the number of hours.
M is the minute designator that follows the value for the number of minutes.
S is the second designator that follows the value for the number of seconds.
So, "1 year and 10 months" is represented as P1Y10M and "1 hour and 10 minutes" is PT1H10M (note that the T is required to resolve the potencial ambiguity between 1 month (P1M) and 1 minute (PT1M), as they use the same letter M as designator).
As #MattJohnson commented, the math with dates it's not always obvious, so the equivalence between different durations can't be what we normally expect.
For the examples below, I'm using Java 8 (just to show how durations can be tricky). Note that the java.time API uses 2 different classes (Period and Duration), but the idea for both is the same (they're both amounts of time).
A duration of 1 month is equivalent to how many days? It depends:
// one month period
Period oneMonth = Period.parse("P1M");
// January 1st
LocalDate jan = LocalDate.of(2016, 1, 1);
System.out.println(jan); // 2016-01-01
// January 1st plus 1 month period = February 1st
LocalDate feb = jan.plus(oneMonth);
System.out.println(feb); // 2016-02-01
// February 1st plus 1 month period = March 1st
LocalDate mar = feb.plus(oneMonth);
System.out.println(mar); // 2016-03-01
// difference between Jan 1st and Feb 1st = 31 days
System.out.println(ChronoUnit.DAYS.between(jan, feb)); // 31
// difference between Feb 1st and Mar 1st = 29 days (2016 is leap year)
System.out.println(ChronoUnit.DAYS.between(feb, mar)); // 29
So, adding 1 month to January 1st results in February 1st - in this case, 1 month is equivalent 31 days (A.K.A. adding a 1 month duration (P1M) is equivalent to adding a 31 days duration (P31D)), and adding 1 month to February 1st results in March 1st (in this case, 1 month = 29 days, because 2016 is a leap year).
1 day = 24 hours? Not always. If there's a Daylight Saving Time shift involved, you can get strange results:
// 1 day period
Period oneDay = Period.parse("P1D");
// 24 hours period
Duration twentyFourHours = Duration.parse("PT24H");
// in Sao Paulo, summer time starts at Oct 15, at midnight
// getting a date one day before DST change, at 10:00 AM
ZonedDateTime z = ZonedDateTime.of(2017, 10, 14, 10, 0, 0, 0, ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2017-10-14T10:00-03:00[America/Sao_Paulo]
// add 1 day - gets the same hour (10:00 AM)
System.out.println(z.plus(oneDay)); // 2017-10-15T10:00-02:00[America/Sao_Paulo]
// add 24 hours - gets 11:00 AM because of DST shift (at midnight, clocks moved forward 1 hour)
System.out.println(z.plus(twentyFourHours)); // 2017-10-15T11:00-02:00[America/Sao_Paulo]
In São Paulo, at October 15th, 2017, DST starts (clocks are moved forward by 1 hour), so:
If you add 24 hours to October 14th at 10 AM, you'll get October 15th at 11 AM
But if you add 1 day, you'll get October 15th at 10 AM
So, in this case, 1 day = 23 hours - it means that adding a 1 day duration (P1D) is equivalent to adding a 23 hours duration (PT23H)
When DST ends, is the opposite: clocks move back 1 hour, and 1 day will be equivalent to 25 hours.
So, the standard defines the format and meaning of the amounts of time concepts, but the equivalence between different durations will depend on the context (although it might sound non-intuitive that 1 day is not always 24 hours, but date/time math is not as obvious as we'd like).
You can use moment: http://momentjs.com/docs/#/durations/
moment.duration(100); // 100 milliseconds
moment.duration(60000).humanize(); // a minute
Read more in the above linked docs. And to get all unit values you may want to use the ISO8601 Format:
moment.duration(1, 'd').toISOString() // "P1D"
For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds".
Read more directly under http://momentjs.com/docs/#/durations/as-iso-string/
To give an example, starting date '2016-01-01' and ending date '2016-06-01', the number of days of '10th' is 5 as it appears in every month from Jan to May. The number of days of '29th' is also 5 as it is a leap year. The number of days of '30th' is 4 and the number of days of '31st' is 3 as they only appear in certain months.
What is the easiest (or say most elegant) way to calculate this number given the starting and ending dates?
You can create a list of dates using generate_series() and then count the occurrences of each day:
select extract(day from dt) as day_in_month, count(*)
from generate_series(date '2016-01-01', date '2016-06-01', interval '1' day) as g(dt)
group by extract(day from dt)
order by 1;
I want to create a list of dates that go until the end of February. However, since the end of February changes from 28 to 29 depending on whether there's a leap year, I'm having trouble with how to consider both options.
Here's what I have so far:
date = datenum(years(i),12,01):1:datenum(years(i)+1,02,29);
This case, when run on a year that is not a leap year, ends up counting March 1st instead of ending on Feb. 28th.
Here's a little hack I came up with. You can check whether a year is a leap year quite easily by calculating the number of days between February 28 and March 1, like so:
datenum(years(i), 3, 1) - datenum(years(i), 2, 28)
Checking whether it's larger than 1 would indicate leap year. This 1 or 0 logical MATLAB convention leads to the second part of the hack: this is exactly the number of days you need to add to Feb 28: 0 if not leap year, 1 if leap year. Here, therefore, is the full hack:
date = datenum(years(i),12,01):datenum(years(i)+1,02, ...
28 + ((datenum(years(i)+1,3,1) - datenum(years(i)+1,2,28))>1) );
UPDATE / IMPROVEMENT:
Answer already accepted, but I came up with an even better solution. I didn't realize that datenum simply counts days. In this case, we can simply say that the last day of February is the day before March 1. This yields the following drastic simplification:
date = datenum(years(i),12,01):1:(datenum(years(i)+1,3,1)-1);
Datenum, for good or ill, takes negative and zero numbers. So the last day of February can be written:
datenum(2015, 3, 0)
With a comment explaining this madness, of course.
I'm creating a new form in Access 2010. I need to create a field that will calculate a year to date timeframe, in months, using a calculated value for the first day of the year. The scenario is that the user will input a date. Then, Access will calculate a value for months YTD, based on the date the user keys. The catch is that the user may input a date for the current year, or a date from a previous year. So, I cannot simply hard code a baseline date, like 1/1/2014, to perform the calculation. I need Access to generate the first day of the year, based on the date entered, then perform the calculation.
Example: User enters '4/10/2013'
Access calculates months YTD from '1/1/2013' to '4/10/2013'.
Expected Result: 3.25 Months
I need this to assist with calculating income, which will utilize dates over multiple years.
I quickly created this function for you:
Public Function calcmonths(datecalc As Date) As Double
calcmonths = Round(DateDiff("d", DateSerial(Year(datecalc), 1, 1), datecalc) * 12 / DateDiff("d", DateSerial(Year(datecalc), 1, 1), DateSerial(Year(datecalc) + 1, 1, 1)), 2)
End Function
You can use it with the input from your form. It calculates the first day of the entered year with DateSerial(Year(datecalc), 1, 1) , then calculates the difference in days. That result is multiplied by 12 (months) and divided by the number of days within that year (calculated to make sure leap years are 366 days). In the end the result is rounded to 2 decimal numbers.