How to use defined variable in Where clause - oracle

Defining and selecting variable works just fine in Oracle SQL Developer.
ALTER SESSION SET NLS_LANGUAGE=english; -- First day of week
--DEFINE SUMMER_START_DT = TO_CHAR(TO_DATE('03-24-2022', 'MM-DD-YYYY'),'yyyymmdd')
DEFINE SUMMER_START_DT = TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd') FROM DUAL CONNECT BY level <=1
SELECT &SUMMER_START_DT;
But I get an error when trying to use the variable in Select statement using it as filter in the Where clause.
SELECT a.* FROM TRADE a WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') = &SUMMER_START_DT;
I get the error "SQL command not properly ended"
Hope someone can help me. Thanks
Kind regards
Soren Sig Mikkelsen

You substitution variable includes from dual, which is OK when you just prepend select in your first example; but in the second you end up with two from clauses:
SELECT a.*
FROM TRADE a
WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') =
TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd')
FROM DUAL CONNECT BY level <=1
(You can see that in the generated column name/alias in the output grid; or set verify on and run as a script.)
If you really wanted to use that as the right-hand side of the filter then you could enclose it in parentheses:
SELECT a.* FROM TRADE a WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') = (SELECT &SUMMER_START_DT);
which would become:
SELECT a.*
FROM TRADE a
WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') =
(
SELECT TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd')
FROM DUAL CONNECT BY level <=1
)
But the connect by isn't doing anything here, so you can remove that; and if you remove from dual as well then you can run your first statement as:
SELECT &SUMMER_START_DT FROM DUAL;
and the second as it is.
You could simplify the calculation though. For a start you aren't using the time element, so you don't need to make it 2am; and you can truncate to the start of the year and add two months to get March 1st; as a string if that's really what you want:
to_char(next_day(last_day(add_months(trunc(sysdate, 'YYYY'), 2)) - 7, 'SUNDAY'), 'YYYYMMDD')
db<>fiddle
But you can keep it as a date; if you:
DEFINE SUMMER_START_DT = next_day(last_day(add_months(trunc(sysdate, 'YYYY'), 2)) - 7, 'SUNDAY')
then again you can do:
SELECT &SUMMER_START_DT FROM DUAL;
and your second query can be:
SELECT a.*
FROM TRADE a
WHERE a.TRADE_DATE_TIME >= &SUMMER_START_DT
AND a.TRADE_DATE_TIME < &SUMMER_START_DT + 1
which avoids converting every TRADE_DATE_TIME date value to a string to compare it, and allows an index on that date column to be used.

Related

How to convert this code from oracle to redshift?

I am trying to implement the same in redshift and i am finding it little difficult to do that. Since redshift is in top of postgresql engine, if any one can do it in postgresql it would be really helpfull. Basically the code gets the count for previous two month at column level. If there is no count for exact previous month then it gives 0.
This is my code:
with abc(dateval,cnt) as(
select 201908, 100 from dual union
select 201907, 200 from dual union
select 201906, 300 from dual union
select 201904, 600 from dual)
select dateval, cnt,
last_value(cnt) over (order by dateval
range between interval '1' month preceding
and interval '1' month preceding ) m1,
last_value(cnt) over (order by dateval
range between interval '2' month preceding
and interval '2' month preceding ) m2
from (select to_date(dateval, 'yyyymm') dateval, cnt from abc)
I get error in over by clause. I tried to give cast('1 month' as interval) but still its failing. Can someone please help me with this windows function.
expected output:
Regards
This is how I would do it. In Redshift there's no easy way to generate sequences, do I select row_number() from an arbitrary table to create a sequence:
with abc(dateval,cnt) as(
select 201908, 100 union
select 201907, 200 union
select 201906, 300 union
select 201904, 600),
cal(date) as (
select
add_months(
'20190101'::date,
row_number() over () - 1
) as date
from <an arbitrary table to generate a sequence of rows> limit 10
),
with_lag as (
select
dateval,
cnt,
lag(cnt, 1) over (order by date) as m1,
lag(cnt, 2) over (order by date) as m2
from abc right join cal on to_date(dateval, 'YYYYMM') = date
)
select * from with_lag
where dateval is not null
order by dateval

encountered the symbol FROM when expecting one of the following pl sql

