oracle case or decode? - how to handle no recs - sql

How can I make sure in this case statement that if I get now rows selected that I set that my result=1?
I want a value of 1 if I have no records or a count of 0. I would also want a null date to be sysdate by default.
Here is what I have so far. It works with (sysdate-1), but when I tested it with (sysdate-0) or =sysdate (today's date), I
was getting no records. So I want to be able to handle null values too.
How can I change this query to do that? or would I use something like DECODE?
select
to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD') as mydt,
case when count(*) = 0 then 1 else 0 end result
from Table1
where
to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD') >= trunc(sysdate)-1
GROUP BY
to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD')
Here is my table desc. Perhaps, I should go off of timestamp instead?
There is also a value of counts which may help change this query or not. So what i'm looking for is coming up
with a value of 1 or 0 with a timestamp if that's possible.
SQL> desc Table1
Name Null? Type
----------------------------------------- -------- ----------------------------
YEAR NUMBER
QUARTER NUMBER
MONTH NUMBER
DAY NUMBER
HOUR NUMBER
TIMESTAMP NUMBER
CNT_N NUMBER
ACTN_N NUMBER
ADDR_C VARCHAR2(255)

You will never get a count(*) of zero with a group by. If there are no rows, you won't get any groups and if there are rows then any group you get will show the number of rows.
If you have 3 rows for January 1st and another 3 for January 3rd, you will get :
2011/01/01 3
2011/01/03 3
You won't get a row for January 2nd. Are you wanting to generate that row ? Is there a maximum number of rows that you want returned ?

I think you can do modify your query like below to assign the sysdate to null values
select
nvl(to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD') ,sysdate) as mydt
from Table1
where
nvl(to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD') ,sysdate)
>= trunc(sysdate)-1
GROUP BY nvl(to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD') ,sysdate)
but your second requirement is not very much understood.... please clarify what exectly you want with count(*)
if you want to assign 1 for null date count then you can add following line in select statment
case
when nvl(to_date(year||'/'||month||'/'||day, 'YYYY/MM/DD') ,sysdate) = sysdate then
1
else
count(*)-- or 0 ???!!!!!

It looks like you need to generate the date(s) and then see how may records match each one. Assuming you don't have any data in the future you can do something like:
select trunc(sysdate) + level - (:range + 1) as dt
from dual
connect by level <= (:range + 1);
which with :range set to 1 gives two dates:
DT
---------
19-JUL-11
20-JUL-11
You can then have outer join to that list of dates:
with tmp_dt as (
select trunc(sysdate) + level - (:range + 1) as dt
from dual
connect by level <= (:range + 1)
)
select td.dt as mydt,
case when count(t.year) = 0 then 1 else 0 end as result
from tmp_dt td
left join table1 t on t.year = extract(year from td.dt)
and t.month = extract(month from td.dt)
and t.day = extract(day from td.dt)
group by td.dt
order by td.dt;
If I only have any data in the table for 19-Jul-11, I get:
MYDT RESULT
--------- ----------
19-JUL-11 0
20-JUL-11 1
If you do have data in the future this won't show it; range is how many days to look in the past. If you know there's a limit of, say, seven days you can use connect by level <= (:range + 1) + 7 or have a second variable, but it rather depends on what you want to see.
I've swapped the join around a bit to avoid doing a date conversion for every row in the table, extracting the relevant part of the generated date instead. I hope you have a reason for storing the date components in separate fields rather than as a date.
If you're only looking for data from today, just change the date generator:
with tmp_dt as (select trunc(sysdate) as dt from dual)
select td.dt as mydt,
case when count(t.year) = 0 then 1 else 0 end result
from tmp_dt td
left join table1 t on t.year = extract(year from td.dt)
and t.month = extract(month from td.dt)
and t.day = extract(day from td.dt)
group by td.dt;
If you always want yesterday, it would be select trunc(sysdate) - 1 as dt from dual, etc.

May be you can encapsulate your whole query within the NVL function
like this
SELECT NVL(TO_CHAR((select to_date(year || '/' || month || '/' || day,
'YYYY/MM/DD') as mydt
from Table1
where to_date(year || '/' || month || '/' || day,
'YYYY/MM/DD') >= trunc(sysdate) - 1
GROUP BY to_date(year || '/' || month || '/' || day,
'YYYY/MM/DD')),
'DD/MM/YYYY HH:MI:SS AM'),
'1')
FROM DUAL
I have removed the case statement from the query
Instead , what this query will do is, in case your query does return any value, it will do so without interference. However, in case the query returns NOTHING, then the NVL function will take over and return 1
I have used the TO_CHAR function to maintain the datatype of both the arguments in the NVL function. Both the date returned and the value for NVL are character based
Hope it helps

Related

how to convert static to dynamic function/procedure or query

DESIRE O/P LINK WHICH I EXPECT PLEASE CLICK THIS SCREENSHOOT.PNGI created following static query
SELECT '1' AS KPI_ID,
'P2P' AS KPI_DESC,
'22-MAR-17' AS dates,
(SUM (
CASE
WHEN S_DATE BETWEEN ADD_MONTHS ('22-MAR-17', 0) - 13
AND ADD_MONTHS ('22-MAR-17', 0) - 7
THEN
VOLUME
ELSE
0
END))
LAST_WEEK_Volume,
(SUM (
CASE
WHEN S_DATE BETWEEN ADD_MONTHS ('22-MAR-17', 0) - 6
AND ADD_MONTHS ('22-MAR-17', 0)
THEN
VOLUME
ELSE
0
END))
THIS_WEEK_Volume
FROM TABLE
problem statement:
Actually I have two years old data ..
I want calculate last week volume,current week volume day wise for 2 years.
So what should I modify in query so I can get dynamic query.
The current query gives only 1 day calculation.
I want to do per day wise calculation.
According to google I found that after defining paramater start_date and end_date and passing to column it can be possible,but i dont know what is right or wrong ?
Could u help me out please?
You could try something like this:
with dates_table as (
SELECT to_date(:begin_date, 'dd/mm/yyyy') + ROWNUM - 1 c_date
FROM dual
CONNECT BY LEVEL <= to_date(:end_date, 'dd/mm/yyyy') - to_date(:begin_date, 'dd/mm/yyyy') + 1)
SELECT '1' AS KPI_ID,
'P2P' AS KPI_DESC,
c_date AS dates,
(SUM (
CASE
WHEN S_DATE BETWEEN ADD_MONTHS (c_date, 0) - 13
AND ADD_MONTHS (c_date, 0) - 7
THEN
VOLUME
ELSE
0
END))
LAST_WEEK_Volume,
(SUM (
CASE
WHEN S_DATE BETWEEN ADD_MONTHS (c_date, 0) - 6
AND ADD_MONTHS (c_date, 0)
THEN
VOLUME
ELSE
0
END))
THIS_WEEK_Volume
FROM TABLE, dates_table
Here dates_table will contain all the dates between begin_date and end_date (as rows). So we join that table with your 'static' query to get the desired result.
What is "dynamic" in this query? Seems to be '22-MAR-17' (which was Wednesday).
So, if you pass a different date, you'll get a different result. How to pass it? Using a variable. As you tagged SQL Developer, you'd use a :param_name syntax (parameter name precede by a colon), such as
select count(*) from emp where deptno = :par_deptno
If it is a PL/SQL (is it? How?), then what kind of a PL/SQL is it? If anonymous block, you could use the same principle as above (i.e. :param_name). If it is a procedure, you'd rather create a parameter and pass a value to it.

map/sample timeseries data to another timeserie db2

I am trying to combine the results of two SQL (DB2 on IBM bluemix) queries:
The first query creates a timeserie from startdate to enddate:
with dummy(minute) as (
select TIMESTAMP('2017-01-01')
from SYSIBM.SYSDUMMY1 union all
select minute + 1 MINUTES
from dummy
where minute <= TIMESTAMP('2018-01-01')
)
select to_char(minute, 'DD.MM.YYYY HH24:MI') AS minute
from dummy;
The second query selects data from a table which have a timestamp. This data should be joined to the generated timeseries above. The standalone query is like:
SELECT DISTINCT
to_char(date_trunc('minute', TIMESTAMP), 'DD.MM.YYYY HH24:MI') AS minute,
VALUE AS running_ct
FROM TEST
WHERE ID = 'abc'
AND NAME = 'sensor'
ORDER BY minute ASC;
What I suppose to get is a query with one result with contains of two columns:
first column with the timestamp from startdate to enddate and
the second with values which are sorted by there own timestamps to the
first column (empty timestamps=null).
How could I do that?
A better solution, especially if your detail table is large, is to generate a range. This allows the optimizer to use indices to fulfill the bucketing, instead of calling a function on every row (which is expensive).
So something like this:
WITH dummy(temporaer, rangeEnd) AS (SELECT a, a + 1 MINUTE
FROM (VALUES(TIMESTAMP('2017-12-01'))) D(a)
UNION ALL
SELECT rangeEnd, rangeEnd + 1 MINUTE
FROM dummy
WHERE rangeEnd < TIMESTAMP('2018-01-31'))
SELECT Dummy.temporaer, AVG(Test.value) AS TEXT
FROM Dummy
LEFT OUTER JOIN Test
ON Test.timestamp >= Dummy.temporaer
AND Test.timestamp < Dummy.rangeEnd
AND Test.id = 'abc'
AND Test.name = 'text'
GROUP BY Dummy.temporaer
ORDER BY Dummy.temporaer ASC;
Note that the end of the range is now exclusive, not inclusive like you had it before: you were including the very first minute of '2018-01-31', which is probably not what you wanted. Of course, excluding just the last day of a month also strikes me as a little strange - you most likely really want < TIMESTAMP('2018-02-01').
found a working solution:
with dummy(temporaer) as (
select TIMESTAMP('2017-12-01') from SYSIBM.SYSDUMMY1
union all
select temporaer + 1 MINUTES from dummy where temporaer <= TIMESTAMP('2018-01-31'))
select temporaer, avg(VALUE) as text from dummy
LEFT OUTER JOIN TEST ON temporaer=date_trunc('minute', TIMESTAMP) and ID='abc' and NAME='text'
group by temporaer
ORDER BY temporaer ASC;
cheers

SQL many sysdate in a query

I have a big query with many sysdate, everytime i must check for some sysdate ( +1 +2 +3 etc) i must change in every part and i lost many time, i'd like to create a variable or something on top of my query with unique sysdate who change every sysdate in the query.
this is a little part as example
SELECT DISTINCT(COD)
FROM TABLE_COD
WHERE START_DATE <= trunc(sysdate + 5)
AND END_DATE >= trunc(sysdate + 5)
AND VALUE = 1
AND (COD_REF = ( SELECT to_char(sysdate + 5, 'D') FROM dual) OR COD_REF = 0)
.......continue
If I understand your question correctly, you need something like this.
with sysdate_plus_n as
(select sysdate+1 as sysdate_plus_1 ,-- other variables
sysdate+5 as sysdate_plus_5 from dual)
--This is a temporary table. So if you query
--(select sysdate_plus_5 from sysdate_plus_n), you will get sysdate+5
--You can change your values in this temporary table
SELECT DISTINCT(COD)
FROM TABLE_COD
WHERE START_DATE <= trunc(select sysdate_plus_5 from sysdate_plus_n)
AND END_DATE >= trunc(select sysdate_plus_5 from sysdate_plus_n)
AND VALUE = 1
AND (COD_REF = ( SELECT to_char(select sysdate_plus_5 from sysdate_plus_n, 'D')
FROM dual) OR COD_REF = 0)
.......continue
Now just change values in the temporary table. You dont have to touch the query.
You can join the date:
select ...
from ...
cross join (select trunc(sysdate) + 5 as mydate from dual)
where start_date <= mydate
and end_date >= mydate
and value = 1
and cod_ref in (to_char(mydate, 'D', 'NLS_DATE_LANGUAGE=AMERICAN'), 0)
...
Please see also that I added the NLS_DATE_LANGUAGE to TO_CHAR, because otherwise the output would be session-dependent (i.e. one session may regard Sunday the first day of the week, another Monday).
Use a substitution variable i.e. & and enter the value when prompted. Thus, you will enter the value only once and it will be used simultaneously everywhere in the code.
For example,
SQL> SELECT &dt FROM dual;
Enter value for dt: SYSDATE
old 1: SELECT &dt FROM dual
new 1: SELECT SYSDATE FROM dual
SYSDATE
---------
23-DEC-15
SQL> /
Enter value for dt: SYSDATE +5
old 1: SELECT &dt FROM dual
new 1: SELECT SYSDATE +5 FROM dual
SYSDATE+5
---------
28-DEC-15
AND (COD_REF = ( SELECT to_char(sysdate + 5, 'D') FROM dual) OR COD_REF = 0)
On a side note, you don't have to use a sub-query all the time. SYSDATE is an in-built function, so you can call it directly. No need to select it from dual.
AND (COD_REF = to_char(sysdate + 5, 'D') OR COD_REF = 0)
If you want multiple days at a time, how about writing one query for the purpose? Something like this:
WITH params AS (
SELECT 5 as val FROM DUAL UNION ALL
SELECT 6 as val FROM DUAL
)
SELECT DISTINCT v.val, COD
FROM params CROSS JOIN
TABLE_COD c
WHERE START_DATE <= trunc(sysdate + params.val) AND END_DATE >= trunc(sysdate + params.val) AND
VALUE = 1 AND
(COD_REF = (to_char(sysdate + params.val, 'D') OR COD_REF = 0);
With this structure, you can add as many values as you want to the query (including only one).
As I understand, the value of SYSDATE is evaluated only once before the query is actually ran. In other words, the value of SYSDATE will stay constant throughout your complete query. Knowing this detail may greatly help or solve your question.
I do think the best solution in your case would be to create a user function to be called as many times as you want from within your select statement. Then, when you need any changes, just update the function instead.
I don't think there's a way to create a variable at the beginning of your query in order to be reused through out a single statement. You can however, use the WITH clause in order to avoid repeating embedded queries and improve performance and ease of reading. This is similar to what you need, but wanted to mention it as it might be a solution too depending on your needs.
Check out this link for more information on Oracle Functions:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5009.htm
I hope this helps!
WITH FIX_SYSDATE AS
(SELECT TRUNC(SYSDATE) AS FIXED_SYSDATE
FROM DUAL)
SELECT DISTINCT(COD)
FROM TABLE_COD
CROSS JOIN FIX_SYSDATE FS
WHERE START_DATE <= FS.FIXED_SYSDATE + 5
AND END_DATE >= FS.FIXED_SYSDATE + 5
AND VALUE = 1
AND (COD_REF = (SELECT TO_CHAR(FS.FIXED_SYSDATE + 5, 'D')
FROM DUAL)
OR COD_REF = 0)
.......continue
Fix TRUNC(SYSDATE) or simply SYSDATE (I don't know the remaining of your query, since there is "... continue" there) and then use it as many times as required on your main query.

Combining dates fields from separate tables

I have two tables that are almost exactly the same. The only difference is one is an archive table (call that B) that has any records removed from the other table (call that A)
I'm needing to get ALL records from a given data range, thus I need to join the two tables (and actually join them to a third to get a piece of information that's not on those tables, but that doesn't affect my problem).
I'm wanting to group by the hour the record comes from (i.e. trunc(<date_field>, 'hh')
However, since I need to get records from each hour from the two tables it seems I would need to generate a single date field to group on, otherwise the group wouldn't make sense; each record will only have a date from one field so if I group by either table's date field it would inherently omit records from the other, and if I group by both I'll get no data back as no record appears in both tables.
SO, what I want to do is add two "dates" and have it work like it would in Excel (i.e. the dates get treated as their numeric equivalent, get added, and the resultant date is returned, which by the way is at least one case where adding dates is valid, despite this thread's opinion otherwise)
This makes even more sense as I'll be replacing the null date value with 0 so it should functionally be like adding a number to a date (12/31/14 + 1 = 1/1/15).
I just haven't been able to get it to work. I've tried several iterations to get the calculation to work the latest being:
SELECT DISTINCT Avg(NVL(to_number(to_char(trunc(fcr.actual_start_date, 'hh')))*86400, 0) + NVL(to_Number(to_char(trunc(acr.actual_start_date, 'hh')))*86400, 0)) Start_Num, SUM(AA.SESSIONCPU) TotalCPU, Count(1) Cnt
, SUM((NVL(to_number(to_char(trunc(fcr.actual_completion_date, 'hh')))*86400, 0) + NVL(to_Number(to_char(trunc(acr.actual_completion_date, 'hh')))*86400, 0)
- NVL(to_number(to_char(trunc(fcr.actual_start_date, 'hh')))*86400, 0) - NVL(to_Number(to_char(trunc(acr.actual_start_date, 'hh')))*86400, 0))) TotRun
FROM PSTAT.A$_A AA
LEFT OUTER JOIN APPL.FND_CR FCR On FCR.O_SES_ID = AA.SEsID
LEFT OUTER Join XX.E_FND_CR ACR on ACR.O_SES_ID = aa.sesid
WHERE (trunc(fcr.actual_start_date) >= to_date('28-Dec-2014', 'DD-MON-YYYY')
Or trunc(acr.actual_start_date) >= to_date('28-Dec-2014', 'DD-MON-YYYY'))
AND rownum <= 1048500
and (acr.status_code = 'C' or fcr.status_Code = 'C')
AND aa.sessioncpu is not null
GROUP BY to_number(NVL(trunc(fcr.actual_start_date, 'hh'), 0))*86400 + to_Number(NVL(trunc(acr.actual_start_date, 0), 'hh'))*86400
ORDER BY 2, 1;
My explicit problem with the code above is that Toad keeps ignoring the casts and says it is expecting a date value when it gets a number (the 0 gets highlighted). So if someone could:
A) Tell my why Toad would ignore the casts (it should be seeing a number and so should have absolutely no expectation of a date)
B) Provide any suggestions on how to get the addition to work, or failing that suggest an alternative route to combine the three tables so that I'm able to group by the start date values
As always, any help is much appreciated.
Adding dates or casting them to number throws ORA-00975: date+date not allowed and ORA-01722: invalid number.
So what can be done here to operate on dates in Excel way? My idea is to substract first day from calendar to_date(1, J) from each date you want to operate on.
Example with test dates:
with test_data as (
select sysdate dt from dual union all
select to_date(1, 'J') from dual union all
select null from dual )
select nvl(trunc(dt, 'hh') - to_date(1, 'J'), 0) num_val, dt,
to_char(dt, 'J') tc1, to_char(dt, 'yyyy-mm-ss hh24:mi:ss') tc2
from test_data
NUM_VAL DT TC1 TC2
---------- ---------- ------- -------------------
2457105,96 2015-03-24 2457106 2015-03-14 23:12:14
0 4712-01-01 0000001 4712-01-00 00:00:00
0
#David, your suggestion seems to have worked like charm. For those who come along afterwards my code as updated follows:
SELECT trunc(cr.actual_start_date, 'hh') Start_Date, SUM(AA.SESSIONCPU) TotalCPU,
Count(1) Cnt, SUM((cr.Actual_Completion_Date - cr.Actual_Start_Date)*86400) TotalRun
FROM (SELECT Actual_Start_Date, Actual_Completion_Date, Oracle_Session_ID, Status_Code
FROM APPL.FND_CR
UNION ALL
SELECT Actual_Start_Date, Actual_Completion_Date, Oracle_Session_ID, Status_Code
FROM XX.E_FND_CR) cr
RIGHT OUTER JOIN PSTAT.A$_A AA ON cr.Oracle_Session_ID = AA.SessionID
WHERE trunc(cr.actual_start_date) >= to_date('28-Dec-2014', 'DD-MON-YYYY')
AND rownum <= 1048500
and cr.status_code = 'C'
GROUP BY trunc(cr.actual_start_date, 'hh')
ORDER BY 1;

For loop in a select statement?

Can you use a for loop in a select statement (specifically, a case) in order to define multiple values?
Here's my code:
select case when sysdate in ( declare k DATE:= '2015-01-01',
Begin
FOR i in 1...365 LOOP
k:=sydate +1;
END LOOP;
END;
) then '1' else 'n/a' end FISCAL_YEAR from dual
There may be multiple issues with my syntax..I am trying to say that when today's date is within the year, then a '1' shows up in the FISCAL_YEAR column. This would be a much cleaner way then using a case statement for each day, so any help would be appreciated. Thank you.
You probably just need something like:
select case when to_char(sysdate,'YYYY') = '2015' then 1 else 0 end
from dual
No idea what you're trying to do with that "nested" pl/sql block O.o
Just fill in "the year" in place of 2015 as you need. (ie a variable as you need).
If that's not what you need, please provide a small sample showing input and output expected.
There are easier ways of doing this:
SELECT CASE WHEN TRUNC(sysdate, 'YEAR') = DATE'2015-01-01' THEN '1' ELSE 'n/a' END AS fiscal_year
FROM dual;
or you could use DECODE():
SELECT DECODE(TRUNC(sysdate, 'YEAR', DATE'2015-01-01', '1', 'n/a') AS fiscal_year
FROM dual;
Instead of TRUNC(sysdate, 'YEAR') you could use TO_CHAR(sysdate, 'YYYY') etc.
There are two places you can put your check, depending on which rows you want in your result set. If you want all rows back to a specific date which may be beyond the current FY, use a check in a case in the selection list.
select <whatever>,
case when DateField between date '2015-01-01'
and Add_Months( date '2015-01-01', 12 ) - 1
then 'Y' else 'N' end
as InFY2015
from DataSource;
If you want the result set to contain only rows with dates within the FY, put the check in the where clause.
select <whatever>
from DataSource
where DateField between date '2015-01-01'
and Add_Months( date '2015-01-01', 12 ) - 1;
In the latter query, there is no need for an indicator that says "this row in FY2015" because they all are.
I think previous answer with time interval is the right solution to your problem.
However to answer your question whether there is a way how to loop inside select statement: No, but there is a way how to generate set of increasing numbers, so this code works like your intended pseudocode:
select case when a.c>0 then '1' else 'n/a' end FISCAL_YEAR
from (select count(*) c from (
SELECT ROWNUM+to_date('2015-01-01','yyyy-mm-dd')-1 n
FROM ( SELECT 1
FROM dual
CONNECT BY LEVEL <= 365)) lo
where trunc(sysdate)=lo.n) a