Add interval to timestamp using Ecto Fragments - postgresql

I want to write the following query in a phoenix application using Ecto fragments:
select *
from (
select id,
inserted_at + interval '1 day' * expiry as deadline
from friend_referral_code
) t
where localtimestamp at time zone 'UTC' > deadline
The value of expiry is an integer value that represents number of days. What I've got so far is something like this:
query = from frc in FriendReferralCode,
where: fragment("localtimestamp at time zone 'UTC'") > fragment("? + INTERVAL '1' * ?", frc.inserted_at, frc.expiry)
FriendReferralCode
|> Repo.all(query)
|> Enum.each(fn frc -> update_friend_referral_code_users(get_friend_referral_code_users_by(receiver_id: frc.id), %{status: false}) end)
|> IO.puts()
end
but it throws the following error:
** (EXIT) an exception was raised:
** (FunctionClauseError) no function clause matching in Keyword.merge/2
(elixir 1.11.2) lib/keyword.ex:764: Keyword.merge([], #Ecto.Query<from f0 in Stakester.Accounts.FriendReferralCode, where: fragment("localtimestamp at time zone 'UTC'") > fragment("? + INTERVAL '1 day' * ?", f0.inserted_at, f0.expiry)>)

You are after Ecto.Query.API.ago/2 and Ecto.Query.API.from_now/2 for querying interval and Ecto.Query.subquery/2 for inner select.
Also, Repo.all/2 expects a query as a first argument, while you pass FriendReferralCode as the first argument in the call to Repo.all/2, where it expects a query, and query as a second one, where it expects a keyword list of options.
Do just query |> Repo.all() instead.

Related

How to use time.Time in a postgres query?

I am using pq library in golang for postgres queries. I want to pass timestamp in the query string.
Ex. query = 'SELECT \* FROM table WHERE ts \<= NOW() - interval '5 seconds''
Instead of NOW() ...can I pass a timestamp?
Ex.
t=time.Now()
query = 'SELECT \* FROM table WHERE ts \<= t- interval '5 seconds''
I tried formatting the timestamp and using arguments to pass the timestamp but getting errors.

PostgreSQL query for elapsed interval

I am trying to query PostgreSQL database for rows where interval has elapsed from the last run. Main columns for this purpose are processed_at as timestamptz and frequency (in minutes) as integer.
I am failing with operators, since not many of them can operate together timestamp & integer.
Can someone please propose a query that would solve this? Thank you very much for help
From here Date/time operators:
timestamp + interval → timestamp
Add an interval to a timestamp
timestamp '2001-09-28 01:00' + interval '23 hours' → 2001-09-29 00:00:00
select now() + (10::varchar || ' min')::interval;
?column?
-------------------------------
2021-10-15 09:05:37.927163-07
--Or in your case. If I'm following you are adding the interval.
select processed_at + (frequency::varchar || ' min')::interval;
The query takes the integer value of minutes and converts it to an interval of minutes that can be added to the timestamp.
Further explanation, || is the Postgres concatenation operator and ::varchar, ::interval are casting shorthand.
UPDATE
I keep forgetting about the make_*() functions for date/time/interval
--A shorter version
select processed_at + make_interval(mins => frequency);
Saves all the casting.

Postgres : invalid input syntax for type timestamp with time zone: ""

I am new to Postgres DB. I am getting the error:
invalid input syntax for type timestamp with time zone: ""
when trying to run the below query from java. ":from_date" and ":upto_date" will be replaced with null while executing in java. Any ideas on how to resolve this?
select tm.*
from team tm
and tm.schedule_finish >= (case
when :from_date is null or :upto_date is null
then TO_CHAR(to_date(concat(TO_CHAR(LOCALTIMESTAMP - INTERVAL '1 month','YYYY-MM') , '-01'),'YYYY-MM-DD'),'YYYY-MM-DD')
when (date_part('year',age(:upto_date,:from_date))*12+date_part('month',age(:upto_date,:from_date)))>36
then TO_CHAR(to_date(concat(TO_CHAR(LOCALTIMESTAMP -INTERVAL '1 month','YYYY-MM') , '-01'),'YYYY-MM-DD'),'YYYY-MM-DD')
else TO_CHAR(:from_date,'YYYY-MM-DD')
end)::timestamp
It's a bit unclear to me what type those parameters have when passed to the query.
TO_CHAR(:from_date,'YYYY-MM-DD') suggests it's a timestamp or timestamptz value (e.g. passed as a java.time.LocalDate or java.time.OffsetTime)
However the error message seems to indicate that you are passing strings as parameters (which is a bit dangerous as in that case you rely on implicit data type conversion when calling the age() function).
Assuming you do pass strings, you can handle empty strings in the first WHEN part by converting them to NULL. That way the subsequent expressions don't have to deal with empty strings.
I would add an explicit conversion for the call of the age() function. You also don't need to call age() twice, you can compare the result directly with an interval constant age(...) > interval '36 month'. The back and forth between to_date() and to_char() just to get a timestamp value of the previous month's first day is also unnecessary.
And if all the expressions in the then part already return a timestamp it's no longer necessary to cast the whole CASE expression to a timestamp
(case
when nullif(trim(:from_date),'') is null or nullif(trim(:upto_date), '') is null
then date_trunc('month', LOCALTIMESTAMP - INTERVAL '1 month')
when age(to_date(:upto_date, 'yyyy-mm-dd'), to_date(:from_date, 'yyyy-mm-dd')) > interval '36 month'
then date_trunc('month', LOCALTIMESTAMP - INTERVAL '1 month')
else to_timestamp(:from_date,'YYYY-MM-DD')
end)
If you want to do any calculation with your "date strings", you have to convert them first:
to_date(:from_date, 'yyyy-mm-dd') - interval '1 month'

How to handle timestamp with timezone on postgres with knex.js

I am trying to translate following sql query into knex:
select count(*) as finished_on_time from task_history
where date = 20160303
and store_id = 2
and (schedule_start_time at time zone 'Australia/sydney' + interval '1' minute * floor (group_duration) )::time >= (finish_time at time zone 'Australia/sydney')::time
date field has in yyyymmdd format
Here is what I have been trying on knex:
db.table('task_history')
.count('*')
.where({date: request.params.storeid, store_id: request.params.storeid })
??????
As you can guess, I am not sure which clause to use to handle sql syntax [at time zone Australia/sydney].
I have been trying to find any similar soloutions on the internet, but ended up here.
http://knexjs.org/#Builder-whereRaw
db.table('task_history')
.count('*')
.where({date: request.params.storeid, store_id: request.params.storeid })
.whereRaw("(schedule_start_time at time zone 'Australia/sydney' + interval '1' minute * floor (group_duration) )::time >= (finish_time at time zone 'Australia/sydney')::time")

Postgresql Query very slow with ::date, ::time, and interval

I have a sql query that is very slow:
select number1 from mytable
where symbol = 25
and timeframe = 1
and date::date = '2008-02-05'
and date::time='10:40:00' + INTERVAL '30 minutes'
The goal is to return one value, and postgresql takes 1.7 seconds to return the desired value(always a single value). I need to execute hundreds of those queries for one task, so this gets extremely slow.
Executing the same query, but pointing to the time directly without using interval and ::date, ::time takes only 17ms:
select number1 from mytable
where symbol = 25
and timeframe = 1
and date = '2008-02-05 11:10:00'
I thought it would be faster if I would not use ::date and ::time, but when I execute a query like:
select number1 from mytable
where symbol = 25
and timeframe = 1
and date = '2008-02-05 10:40:00' + interval '30 minutes'
I get a sql error (22007). I've experimented with different variations but I couldn't get interval to work without using ::date and ::time. Date/Time Functions on postgresql.org didn't help me out.
The table got a multi column index on symbol, timeframe, date.
Is there a fast way to execute the query with adding time, or a working syntax with interval where I do not have to use ::date and ::time? Or do I need to have a special index when using queries like these?
Postgresql version is 9.2.
Edit:
The format of the table is:
date = timestamp with time zone,
symbol, timeframe = numeric.
Edit 2:
Using
select open from ohlc_dukascopy_bid
where symbol = 25
and timeframe = 1
and date = timestamp '2008-02-05 10:40:00' + interval '30' minute
Explain shows:
"Index Scan using mcbidindex on mytable (cost=0.00..116.03 rows=1 width=7)"
" Index Cond: ((symbol = 25) AND (timeframe = 1) AND (date = '2008-02-05 11:10:00'::timestamp without time zone))"
Time is now considerably faster: 86ms on first run.
The first version will not use a (regular) index on the column named date.
You didn't provide much information, but assuming the column named date has the datatype timestamp (and not date), then the following should work:
and date = timestamp '2008-02-05 10:40:00' + interval '30 minutes'
this should use an index on the column named date (but only if it is in fact a timestamp not a date). It is essentially the same as yours, the only difference is the explicit timestamp literal (although Postgres should understand '2008-02-05 10:40:00' as a timestamp literal as well).
You will need to run an explain to find out if it's using an index.
And please: change the name of that column. It's bad practise to use a reserved word as an identifier, and it's a really horrible name, which doesn't say anything about what kind of information is stored in the column. Is it the "start date", the "end date", the "due date", ...?