I am trying to extract year from datefield but when I use extract (year from
datefield) I get this error
Encountered the symbol FROM when expecting one of the following pl sql
cursor o1 is
select substr(tarifa,1,2), count(*)
from pol p, uvod u, doppov d
where extract(year FROM datum_dop) = EXTRACT(YEAR FROM sysdate)
and izdavanje >='1-jul-13'
and p.orgjed = u.sorgz (+)
and DATUM_PREKIDA is not null
and p.polica=d.polica and d.pov_dopl='P'
and d.status='F'
and cisti_ao(p.polica)!=0
group by substr(tarifa,1,2);
Where did I made mistake ?
Ah, this is Forms, probably 6i.
Its engine doesn't know extract function. Change that line to
where to_char(datum_dop, 'yyyy') = to_char(sysdate, 'yyyy')
This would, though, make index on datum_dop column (if it exists) unusable and force Oracle to convert dates to strings, so you'd rather try with
where datum_dop >= trunc(sysdate, 'yyyy')
and datum_dop < add_months(trunc(sysdate, 'yyyy'), 12)
Other than that:
count(*) should have an alias (if you plan to use it), e.g. count(*) as broj_tarifa
if izdavanje is date, don't compare it to a string ('1-jul-13') but date, e.g. izdavanje >= to_date('01.07.2013', 'dd.mm.yyyy')
use table aliases for all columns

Putting values into a collection for different date ranges

I am writing a PL/SQL procedure which gives the count of a query based on date range values. I want to get the date range dynamically and I have written a cursor for that.
I am using a collection and getting the counts of each month, the problem I am facing is that collection is populated with the count of the last month alone. I want to get the count of all months. Can anyone help?
This is the procedure I have written:
create or replace
Procedure Sample As
Cursor C1 Is
With T As (
select to_date('01-JAN-17') start_date,
Last_Day(Add_Months(Sysdate,-1)) end_date from dual
)
Select To_Char(Add_Months(Trunc(Start_Date,'mm'),Level - 1),'DD-MON-YY') St_Date,
to_char(add_months(trunc(start_date,'mm'),level),'DD-MON-YY') ed_date
From T
Connect By Trunc(End_Date,'mm') >= Add_Months(Trunc(Start_Date,'mm'),Level - 1);
Type T_count_Group_Id Is Table Of number;
V_count_Group_Id T_count_Group_Id;
Begin
For I In C1
Loop
Select Count(Distinct c1) bulk collect Into V_Count_Group_Id From T1
Where C2 Between I.St_Date And I.Ed_Date;
End Loop;
For J In V_Count_Group_Id.First..V_Count_Group_Id.Last
Loop
Dbms_Output.Put_Line(V_Count_Group_Id(J));
end loop;
END SAMPLE;
Your bulk collect query is replacing the contents of the collection each time around the loop; it doesn't append to the collection (if that's what you expected). So after your loop you are only seeing the result of the last bulk collect, which is the latest month from your cursor.
You're also apparently comparing dates as string, which isn't a good idea (unless c2 is stored as a string - which is even worse). And as between is inclusive, you risk including data for the first day of each month in two counts, if the stored time portion is midnight. It's safer to use equality checks for date ranges.
You don't need to use a cursor to get the dates and then individual queries inside that cursor, you can just join your current cursor query to the target table - using an outer join to allow for months with no matching data. Your cursor seems to be looking for all months in the current year, up to the start of the current year, so that could perhaps be simplified to:
with t as (
select add_months(trunc(sysdate, 'YYYY'), level - 1) as st_date,
add_months(trunc(sysdate, 'YYYY'), level) as ed_date
from dual
connect by level < extract(month from sysdate)
)
select t.st_date, t.ed_date, count(distinct t1.c1)
from t
left join t1 on t1.c2 >= t.st_date and t1.c2 < t.ed_date
group by t.st_date, t.ed_date
order by t.st_date;
You can use that to populate your collection:
declare
type t_count_group_id is table of number;
v_count_group_id t_count_group_id;
begin
with t as (
select add_months(trunc(sysdate, 'YYYY'), level - 1) as st_date,
add_months(trunc(sysdate, 'YYYY'), level) as ed_date
from dual
connect by level < extract(month from sysdate)
)
select count(distinct t1.c1)
bulk collect into v_count_group_id
from t
left join t1 on t1.c2 >= t.st_date and t1.c2 < t.ed_date
group by t.st_date, t.ed_date
order by t.st_date;
for j in v_count_group_id.first..v_count_group_id.last
loop
dbms_output.put_line(v_count_group_id(j));
end loop;
end;
/
although as it only stores/shows the counts, without saying which month they belong to, that might not ultimately be what you really need. As the counts are ordered, you at least know that the first element in the collection represents January, i suppose.

Oracle - Inline View - External WHERE condition applied inline

