PSQL: Aggregate function (sum) not working - postgresql

I have this query (artist_money is of MONEY type, e.g. $30,456.11.):
SELECT SUM(
CASE
WHEN end_date - date '2015-12-3' <= 28 AND end_date - date '2015-12-3' > 0 THEN artist_money
END,
CASE
WHEN date '2015-12-3' - start_date > 28 THEN artist_money
END
) AS "gonorar"
FROM peacecard
WHERE artist_id = 12345 AND contract IS NOT NULL
When I try to get the result, here's the error:
ERROR: function sum(money, money) does not exist
LINE 1: SELECT sum(
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
********** Error **********
What's going on? According to Documentation, SUM should take the parameters if it's MONEY type.
Thanks a lot!

The problem is the comma, not the money type. Perhaps you intend:
SELECT SUM(CASE WHEN end_date - date '2015-12-3' <= 28 AND end_date - date '2015-12-03' > 0
THEN artist_money
END),
SUM(CASE WHEN date '2015-12-3' - start_date > 28
THEN artist_money
END
) AS "gonorar"
FROM peacecard
WHERE artist_id = 12345 AND contract IS NOT NULL;
Or, if you want one column, then it makes much more sense to only use one case:
SELECT SUM(CASE WHEN end_date - date '2015-12-3' <= 28 AND end_date - date '2015-12-03' > 0
THEN artist_money
WHEN date '2015-12-3' - start_date > 28
THEN artist_money
END
) AS "gonorar"
FROM peacecard
WHERE artist_id = 12345 AND contract IS NOT NULL;

You are trying to pass two parameters to an aggregate 1 parameter function.

Related

How to subtract a seperate count from one grouping

I have a postgres query like this
select application.status as status, count(*) as "current_month" from application
where to_char(application.created, 'mon') = to_char('now'::timestamp - '1 month'::interval, 'mon')
and date_part('year',application.created) = date_part('year', CURRENT_DATE)
and application.job_status != 'expired'
group by application.status
it returns the table below that has the number of applications grouped by status for the current month. However I want to subtract a total count of a seperate but related query from the internal review number only. I want to count the number of rows with type = abc within the same table and for the same date range and then subtract that amount from the internal review number (Type is a seperate field). Current_month_desired is how it should look.
status
current_month
current_month_desired
fail
22
22
internal_review
95
22
pass
146
146
UNTESTED: but maybe...
The intent here is to use an analytic and case expression to conditionally sum. This way, the subtraction is not needed in the first place as you are only "counting" the values needed.
SELECT application.status as status
, sum(case when type = 'abc'
and application.status ='internal_review' then 0
else 1 end) over (partition by application.status)) as
"current_month"
FROM application
WHERE to_char(application.created, 'mon') = to_char('now'::timestamp - '1 month'::interval, 'mon')
and date_part('year',application.created) = date_part('year', CURRENT_DATE)
and application.job_status != 'expired'
GROUP BY application.status

How can I filter for CURRENT_DATE / SYSDATE - 2 years?

