Boolean expression in CASE in WHERE clause does not work - sql

I am having problems with Firebird SQL statement in version Firebird 2.5.
Based on today's date, I have to select either this month's data or the previous month's data.
SELECT * FROM FA_DOBAVNICA
WHERE
1=1
AND CASE WHEN
extract(day from cast('Now' as date)) < 9
THEN
DATUM_NAROCILA BETWEEN 'start of previous month' AND 'end of previous month'
ELSE
DATUM_NAROCILA BETWEEN 'start of this month' AND 'end of this month'
END
I am getting a 104 error Token unknown for BETWEEN. I have no idea what am I doing wrong.

If I understood your problem correctly you can rephrase it with an or clause like this and it should do the job:
SELECT * FROM FA_DOBAVNICA
WHERE
1=1
AND
((extract(day from cast('Now' as date)) < 9 AND DATUM_NAROCILA BETWEEN 'start of previous month' AND 'end of previous month')
or
(extract(day from cast('Now' as date)) > 8 AND DATUM_NAROCILA BETWEEN 'start of this month' AND 'end of this month'))

Personally, I would avoid forking out conditions variants in where and make it two queries instead. I suspect conditional where might suppress Firebird's query optimizer, and two distinct queries connected together might end up with a better query plan using indexes.
Especially if you have more conditions than actually shown there which 1=1 placeholder implies. You have to check and compare real plans using you real data and real added conditions.
SELECT * FROM FA_DOBAVNICA
WHERE extract(day from cast('Now' as date)) < 9
AND DATUM_NAROCILA BETWEEN 'start of previous month' AND 'end of previous'
UNION ALL
SELECT * FROM FA_DOBAVNICA
WHERE extract(day from cast('Now' as date)) >= 9
AND DATUM_NAROCILA BETWEEN 'start of this month' AND 'end of this month'
However in your specific case - why using CASE at all? Why not computing the target date span instead?
WITH
TargetDay as
(SELECT DATEADD( -8 DAY TO CURRENT_DATE) AS TPoint FROM RDB$DATABASE)
,TargetStart as
(SELECT DATEADD( 1 - EXTRACT(DAY FROM TPoint) DAY TO TPoint) AS TStart FROM TargetDay)
,TargetEnd as
(SELECT DATEADD( -1 DAY TO DATEADD( 1 MONTH TO TStart)) AS TEnd FROM TargetStart)
select TStart, TEnd, TPoint from TargetStart, TargetEnd, TargetDay
TSTART
TEND
TPOINT
2021-08-01
2021-08-31
2021-08-03
db<>fiddle here
See? you do not need any if-then-else at all!
Added bonus is that you now can easily turn your 9 into an SQL parameter, rather than having it a literal constant injected into SQL code via always fragile string splicing, because now you only use 9 once in your query, thus you no more have have to care about changing two different parameters/constants in a query always in sync.
WITH
TargetDay as
(SELECT DATEADD( 1 - (cast( ? as integer )) DAY TO CURRENT_DATE) AS TPoint FROM RDB$DATABASE)
,TargetStart as....
Or in languages like Delphi which simulate named parameters for Firebird, it can be like
WITH
TargetDay as
(SELECT DATEADD( 1 - (cast( :ThresholdDay as integer )) DAY TO CURRENT_DATE) AS TPoint FROM RDB$DATABASE)
,TargetStart as....

Related

Can I search between two dates in SQL using DATE_TRUNC and INTERVAL?