We have a table where dates are stored as VARCHAR2(It's legacy data table which we have no control on!) in format of YYYYMMDD. We have only SELECT privilege on table (Not feasible to write procedure/functions).
Need to select all rows from table where date is > sysdate.
We apply regular expression check for valid format and additional checks to ensure that day is valid for given month. All this works great! Our inline view selection ensures that only records with valid date strings are picked.
But, when we apply condition to check > sysdate as an external clause - we get invalid date for given month error even though inline view selection ensures that no such records are picked.
It looks like query execution is applying the condition from outer clause before inline view conditions are applied. Appreciate any comments on the behavior; and, how can we ensure that conditions from outside are applied only once inline conditions are met?
Data and Used Queries:
CREATE TABLE TEST_DATA_TABLE
(
DATESTRING VARCHAR2(20 BYTE)
);
Insert 3 rows with values:
19960322 --Valid Date in past
19831131 --Invalid Date 11/31
20180224 --Valid Date > SYSDATE
Valid data selection:
(Where clause 1 ensures format and clause 2 ensures valid date for month):
SELECT datestring AS i_dob
FROM test_data_table
WHERE REGEXP_LIKE (TRIM (datestring),
'(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])')
AND TRIM (datestring) <=
TO_CHAR (
LAST_DAY (
TO_DATE (SUBSTR (TRIM (datestring), 1, 6) || '01',
'YYYYMMDD')),
'YYYYMMDD')
Above query works fine and return valid rows with valid date string,
To select records having date string > SYSDATE, above data is used inline and we apply condition >SYSDATE as below.
SELECT i_dob
FROM (
SELECT datestring AS i_dob
FROM test_data_table
WHERE REGEXP_LIKE (TRIM (datestring),
'(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])')
AND TRIM (datestring) <=
TO_CHAR (
LAST_DAY (
TO_DATE (SUBSTR (TRIM (datestring), 1, 6) || '01',
'YYYYMMDD')),
'YYYYMMDD')
) X
WHERE TO_DATE (X.I_DOB, 'YYYYMMDD') > SYSDATE
It starts throwing error: ORA-01839: date not valid for month specified
Looks like the conditions are applied before all inline view conditions are checked.
You can get around this by making the inline view be actioned before the outer query by adding an additional rownum column to it (the presence of the rownum column means that Oracle needs to calculate that column for the subquery it applies to before it can further filter on it.
So, your query would become:
SELECT i_dob
FROM (
SELECT datestring AS i_dob,
rownum rn
FROM test_data_table
WHERE REGEXP_LIKE (TRIM (datestring),
'(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])')
AND TRIM (datestring) <=
TO_CHAR (
LAST_DAY (
TO_DATE (SUBSTR (TRIM (datestring), 1, 6) || '01',
'YYYYMMDD')),
'YYYYMMDD')
) X
WHERE TO_DATE (X.I_DOB, 'YYYYMMDD') > SYSDATE;
I would add a comment into your query explaining the presence of this column, otherwise some developer in the future may think "eh? This does nothing; I'll remove it!".
(Also of note is in 12.2, they have added/amended functions to allow for easier data validation, which would simplify your query when you get to 12.2!)

Alternate for decode function

I have a table 'Holiday' which lists a set of holiday details.If i specify a date,I should obtain a result date after 5 days of specified date.If there is holiday in between it should exclude them and display the non holiday date.I have table named holiday which includes holiday date,holiday type|(weekly off,local holiday).Now i have used nested decode for continuous holiday checking.Tell me how this can be changed in case function.
DECODE
(date,
holidaydate, DECODE
(date + 1,
holidaydate + 1, DECODE
(date + 2,
holidaydate + 2, DECODE
(date + 3,holidaydate+3,date+4,date+3),date+2),date+1),date);
This can be achieved with a simple subquery which counts the number of holiday dates between a specified date and date+5. The following will return a date that is five non-holiday days in the future:
testdate+(select 5+count(1)
from holiday
where holidaydate between testdate
and testdate + 5)
Simply change both "5"s so another number to change the evaluation period.
SQLFiddle here
Edit - based on comment below, my code doesn't evaluate any days after the fifth day. This would probably be much easier with a function, but the following cte-based code will work also:
with cte as ( (select alldate,holidaydate
from (select to_date('20130101','yyyymmdd')+level alldate
from dual
connect by level < 10000 -- adjust for period to evaluate
) alldates
left join holiday on alldate=holidaydate) )
select
testdate,test_plus_five
from (
select
alldate test_plus_five,testdate,
sum(case when holidaydate is null
then 1
else 0 end) over (partition by testdate order by alldate) lastday
from
cte,
testdates
where
alldate >= testdate
group by
alldate,holidaydate,testdate)
where
lastday = 6
This script builds a calendar table so it can evaluate each day (holiday or non-holiday); then we get a running count of non-holiday days, and use the sixth one.
SQLFiddle here
AFAIK, You can use CASE alternative to DECODE in Oracle
CASE [ expression ]
WHEN condition_1 THEN result_1
WHEN condition_2 THEN result_2
...
WHEN condition_n THEN result_n
ELSE result
END
Finally i found the optimal solution.Thanks for ur response guys. SELECT dt FROM
(SELECT dt FROM (SELECT TO_DATE('15-AUG-2013','dd-mon-yyyy')+LEVEL dt FROM DUAL
CONNECT BY LEVEL < 30)
WHERE
(SELECT COUNT (*) FROM mst_holiday WHERE holidaydate = dt) = 0 )
where rownum=1

Resources