Let's say I have a table for a time-sheet like this:
CREATE TABLE foo (
spent_on DATETIME,
hours FLOAT
)
Assuming spent_on is the timestamp the value was logged, and hours is a floating point value representing the amount of hours spent on a task.
How can I get a floating average of hours over the past 7 days?
I've came up with the following but it won't work:
select spent_on, hours, avg(hours)
over RANGE BETWEEN spent_on - INTERVAL '7 days' AND CURRENT ROW from daily;
I get the following error:
ERROR: syntax error at or near "ROW"
LINE 1: ... BETWEEN spent_on - INTERVAL '7 days' AND CURRENT ROW from d...
I've tried to understand the docs for window functions, but I have real trouble grasping the idea between partitions, windows and frames. And as a result, can't come up with a query.
I'm not sure about the RANGE syntax, so let me offer a solution with a sub query (If performance is not an issue with small tables ETC..) :
SELECT t.spent_on,t.hours,
COALESCE( (SELECT AVG(s.hours) FROM foo
WHERE t.spent_on > CURRENT_TIMESTAMP - INTERVAL '7 days'),0) float_avg
FROM foo t
Related
I have a column called number_of_days_since_event and I want to change it to data_of_last_event, the way I can do that is subtracting today's date from the number of days in the number_of_days_since_event column. But I do not know how to subtract days in a column.
This question answers the problem when you know the number of days in advance, i.e., if I would like to subtract 10 days from today it would be:
SELECT CURRENT_DATE - INTERVAL '10 days';
However, I would like to do something like:
SELECT CURRENT_DATE - INTERVAL "myTable.number_of_days_since_event" 'days'
FROM myTable;
But this does not work leading to the error message: syntax error at or near "'day'"
The following using concat solves my problem:
SELECT CURRENT_DATE - concat(myTable.number_of_days_since_event::text,' day')::interval
FROM myTable;
If you are happy with a date result, you could use
SELECT current_date - number_of_days_since_event::integer
FROM mytable;
Can anyone suggest me, the easiest way to find summation of time field in POSTGRESQL. i just find a solution for MYSQL but i need the POSTGRESQL version.
MYSQL: https://stackoverflow.com/questions/3054943/calculate-sum-time-with-mysql
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(timespent))) FROM myTable;
Demo Data
id time
1 1:23:23
2 4:00:23
3 9:23:23
Desired Output
14:47:09
What you want, is not possible. But you probably misunderstood the time type: it represents a precise time-point in a day. It doesn't make much sense, to add two (or more) times. f.ex. '14:00' + '14:00' = '28:00' (but there are no 28th hour in a day).
What you probably want, is interval (which represents time intervals; hours, minutes, or even years). sum() supports interval arguments.
If you use intervals, it's just that simple:
SELECT sum(interval_col) FROM my_table;
Although, if you stick to the time type (but you have no reason to do that), you can cast it to interval to calculate with it:
SELECT sum(time_col::interval) FROM my_table;
But again, the result will be interval, because time values cannot exceed the 24th hour in a day.
Note: PostgreSQL will even do the cast for you, so sum(time_col) should work too, but the result is interval in this case too.
I tried this solution on sql fieddle:
link
Table creation:
CREATE TABLE time_table (
id integer, time time
);
Insert data:
INSERT INTO time_table (id,time) VALUES
(1,'1:23:23'),
(2,'4:00:23'),
(3,'9:23:23')
query the data:
SELECT
sum(s.time)
FROM
time_table s;
If you need to calculate sum of some field, according another field, you can do this:
select
keyfield,
sum(time_col::interval) totaltime
FROM myTable
GROUP by keyfield
Output example:
keyfield; totaltime
"Gabriel"; "10:00:00"
"John"; "36:00:00"
"Joseph"; "180:00:00"
Data type of totaltime is interval.
I'm porting some T-SQL stored procs to PL/pgSql and, being very new to PostgreSQL, don't know what helpful utility functions might be available in the pg community. Is there a set of robust date-math functions that "nearly everybody uses" out there somewhere? I don't want to quickly cobble together some date-math functions if there's already a great package out there.
The PostgreSQL date math operators with "natural language" string literal arguments are user-friendly if you're typing a query and you happen to know the interval:
select now() - interval '1 day'
but if the interval 1 is the result of a calculation involving nested date-math function calls, these string literals are actually not very user-friendly at all, and it would easier to work with a date_add function:
select dateadd(d, {calculation that returns the interval}, now() )
Thanks
Let me give you an example. I want to subtract from an arbitrary date the number of months that have elapsed since 1/1/1970, and then add that number of months to 1/1/1970 to return the first day of the month in which the arbitrary date falls
select (date_trunc('month', '2013-01-30'::date))::date
Or add a month to the first day of this month to get the first day of the next month, then subtract one day to get the last day of this month
select date_trunc('month', '2013-01-30'::date + 1 * interval '1 month')::date - 1
Notice in the above example you can add any number of months by multiplying the interval '1 month' by an integer. You can do that with any interval without manipulating the string '1 month'. So to add or subtract any interval you just:
select current_date + 5 * interval '1 month'
No need for messy string manipulations. You can multiply by fractions also:
select current_timestamp + 3.5 * interval '1 minute'
To add or subtract days to a date type you use an integer:
select current_date + 10
The "natural language" strings you're talking about are interval literals. Intervals can also be obtained by using date arithmetic.
Surely dateadd can be quite simply emulated in Postgresql as follows:
select d + ({calculation the returns the interval}::text || ' day')::interval
Substitute "month" or "hours" etc as appropriate.
In PostgreSQL, you simply add and subtract interval values to datetime
values:
'2001-06-27 14:43:21'::TIMESTAMP - '00:10:00'::INTERVAL = '2001-06-27 14:33:21'::TIMESTAMP
'2001-06-27 14:43:21'::TIMESTAMP- '2001-06-27 14:33:21'::TIMESTAMP = '00:10:00'::INTERVAL
For more information, see "Functions and Operators" in the PostgreSQL
online docs.
To compute the first day of the month of a date: date_trunc('month', date)
First day of the next month: date_trunc('month', date) + '1 month'::INTERVAL
Add three months to the first day of the month of this date: date_trunc('month', date) + 3*('1 month'::INTERVAL)
The interval is a data type, not a string, and you can do computations with its values.
I'm trying to get difference in days, casting result to decimal:
SELECT
CAST( TO_DATE('2999-01-01','yyyy-mm-dd') - TO_DATE('2909-01-01','yyyy-mm-dd') AS DECIMAL )
;
Now if I add 1 month to the 2nd date:
SELECT
CAST( TO_DATE('2999-01-01','yyyy-mm-dd') - (TO_DATE('2909-01-01','yyyy-mm-dd') + INTERVAL '1 MONTH' * (1) ) AS DECIMAL )
;
I'm getting an error:
ERROR: cannot cast type interval to numeric
OK, I can cast to char to get result:
SELECT
CAST( TO_CHAR( TO_DATE('2909-02-10','yyyy-mm-dd') - (TO_DATE('2909-01-01','yyyy-mm-dd') + INTERVAL '1 MONTH' * (1) ), 'DD') AS DECIMAL )
;
But in this case the 1st query modified with TO_CHAR casting stop working:
SELECT
CAST( TO_CHAR(TO_DATE('2999-01-01','yyyy-mm-dd') - TO_DATE('2909-01-01','yyyy-mm-dd'), 'DD') AS DECIMAL )
;
I'm getting ERROR: multiple decimal points.
So, my question is, how can I get days using the same sql statement? For both sql queries.
Look at your first two examples again. If you remove the outer CAST ... AS DECIMAL you get
?column?
----------
32872
?column?
------------
32841 days
Clearly the difference is in the "days". The second is an interval value rather than a simple number. You only want the number (because you always just want days) so you need to extract that part. Then you can cast to whatever precision you like:
SELECT extract(days FROM '32841 days'::interval)::numeric(9,2);
date_part
-----------
32841.00
Edit responding to Alexandr's follow-up:
Your first example fails with a fairly specific error:
SELECT extract(days FROM (TO_DATE('2999-01-01','yyyy-mm-dd') - TO_DATE('2909-01-01','yyyy-mm-dd'))::interval)::numeric(9,2);
ERROR: cannot cast type integer to interval
LINE 1: ...yyyy-mm-dd') - TO_DATE('2909-01-01','yyyy-mm-dd'))::interval...
Here you've got an integer (which is what you originally wanted) and try to cast it to an interval (for reasons I don't understand). It's complaining it doesn't know what units you want. You want 32872 what in your interval - seconds, hours, weeks, centuries?
The second example is complaining because you are trying to extract the "day" part from a simple integer, and of course there's no extract() function in the system to do that.
I think you probably need to take a step back and just take the time to understand the values your various expressions return.
Subtracting one date from another gives the number of days separating them - as an integer. There is no other sensible measure, really.
Adding (or subtracting) an interval to a date gives you a timestamp (without time zone) since the interval may contain whole days, days and hours, seconds etc.
Subtracting a timestamp from a date will give you an interval since the result may contain days, hours, seconds etc.
If you have an interval and you just want the days part then you use extract() on it and you will get an integer number of days back.
You will need an integer (or floating-point) number of days if you want to cast to numeric, not an interval because casting an interval to an scalar number makes no sense without units.
So - either stick to dates and date arithmetic (easy), or realise you are using timestamps (flexible) but understand which it is.
To get an illustration of what's happening you can do something like this (in psql):
CREATE TEMP TABLE tt AS SELECT
('2909-01-02'::date - '2909-01-01'::date) AS a,
('2909-01-02'::date - '2909-01-02 00:00:00'::timestamp) AS b;
\x
SELECT * FROM tt;
\d tt
That will show you the values and types you are dealing with. Repeat for as many columns as you find useful.
HTH
If you're doing interval arithmetic with dates, you should generally be using timestamps instead, as mentioned in the docs.
# SELECT extract(days FROM TO_TIMESTAMP('2999-01-01','yyyy-mm-dd') - TO_TIMESTAMP('2909-01-01','yyyy-mm-dd'))
date_part
-----------
32872
# SELECT extract(days FROM TO_TIMESTAMP('2999-01-01','yyyy-mm-dd') - (TO_TIMESTAMP('2909-01-01','yyyy-mm-dd') + '1 month'::interval) );
date_part
-----------
32841
The result of adding an interval to a date is actually a timestamp, not another date (the interval might have contained time portions), so you have to cast the result of the addition back down to date first:
SELECT
CAST( TO_DATE('2999-01-01','yyyy-mm-dd')
- CAST( (TO_DATE('2909-01-01','yyyy-mm-dd') + INTERVAL '1 MONTH' * (1) ) AS DATE)
AS DECIMAL )
With a date field I can do this:
ORDER BY ABS(expiry - CURRENT_DATE)
With a timestamp field I get the following error:
function abs(interval) does not exist
Use now() or CURRENT_TIMESTAMP for the purpose.
The reason for the different outcome of your queries is this:
When you subtract two values of type date, the result is an integer and abs() is applicable.
When you subtract two values of type timestamp (or just one is a timestamp), the result is an interval, and abs() is not applicable. You could substitute with a CASE expression:
ORDER BY CASE WHEN expiry > now() THEN expiry - now() ELSE now() - expiry END
Or you can extract() the unix epoch from the resulting interval like #Craig already demonstrated. I quote: "for interval values, the total number of seconds in the interval". Then you can use abs() again:
ORDER BY abs(extract(epoch from (expiry - now())));
age() would just add a more human readable representation to the interval by summing up days into months and years for for bigger intervals. But that's beside the point: the value is only used for sorting.
As your column is of type timestamp, you should use CURRENT_TIMESTAMP (or now()) instead of CURRENT_DATE, or you will get inaccurate results (or even incorrect for "today").
Compare with current_timestamp
SELECT the_timestamp > current_timestamp;
The age function is probably what you want when comparing them:
SELECT age(the_timestamp);
eg:
regress=# SELECT age(TIMESTAMP '2012-01-01 00:00:00');
age
----------------
8 mons 17 days
(1 row)
If you want an absolute distance, use:
SELECT abs( extract(epoch from age(the_timestamp)) );
This works (and gives the correct sorting):
ABS(EXTRACT(DAY FROM expiry - CURRENT_TIMESTAMP))
Unfortunately, as Erwin Brandstetter pointed out, it reduces the granularity of the sorting to a full day.