Here is what I have working:
select date_trunc('hour', 123.created_at) AS trunc_created_at
FROM 123abc 123
WHERE 123.expires_at > date_trunc('day', GETDATE()) + INTERVAL '2 days'
Is it possible, from here, to then do an 'and' or 'between' to do something like
and 123.expires_at < date_trunc('day', GETDATE()) + INTERVAL '28 days'
Obviously, that doesn't work, but is there a way to do this using number of days instead of a specific date? I'm pretty new to SQL and have been playing with this for 30 minutes, including a dozen google queries, and can't seem to find a way to make something like this work. Only using between and a specific date range.
If you are just trying to look back 28 days you can just subtract that from GETDATE
SELECT CAST(GETDATE() - 28 AS DATE)
So if you wanted to look back 28 days it would be something like this
SELECT
*
FROM TABLE
WHERE 1=1
AND DATE BETWEEN GETDATE() AND GETDATE() - 28
I figured out how to do what I wanted
WHERE 123.expires_at > (date_trunc('day', GETDATE()) + INTERVAL '2 days') AND 123.expires_at < (date_trunc('day', GETDATE()) + INTERVAL '21 days')

Netezza SQL show only last year's data

it sounds simple and it should be simple but for some reason I can't seem to make it happen in Netezza... So far I tried:
select *
from table
where placed_dt >= DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0);
and it looked like dateadd function doesn't work on Netezza. So I tried:
select *
from table
where placed_dt between (current_date - interval 1 year) and current_date
but still had no luck. Any help would be appreciated!
If you want the last year from the current date:
where placed_dt >= current_date - interval '1 year'
Note that the single quotes are needed.
and you can include the <= current_date if that is also needed.
If you want the last calendar year, there are various methods, but one is:
where date_trunc('year', placed_dt) = date_trunc('year', current_date) - interval '1 year'
You may try:
SELECT *
FROM yourTable
WHERE
placed_dt >= ADD_MONTHS(DATE_TRUNC('year', current_timestamp), -12) AND
placed_at < DATE_TRUNC('year', current_timestamp);
In the above inquality in the WHERE clause, for a current year of 2020, the lower bound represents 2019-01-01 and the upper bound represents 2020-01-01.

SQL Aggregation Join and Subquery Optimisation

I am trying to get aggregate values by time periods of two relations (buys and uses) and join them so that I can get the results in one report and also draw a ratio on them. I am using PostgreSQL. The end report required is: dateTime, u.sum, b.sum, b.sum/u.sum
The following query works but scales very poorly with larger table sizes.
SELECT b2.datetime AS dateTime, b2.sum AS BUY_VOLUME, u1.sum AS USE_VOLUME,
CASE u1.sum
WHEN 0 THEN 0
ELSE (b2.sum / u1.sum)
END AS buyToUseRatio
FROM(
SELECT SUM(b.total / 100.0) AS sum, date_trunc('week', (b.datetime + INTERVAL '1 day')) - INTERVAL '1 day' as datetime
FROM buys AS b
WHERE
datetime > date_trunc('month', CURRENT_DATE) - INTERVAL '1 year'
GROUP BY datetime) AS b2
INNER JOIN (SELECT SUM(u.amount) / 100.00 AS sum, date_trunc('week', (u.datetime + INTERVAL '1 day')) - INTERVAL '1 day' AS datetime
FROM uses AS u
WHERE
datetime > date_trunc('month', CURRENT_DATE) - INTERVAL '1 year'
GROUP BY datetime) AS u1 ON b2.datetime = u1.datetime
ORDER BY b2.datetime ASC;
I was wondering if anyone could help me by providing an alternative query that would get the end result required and is faster to execute.
I appreciate any help on this :-) My junior level SQL is a little rusty and I can't think of another way of doing this without creating indexes. Thanks in advance.
At least, these indexes can help your query:
create index idx_buys_datetime on buys(datetime);
create index idx_uses_datetime on uses(datetime);
Your query seems fine. However, you could use full join (instead of inner) to have all rows, where at least one of your tables have data. You could even use generate_series() to always have 1 year of results, even when there is no data in either of your tables, but I'm not sure if that's what you need. Also, some other things can be written more easily; your query could look like this:
select dt, buy_volume, use_volume, buy_volume / nullif(use_volume, 0.0) buy_to_use_ratio
from (select sum(total / 100.0) buy_volume, date_trunc('week', (datetime + interval '1 day')) - interval '1 day' dt
from buys
where datetime > date_trunc('month', current_timestamp - interval '1 year')
group by 2) b
full join (select sum(amount) / 100.0 use_volume, date_trunc('week', (datetime + interval '1 day')) - interval '1 day' dt
from uses
where datetime > date_trunc('month', current_timestamp - interval '1 year')
group by 2) u using (dt)
order by 1
http://rextester.com/YVASV92568
So the answer depends on how large your tables are, but if it was me, I would create one or two new "summary" tables based on your query and make sure to keep them updated (run a batch job once a day to update them or once an hour with all the data that has changed recently).
Then, I would be able to query those tables and do so, much faster.
If however, your tables are very small, then just keep going the way you are and play around with indexes till you get some timing which is acceptable.

