how to convert static to dynamic function/procedure or query - sql

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.

Related

SQL Report - query multiple tables

I have been working on a Stats page in APEX and currently have the following report query:
select to_char(DATELOGGED,'Month - YYYY') as Month,
COUNT(*) as "Total Calls",
SUM(case when CLOSED is null then 1 else null end) as "Open",
COUNT(case CLOSED when 'Y' then 1 else null end) as "Closed",
SUM(case when EXTREF is null then 0 else 1 end) as "Referred",
round((COUNT(case SLA_MET when 'Y' then 1 else null end)/COUNT(case CLOSED when 'Y' then 1 else null end)*100),2) as "SLA Met %"
from IT_SUPPORT_CALLS
GROUP BY to_char(DATELOGGED,'Month - YYYY')
order by MIN (DATELOGGED) desc
I wish to add the sum of DURATION from a different table:
select
"START_TIME",
DECODE(DURATION,null,'Open',((select extract( minute from DURATION )
+ extract( hour from DURATION ) * 60
+ extract( day from DURATION ) * 60 * 24
from dual)||' minutes')) DURATION
from "IT_DOWNTIME"
The IT_DOWNTIME table uses START_TIME (varchar2) as the date identifier, the IT_SUPPORT_CALLS uses DATELOGGED (DATE) as date identifier.
The current output for IT_DOWNTIME is for example:
08-FEB-2019 - 30 Minutes
20-FEB-2019 - 15 Minutes
I would like the report SUM and group IT_DOWNTIME and add this into the existing report.
Hope this makes sense.
Please let me know if I missed any information that would help to resolve this.
Many thanks
Thanks for that, much appreciated. Unfortunately it doesn't return any data from IT_DOWNTIME.
I'm guessing the different date formats doesn't help, hope this clears things up a bit:
These are the columns in IT_DOWNTIME that are of interest:
START_TIME ( VARCHAR2(30) )
DURATION ( INTERVAL DAY(2) TO SECOND(6) )
Example of current IT_DOWNTIME output without formatting:
START_TIME
06-JUL-2016 11:05
DURATION
+00 00:35:00.000000
Example of current IT_SUPPORT_CALLS output without formatting:
DATELOGGED
06/07/2016
Something like this will probably do it, but there has been some guesswork as to your column names etc:
SELECT *
FROM
(
SELECT
to_char(DATELOGGED,'MON-YYYY') as Month,
COUNT(*) as Total_Calls,
SUM(case when CLOSED is null then 1 else null end) as case_Open,
COUNT(case CLOSED when 'Y' then 1 else null end) as case_Closed,
SUM(case when EXTREF is null then 0 else 1 end) as case_Referred,
round((COUNT(case SLA_MET when 'Y' then 1 else null end)/COUNT(case CLOSED when 'Y' then 1 else null end)*100),2) as percent_SLA_met
FROM IT_SUPPORT_CALLS
GROUP BY to_char(DATELOGGED,'MON-YYYY')
) calls
LEFT JOIN
(
SELECT
SUBSTR(START_TIME, 4) as down_month,
SUM(extract(minute from DURATION) +
extract(hour from DURATION) * 60 +
extract(day from DURATION) * 60 * 24
) || 'minutes' as total_down_mins
FROM IT_DOWNTIME
WHERE duration is not null
GROUP BY SUBSTR(START_TIME, 4)
) downs
ON calls.month = downs.down_month
Changed your date formatting of the first query to be MON-YYYY to make it align with what you claim is the formatting of the varchar2 date of the second query (dd-mon-yyy), and substringed the date to remove the day, leaving just the month
Edit:
Ok, so since you've posted some different example data from IT_DOWNTIME I see the problem: there's a time on the date also. Your first sample data didn't contain this time, it was just a date (as a string) so I was doing...
SUBSTR('01-JAN-1970', 4)
...to reduce the day date to a month date ('JAN-1970') and this was intended to align with the stuff going on in the other table ( to_date() with a format of 'non-yyyy' )
Now we know that there is a time in there too, of course it won't align because...
SUBSTR('01-JAN-1970 12:34', 4)
...produces 'JAN-1970 12:34' and this will then not match to anything from the other table (which will be just 'JAN-1970' without the time), so the left join means that nulls will be output
The solution is to change the SUBSTR call so it cuts 8 characters, starting at position 4:
SUBSTR(start_time, 4, 8)
This will remove the day and the time, leaving just the month-year that we need. You'll need to make the change in two places in the query above..
Apologies for the delay on replying to this. However, that is working perfectly Caius, thanks very much! So to be complete, had to change your above code to:
SUBSTR(START_TIME, 4, 8) as down_month,
and
GROUP BY SUBSTR(START_TIME, 4, 8)

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