I have a table t with a column "date" which has the type "DATE":
date
2018-10-01
2019-02-03
2020-01-01
2021-01-01
I want to get only entries where CURRENT_DATE / SYSDATE minus 2 years is true. So the result should be (CURRENT_DATE / SYSDATE = "2021-05-01":
date
2019-02-03
2020-01-01
2021-01-01
My code:
SELECT *
FROM t
WHERE YEAR(t.date) >= ADD_YEARS(TRUNC(CURRENT_DATE), -2)
But that gives me the error
Feature not supported: Incomparable Types: DECIMAL(4,0) and DATE!
Using SYSDATE with
SELECT *
FROM t
WHERE YEAR(t.date) >= ADD_YEARS(TRUNC(SYSDATE), -2)
gives the error
Feature not supported: Incomparable Types: DECIMAL(4,0) and DATE!
I tried https://stackoverflow.com/a/28888880/4435175 with
SELECT *
FROM t
WHERE YEAR(t.date) >= add_months( trunc(sysdate), -12*2 )
but that gave me the same error
Feature not supported: Incomparable Types: DECIMAL(4,0) and DATE!
sysdate already returns you date. No need to trunc it.
Looks like something is wrong with data type of column t.date.
What describe t; shows you?
UPD.
I see 2 options here. First one is more preferable for me, as it doesn't apply function to every t.date value.
SELECT *
FROM t
WHERE t.date >= add_years (sysdate, -2)
--WHERE years_between (sysdate, t.date) >= 2

DATE ADD function in PostgreSQL

I currently have the following code in Microsoft SQL Server to get users that viewed on two days in a row.
WITH uservideoviewvideo (date, user_id) AS (
SELECT DISTINCT date, user_id
FROM clickstream_videos
WHERE event_name ='video_play'
and user_id IS NOT NULL
)
SELECT currentday.date AS date,
COUNT(currentday.user_id) AS users_view_videos,
COUNT(nextday.user_id) AS users_view_next_day
FROM userviewvideo currentday
LEFT JOIN userviewvideo nextday
ON currentday.user_id = nextday.user_id AND DATEADD(DAY, 1,
currentday.date) = nextday.date
GROUP BY currentday.date
I am trying to get the DATEADD function to work in PostgreSQL but I've been unable to figure out how to get this to work. Any suggestions?
I don't think PostgreSQL really has a DATEADD function. Instead, just do:
+ INTERVAL '1 day'
SQL Server:
Add 1 day to the current date November 21, 2012
SELECT DATEADD(day, 1, GETDATE()); # 2012-11-22 17:22:01.423
PostgreSQL:
Add 1 day to the current date November 21, 2012
SELECT CURRENT_DATE + INTERVAL '1 day'; # 2012-11-22 17:22:01
SELECT CURRENT_DATE + 1; # 2012-11-22 17:22:01
http://www.sqlines.com/postgresql/how-to/dateadd
EDIT:
It might be useful if you're using a dynamic length of time to create a string and then cast it as an interval like:
+ (col_days || ' days')::interval
You can use date + 1 to do the equivalent of dateadd(), but I do not think that your query does what you want to do.
You should use window functions, instead:
with plays as (
select distinct date, user_id
from clickstream_videos
where event_name = 'video_play'
and user_id is not null
), nextdaywatch as (
select date, user_id,
case
when lead(date) over (partition by user_id
order by date) = date + 1 then 1
else 0
end as user_view_next_day
from plays
)
select date,
count(*) as users_view_videos,
sum(user_view_next_day) as users_view_next_day
from nextdaywatch
group by date
order by date;

generate series based on particular day in each month -postgresql

i have following query in postgresql for dates between 2 ranges.
select generate_series('2019-04-01'::timestamp, '2020-03-31', '1 month')
as g_date
I need to generate specific date in every month .i.e 15 th of every month. Following is my query to generate series
DO $$
DECLARE
compdate date = '2019-04-15';
BEGIN
CREATE TEMP TABLE tmp_table ON COMMIT DROP AS
select *,
case
when extract('day' from d) <> extract('day' from compdate) then 0
when ( extract('month' from d)::int - extract('month' from compdate)::int ) % 1 = 0 then 1
else 0
end as c
from generate_series('2019-04-01'::timestamp, '2020-03-31', '1 day') d;
END $$;
SELECT * FROM tmp_table
where c=1;
;
But every thing is perfect if input date between (1..29)-04-2019 ..
2019-04-25
2019-05-25
2019-06-25
2019-07-25
2019-08-25
2019-09-25
2019-10-25
2019-11-25
2019-12-25
2020-01-25
2020-02-25
2020-03-25
but if i give compdate: 31-04-2019 or 30-04-2019 giving out put:
2019-05-31
2019-07-31
2019-08-31
2019-10-31
2019-12-31
2020-01-31
2020-03-31
Expected Output:
date flag
2019-04-01 0 ----start_date
2019-04-30 1
2019-05-31 1
2019-06-30 1
2019-07-31 1
2019-08-31 1
2019-09-30 1
2019-10-31 1
2019-11-30 1
2019-12-31 1
2020-01-31 1
2020-02-29 1
2020-03-31 0 ---end_date
If matched day not found in the result it should take last day of that month..i.e if 31 not found in month of feb it
should take 29-02-2019 and also in april month instead of 31 it should take 2019-04-30.
Please suggest.
to generate the last days of the month, just generate first days & subtract a 1 day interval
example: the following generates all last day of month in the year 2010
SELECT x - interval '1 day' FROM
GENERATE_SERIES('2010-02-01', '2011-01-01', interval '1 month') x
You cannot accomplish what you want with generate_series. This results due to that process applying a fixed increment from the previous generated value. Your case 1 month. Now Postgres will successfully compute correct end-of-month date from 1 month to the next. So for example 1month from 31-Jan yields 28-Feb (or 29), because 31-Feb would be an invalid date, Postgres handles it. However, that same interval from 28-Feb gives the valid date 28-Mar so no end-of-month adjustment is needed. Generate_Series will return 28th of the month from then on. The same applies to 30 vs. 31 day months.
But you can achieve what your after with a recursive CTE by employing a varying interval to the same initial start date. If the resulting date is invalid for date the necessary end-of-month adjustment will be made. The following does that:
create or replace function constant_monthly_date
( start_date timestamp
, end_date timestamp
)
returns setof date
language sql strict
as $$
with recursive date_set as
(select start_date ds, start_date sd, end_date ed, 1 cnt
union all
select (sd + cnt*interval '1 month') ds, sd, ed, cnt+1
from date_set
where ds<end_date
)
select ds::date from date_set;
$$;
-- test
select * from constant_monthly_date(date '2020-01-15', date '2020-12-15' );
select * from constant_monthly_date(date '2020-01-31', date '2020-12-31' );
Use the least function to get the least one between the computed day and end of month.
create or replace function test1(day int) returns table (t timestamptz) as $$
select least(date_trunc('day', t) + make_interval(days => day-1), date_trunc('day', t) + interval '1 month' - interval '1 day') from generate_series('2019-04-01', '2020-03-31', interval '1 month') t
$$ language sql;
select test1(31);

How to identify invalid dates in postgres table field?

I have a table in PostgreSQL that has two date fields ( start and end ). There are many invalid dates both date fields like 0988-08-11,4987-09-11 etc.. Is there a simple query to identify them? The data type of the field is DATE. Thanks in advance.
Values in a date column ARE valid per definition. The year 0988 = 988 is a valid historic date as well as the year 4987 which is far in the future.
To filter out dates which are too historic or too far in the future you simply make this query:
SELECT
date_col
FROM
table
WHERE
date_col < /* <MINIMUM DATE> */
OR date_col > /* <MAXIMUM DATE> */
For date ranges (your minimum and maximum date) you could use the daterange functionality:
https://www.postgresql.org/docs/current/static/rangetypes.html
https://www.postgresql.org/docs/current/static/functions-range.html
Example table:
start_date end_date
2015-01-01 2017-01-01 -- valid
200-01-01 900-01-01 -- completely too early
3000-01-01 4000-01-01 -- completely too late
0200-01-01 2000-01-01 -- begin too early
2000-01-01 4000-01-01 -- end too late
200-01-01 4000-01-01 -- begin too early, end too late
Query:
SELECT
start_date,
end_date
FROM
dates
WHERE
daterange('1900-01-01', '2100-01-01') #> daterange(start_date, end_date)
Result:
start_date end_date
2015-01-01 2017-01-01
demo:db<>fiddle
Those are valid dates, but if you have business rules that state they are not valid for your purpose, you can delete them based on those rules:
For example, if you don't want any dates prior to 1900 or after 2999, this statement would delete the records with those dates:
DELETE FROM mytable
WHERE
start_date < '1900-01-01'::DATE OR
start_date >= '2999-01-01'::DATE OR
end_date < '1900-01-01'::DATE OR
end_date >= '2999-01-01'::DATE;
If you want to replace the dates with the lowest/highest acceptable dates instead of deleting the entire record, you could do something like this:
UPDATE mytable
SET
start_date = least('2999-01-01'::DATE, greatest('1900-01-01'::DATE, start_date)),
end_date = least('2999-01-01'::DATE, greatest('1900-01-01'::DATE, end_date))
WHERE
start_date < '1900-01-01'::DATE OR
start_date >= '2999-01-01'::DATE OR
end_date < '1900-01-01'::DATE OR
end_date >= '2999-01-01'::DATE;