Delete rows by date in ANSI SQL

I want to remove rows from last day, I have a date column, how can I do this in ansi?
delete from mytable where mydate < current_date;
This query deletes both yesterday's and today's records, I want to keep today's records ('today' is from 12 am onwards)
Your statement is valid ANSI SQL and will work on any DBMS that complies with the ANSI SQL specification with regards to DATE handling.
With Oracle the situation is different: a DATE column/value always contains a time as well. So current_date (or sysdate which is the same for the sake of this discussion) will not return 2014-09-17 but e.g. 2014-09-17 16:54:12.
Now if you have a row in your table that contains 2014-09-17 08:54:12 the condition mydate < current_date will be true because 08:54:12 is smaller than 16:54:12 and thus the row will be deleted.
You need to rewrite your statement to:
delete from mytable
where trunc(mydate) < trunc(current_date);
trunc() set the time part of a DATE to 00:00:00 and thus the comparison behaves as if there was no time part involves (because it's the same for both comparison values).
If you really, really need to write this condition in ANSI SQL and taking Oracle's non-standard DATE handling into account you need to do something like this:
select *
from mytable
where (extract(year from mydate) < extract(year from current_date))
or (extract(year from mydate) = extract(year from current_date) and extract(month from mydate) < extract(month from current_date))
or (extract(year from mydate) = extract(year from current_date) and extract(month from mydate) = extract(month from current_date) and extract(day from mydate) < extract(day from current_date));
The extract() function as shown is ANSI SQL.

postgres query to check for records not falling between two time periods using the ISO WEEK number and year from DB

i do have a query which works fine but I was just wondering if there are other ways or alternate method to bettter this.
I have a table where i am fetching those records exceeding or do not fall between 1 year time interval however there is only the year and ISO week number column in the table (integer values).
basically the logic is to check ISO WEEK - YEAR falls between 'current_date - interval '1 year' AND current_date.
My query is as below :
select * from raj_weekly_records where
(date_dimension_week > extract(week from current_date) and date_dimension_year = extract(year from current_date) )
or (date_dimension_week < extract(week from current_date) and (extract(year from current_date)-date_dimension_year=1) )
or(extract(year from current_date)-date_dimension_year>1);
Here date_dimension_week and date_dimension_year are the only integer parameters by which I need to check is there any other alternate or better way?.This code is working fine no issues here.
Here is an idea. Convert the year/week to a numeric format: YYYYWW. That is, the year times 100 plus the week number. Then you can do the logic with a single comparison:
select *
from raj_weekly_records
where date_dimension_year * 100 + date_dimension_week
not between (extract(year from current_date) - 1) * 100 + extract(week from current_date) and
extract(year from current_date) * 100 + extract(week from current_date)
(There might be an off-by one error, depending on whether the weeks at the ends are included or excluded.)
select *
from raj_weekly_records
where
date_trunc('week',
'0001-01-01 BC'::date + date_dimension_year * interval '1 year'
)
+ (date_dimension_week + 1) * interval '1 week'
- interval '1 day'
not between
current_date - interval '1 year' and current_date