SQL query for all the days of a month

i have the following table RENTAL(book_date, copy_id, member_id, title_id, act_ret_date, exp_ret_date). Where book_date shows the day the book was booked. I need to write a query that for every day of the month(so from 1-30 or from 1-29 or from 1-31 depending on month) it shows me the number of books booked.
i currently know how to show the number of books rented in the days that are in the table
select count(book_date), to_char(book_date,'DD')
from rental
group by to_char(book_date,'DD');
my questions are:
How do i show the rest of the days(if let's say for some reason in my database i have no books rented on 20th or 19th or multiple days) and put the number 0 there?
How do i show the number of days only of the current month so(28,29,30,31 all these 4 are possible depending on month or year)... i am lost . This must be done using only SQL query no pl/SQL or other stuff.
The following query would give you all days in the current month, in your case you can replace SYSDATE with your date column and join with this query to know how many for a given month
SELECT DT
FROM(
SELECT TRUNC (last_day(SYSDATE) - ROWNUM) dt
FROM DUAL CONNECT BY ROWNUM < 32
)
where DT >= trunc(sysdate,'mm')
The answer is to create a table like this:
table yearsmonthsdays (year varchar(4), month varchar(2), day varchar(2));
use any language you wish, e.g. iterate in java with Calendar.getInstance().getActualMaximum(Calendar.DAY_OF_MONTH) to get the last day of the month for as many years and months as you like, and fill that table with the year, month and days from 1 to last day of month of your result.
you'd get something like:
insert into yearsmonthsdays ('1995','02','01');
insert into yearsmonthsdays ('1995','02','02');
...
insert into yearsmonthsdays ('1995','02','28'); /* non-leap year */
...
insert into yearsmonthsdays ('1996','02','01');
insert into yearsmonthsdays ('1996','02','02');
...
insert into yearsmonthsdays ('1996','02','28');
insert into yearsmonthsdays ('1996','02','29'); /* leap year */
...
and so on.
Once you have this table done, your work is almost finished. Make an outer left join between your table and this table, joining year, month and day together, and when no lines appear, the count will be zero as you wish. Without using programming, this is your best bet.
In oracle, you can query from dual and use the conncect by level syntax to generate a series of rows - in your case, dates. From there on, it's just a matter of deciding what dates you want to display (in my example I used all the dates from 2014) and joining on your table:
SELECT all_date, COALESCE (cnt, 0)
FROM (SELECT to_date('01/01/2014', 'dd/mm/yyyy') + rownum - 1 AS all_date
FROM dual
CONNECT BY LEVEL <= 365) d
LEFT JOIN (SELECT TRUNC(book_date), COUNT(book_date) AS cnt
FROM rental
GROUP BY book_date) r ON d.all_date = TRUNC(r.book_date)
There's no need to get ROWNUM involved ... you can just use LEVEL in the CONNECT BY:
WITH d1 AS (
SELECT TRUNC(SYSDATE, 'MONTH') - 1 + LEVEL AS book_date
FROM dual
CONNECT BY TRUNC(SYSDATE, 'MONTH') - 1 + LEVEL <= LAST_DAY(SYSDATE)
)
SELECT TRUNC(d1.book_date), COUNT(r.book_date)
FROM d1 LEFT JOIN rental r
ON TRUNC(d1.book_date) = TRUNC(r.book_date)
GROUP BY TRUNC(d1.book_date);
Simply replace SYSDATE with a date in the month you're targeting for results.
All days of the month based on current date
select trunc(sysdate) - (to_number(to_char(sysdate,'DD')) - 1)+level-1 x from dual connect by level <= TO_CHAR(LAST_DAY(sysdate),'DD')
It did works to me:
SELECT DT
FROM (SELECT TRUNC(LAST_DAY(SYSDATE) - (CASE WHEN ROWNUM=1 THEN 0 ELSE ROWNUM-1 END)) DT
FROM DUAL
CONNECT BY ROWNUM <= 32)
WHERE DT >= TRUNC(SYSDATE, 'MM')
In Oracle SQL the query must look like this to not miss the last day of month:
SELECT DT
FROM(
SELECT trunc(add_months(sysdate, 1),'MM')- ROWNUM dt
FROM DUAL CONNECT BY ROWNUM < 32
)
where DT >= trunc(sysdate,'mm')

oracle case or decode? - how to handle no recs

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