I have a table called fact_trip which has a column as fare_final and I want to find the difference between values.
SQL> desc fact_trip
Name Null? Type
----------------------------------------- -------- ----------------------------
TRIP_UUID NOT NULL VARCHAR2(20)
DATESTR DATE
PRODUCT_TYPE_NAME VARCHAR2(20)
CITY_ID NUMBER
DRIVER_UUID VARCHAR2(50)
IS_COMPLETED VARCHAR2(10)
ETA NUMBER
ATA NUMBER
UFP_FARE NUMBER(4,2)
FARE_FINAL NUMBER(4,2)
So when I do this I'm getting NULL as output:
SQL> select sum(fare_final) from fact_trip where to_char(datestr, 'W')=1 - (select sum(fare_final) from fact_trip where to_char(datestr, 'W')=2);
SUM(FARE_FINAL)
---------------
I even tried doing those select queries individually like this:
SQL> select sum(fare_final) from fact_trip where to_char(datestr, 'W')=1;
SUM(FARE_FINAL)
---------------
1821.6
SQL> select sum(fare_final) from fact_trip where to_char(datestr, 'W')=2;
SUM(FARE_FINAL)
---------------
67
Which is of course fetching result. But then when I run those queries for what I want a difference of them, it's showing NULL. Like I want difference of them (1821.6 - 67).
Can anybody tell what's wrong in it?
Thank You!
SQL is not a worksheet, where you may evaluate any expression. You cannot use an expression as a SQL command: you have to eitherselect an expression from something, or calculate it in PL/SQL block.
You want to calculate the difference, so you assumed your code is: subquery - subquery. But because of described above this is not syntactically correct command, and parser tries to find a parse tree which is syntactically correct. And it actually finds one:
select sum(fare_final)
from fact_trip
where to_char(datestr, 'W') = (
1 - (
select sum(fare_final)
from fact_trip
where to_char(datestr, 'W') = 2
)
)
Of course, such week doesn't exist and you get null as a result.
To turn your code into a correct command, the simplest way is to select the expression from a dual table with appropriate brackets to specify calculation precedence:
select
(select sum(fare_final) from fact_trip where to_char(datestr, 'W')='1')
- (select sum(fare_final) from fact_trip where to_char(datestr, 'W')='2') as res
from dual
But more performant and set-based approach would be:
select
sum(
case to_char(datestr, 'W')
when '1' then 1
when '2' then -1
end * fare_final
) as res
from fact_trip
where to_char(datestr, 'W') in ('1', '2')
Below is the sample code with results of all that was described above:
select * from t
ID | VAL
-: | --:
1 | 3
2 | 6
3 | 9
select sum(val)
from t
where id = 1 - (
select sum(val)
from t
where id = 2
)
| SUM(VAL) |
| -------: |
| null |
select
(
(select sum(val)
from t
where id = 1) - (
select sum(val)
from t
where id = 2
)
) as q
from dual
| Q |
| -: |
| -3 |
select
sum(
case id
when 1 then 1
when 2 then -1
end * val
) as res
from t
where id in (1, 2)
| RES |
| --: |
| -3 |
db<>fiddle here
Should be
SELECT SUM (
CASE
WHEN TO_CHAR (datestr, 'W') = 1 THEN fare_final
WHEN TO_CHAR (datestr, 'W') = 2 THEN -fare_final
END)
FROM fact_trip;
I don't have your table so I'll illustrate it on Scott's EMP, computing the total for analysts and clerks:
SQL> SELECT job, sal FROM emp ORDER BY job;
JOB SAL
--------- ----------
ANALYST 3000 --> total for analysts is 6000
ANALYST 3000
CLERK 1300 --> total for clerks is 4150
CLERK 950
CLERK 800 --> difference: 6000 - 4150 = 1850
CLERK 1100
MANAGER 2850
MANAGER 2975
MANAGER 2450
PRESIDENT 5000
SALESMAN 1500
SALESMAN 1250
SALESMAN 1250
SALESMAN 1600
14 rows selected.
SQL> SELECT SUM (
2 CASE WHEN job = 'ANALYST' THEN sal
3 WHEN job = 'CLERK' THEN -sal
4 END) total
5 FROM emp;
TOTAL
----------
1850
SQL>
Related
I want to delete the dup lines using PLSQL. The sample of the table is below
Policy #
Price
Dealno for Loan #
Price of Loan
PersonID
123
10
Loan123
1,000
abc
123
10
Loan123
3,000
abc
456
10
Loan456
500
xyz
456
10
Loan456
500
null
As you can see, in the case of Policy #123, I try to get the line with the highest amount of Price of Loan. Which mean the Price of Loan for 3,000.
For Policy #456, I want to get the one without null value.
Is there a way for me to achieve that in PLSQL.
Thank you
This query identifies if a row is OK (rn = 1) or if is is a duplicated copy (rn > 1) based on your definition
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
;
POLICY# PRICE LOAN# PRICE_LOAN PER RN
---------- ---------- -------- ---------- --- ----------
123 10 loan123 3000 abc 1
123 10 loan123 1000 abc 2
456 10 loan4563 500 xyz 1
456 10 loan4563 500 2
Note that you use row_number where you partition by on the unique key and order by so that you get first the row that should be taken.
So to get the duplicates only you use this query
with rn as (
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
)
select * from rn where rn > 1;
POLICY# PRICE LOAN# PRICE_LOAN PER RN
---------- ---------- -------- ---------- --- ----------
123 10 loan123 1000 abc 2
456 10 loan4563 500 2
Based on this you write the DELETE statement (enclose in BEGIN ... END if you insist in PL/SQL)
delete from tab where rowid in
(
with rn as (
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
)
select rowid from rn where rn > 1
);
You may check if the delete worked fine ....
select * from tab;
POLICY# PRICE LOAN# PRICE_LOAN PER
---------- ---------- -------- ---------- ---
123 10 loan123 3000 abc
456 10 loan4563 500 xyz
... and commit
Currently, I have a table MY_TABLE like below:
ID ACCT_TYPE CREDIT_AMT DEBIT_AMT
-- --------- ---------- ---------
1 CDT_01 4 (null)
1 DBT_01 (null) 6
One ID can have multiple ACCT_TYPE like above, and each type has its own amount.
I want to just select the row which has ACCT_TYPE like 'CDT_%' but also the total_amount column which is the total of credit_amt and debit_amt column for the same ID.
My expected output like below:
ID ACCT_TYPE TOTAL_AMT
-- --------- ---------
1 CDT_01 10
I tried with this select statement below but it's no use, I think it's because of different ACCT_TYPE:
Select ID, ACCT_TYPE, SUM(NVL(CREDIT_AMT, 0) + NVL(DEBIT_AMT, 0)) TOTAL_AMT
FROM MY_TABLE WHERE ACCT_TYPE LIKE 'CDT_%' GROUP BY ID, ACCT_TYPE;
Here is the output of the select statement above:
ID ACCT_TYPE TOTAL_AMT
-- --------- ---------
1 CDT_01 4
I just begin to learn some query so I don't know is it really possible to get my expected output.
One way to do it is like below:
with inputs (ID, ACCT_TYPE, CREDIT_AMT, DEBIT_AMT) as
(
select 1, 'CDT_01', 4, null from dual union all
select 1, 'DBT_01', null, 6 from dual
),
prep as
(
select t.*, sum(nvl(credit_amt,0)) over (partition by id) + sum(nvl(debit_amt,0)) over (partition by id) as sum_per_id
from inputs t
)
select id, acct_type, sum_per_id
from prep
where acct_type like 'CDT_%';
Output:
A correlated subquery might be one option; sample data (thank you, #Ranagal) in lines #1 - 5; query that does the job begins at line #6.
SQL> with inputs (ID, ACCT_TYPE, CREDIT_AMT, DEBIT_AMT) as
2 (
3 select 1, 'CDT_01', 4, null from dual union all
4 select 1, 'DBT_01', null, 6 from dual
5 )
6 select a.id,
7 a.acct_type,
8 (select sum(nvl(b.credit_amt, 0)) +
9 sum(nvl(b.debit_amt , 0))
10 from inputs b
11 where b.id = a.id
12 ) total_amt
13 from inputs a
14 where acct_type like 'CDT%';
ID ACCT_T TOTAL_AMT
---------- ------ ----------
1 CDT_01 10
SQL>
I need to modify my script using rowcount to check if the data in table or not?. Here, i write the query to select a data for last 5 days from current system date. But sometimes there is no data in table for 5 days. So i need to fetch for 10 day or more.
Query:
Select ep.ENTERPRISE_NAME||'|'||s.id||'|'||s.SUBMISSION_DATE||'|'||E.VALUE
from JOB_SUMMARY_EXT e, ob_summary s, enterprise ep
where e.id = s.id and e.name_res_key = 'Model'
and s.job_id in (select id from job_summary where
trunc(start_date) > trunc(sysdate) -10 and service_name ='Model2' )
I don't know how to modify my Query using rowcount. If rowcount is 0 then i want select data for 10 days.Otherwise it should to fetch for 5 days automatically. I want this to be done as single query.
It looks that you want to select the last 5 "days" from that table. So, why would you anchor to SYSDATE if there aren't rows for each of those days? I'd suggest another approach: literally, select last 5 days. Here's how.
As I don't have your tables, I'm using Scott's EMP table which contains information about employees. It is an ancient one so HIREDATE column is set to 1980s, but never mind that. Sorting employees by HIREDATE in descending order shows:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select ename, hiredate from emp order by hiredate desc;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983 1.
SCOTT 09.12.1982 2.
MILLER 23.01.1982 3.
FORD 03.12.1981 4.
JAMES 03.12.1981 4.
KING 17.11.1981 5. --> I want to fetch rows up to KING
MARTIN 28.09.1981
TURNER 08.09.1981
CLARK 09.06.1981
BLAKE 01.05.1981
JONES 02.04.1981
WARD 22.02.1981
ALLEN 20.02.1981
SMITH 17.12.1980
14 rows selected.
SQL>
As you can see, the 4th date is shared by two employees so I want to include them both. DENSE_RANK analytic function helps:
SQL> with last5 as
2 (select ename,
3 job,
4 sal,
5 hiredate,
6 dense_rank() over (order by hiredate desc) rnk
7 from emp
8 )
9 select ename, job, sal, hiredate
10 from last5
11 where rnk <= 5;
ENAME JOB SAL HIREDATE
---------- --------- ---------- ----------
ADAMS CLERK 1100 12.01.1983
SCOTT ANALYST 3000 09.12.1982
MILLER CLERK 1300 23.01.1982
JAMES CLERK 950 03.12.1981
FORD ANALYST 3000 03.12.1981
KING PRESIDENT 5000 17.11.1981
6 rows selected.
SQL>
What does it do? The LAST5 CTE sorts employees (as above), DENSE_RANK ranks them; finally, the last SELECT (which begins at line #9) fetches desired rows.
In your case, that might look like this:
with last5 as
(select id,
dense_rank() over (order by start_date desc) rnk
from job_summary
where service_name = 'Model2'
)
select ep.enterprise_name,
s.id,
s.submission_date,
e.value
from job_summary_ext e
join ob_summary s on e.id = s.id
join last5 t on t.id = s.id
join enterprise ep on <you're missing join condition for this table>
where e.name_res_key = 'Model';
Note that you're missing join condition for the ENTERPRISE table; if that's really so, no problem - you'd use cross join for that table, but I somehow doubt that you want that.
Finally, as you use SQL*Plus, perhaps you don't need to concatenate all columns and separate them by the pipe | sign - set it as a column separator, e.g.
SQL> set colsep '|'
SQL>
SQL> select deptno, dname, loc from dept;
DEPTNO|DNAME |LOC
----------|--------------|-------------
10|ACCOUNTING |NEW YORK
20|RESEARCH |DALLAS
30|SALES |CHICAGO
40|OPERATIONS |BOSTON
SQL>
If you want to
return 10 last days if select count(*) returns 0, or
return 5 last days if select count(*) returns a positive number
then something like this might help (again based on Scott's EMP table):
with
tcnt as
-- count number of rows; use your own requirement, I'm checking
-- whether someone got hired today. In Scott's EMP table, nobody was
-- so CNT = 0
(select count(*) cnt
from emp
where hiredate >= trunc(sysdate)
)
select e.ename, e.job, e.sal, e.hiredate
from emp e cross join tcnt c
where e.hiredate >= case when c.cnt = 0 then trunc(sysdate) - 10
else trunc(sysdate) - 5
end;
Apply it to your tables; I don't know which of those 3 tables' count you want to check.
Tried to add in comments but it was too long for comments and Not clear on count based on but here is case in where clause substitute your count statement with nvl function
SELECT ep.ENTERPRISE_NAME||'|'||s.id||'|'||s.SUBMISSION_DATE||'|'||E.VALUE
FROM JOB_SUMMARY_EXT e,
ob_summary s,
enterprise ep
WHERE e.id = s.id
AND e.name_res_key = 'Model'
AND s.job_id IN
(SELECT id
FROM job_summary
WHERE service='Model'
AND trunc(start_date) >
CASE WHEN
(WRITE your SELECT COUNT criteria WITH NVL FUNCTION)<=0 THEN
trunc(sysdate) -10
ELSE trunc(sysdate)-5
END )
I have a problem i hope you can help to resolve.
I have a table named PRODUCT:
Product_ID NOT NULL NUMBER(10)
TARGET_PRODUCT VARCHAR2(10)
SOURCE_PRODUCT VARCHAR2(10)
So each target_product made out of source_product(except the first one - first one just has target_product and source_product is null)
I need to find the fist source_product for given target_product.
I need to go 'back' in a loop until source product is null.
Is there a solution for this scenario?
Thanks in advance
It is not a loop you need, but hierarchical query. Have a look at the following example based on Scott's EMP table.
This is its contents; employees are displayed hierarchically, showing who's whose boss:
SQL> select level,
2 lpad(' ', level * 2, ' ') || e.ename name
3 from emp e
4 start with e.mgr is null
5 connect by prior e.empno= e.mgr;
LEVEL NAME
---------- ---------------
1 KING
2 JONES
3 SCOTT
4 ADAMS
3 FORD
4 SMITH
2 BLAKE
3 ALLEN
3 WARD
3 MARTIN
3 TURNER
3 JAMES
2 CLARK
3 MILLER
14 rows selected.
SQL>
As you want to start from the middle of the table (for example, starting from SMITH), you'd "reverse" it:
SQL> select level lvl,
2 lpad(' ', level * 2, ' ') || e.ename name
3 from emp e
4 start with e.ename = 'SMITH'
5 connect by prior e.mgr= e.empno;
LVL NAME
---------- ---------------
1 SMITH
2 FORD
3 JONES
4 KING
SQL>
Finally, using that query as a CTE (or a subquery, if your Forms version doesn't support CTEs), fetch the one whose name has the highest level:
SQL> with temp as
2 (select level lvl,
3 lpad(' ', level * 2, ' ') || e.ename name
4 from emp e
5 start with e.ename = 'SMITH'
6 connect by prior e.mgr= e.empno
7 )
8 select trim(t.name) name
9 from temp t
10 where t.lvl = (select max(t1.lvl) from temp t1);
NAME
---------------
KING
SQL>
Or, even better, using connect_by_isleaf:
SQL> select e.ename name
2 from emp e
3 where connect_by_isleaf = 1
4 start with e.ename = 'SMITH'
5 connect by prior e.mgr= e.empno;
NAME
---------------
KING
SQL>
You may try this, it will return the base target_product for a given target_product:
select * from (
select PRODUCT_ID, TARGET_PRODUCT, SOURCE_PRODUCT
from PRODUCT
start with target_product = '<your target product>'
connect by prior SOURCE_PRODUCT = TARGET_PRODUCT
)
where SOURCE_PRODUCT is null;
Let's say I have a table with data ranges
create table ranges (id number, date_from date, date_to date);
insert into ranges values (1, to_date('01.01.2017', 'dd.mm.rrrr'), to_date('03.01.2017', 'dd.mm.rrrr'));
insert into ranges values (2, to_date('05.02.2017', 'dd.mm.rrrr'), to_date('08.02.2017', 'dd.mm.rrrr'));
and my output should by one row for every date in these ranges
id | the_date
----------------
1 | 01.01.2017
1 | 02.01.2017
1 | 03.01.2017
2 | 05.02.2017
2 | 06.02.2017
2 | 07.02.2017
2 | 08.02.2017
But connect by gives me ORA-01436 Connect by Loop
SELECT connect_by_root(id), Trunc(date_from, 'dd') + LEVEL - 1 AS the_date
FROM ranges
CONNECT BY PRIOR id = id AND Trunc(date_from, 'dd') + LEVEL - 1 <= Trunc(date_to, 'dd')
ORDER BY id, the_date
What's wrong?
You can add a call to a non-deterministic function, e.g.
AND PRIOR dbms_random.value IS NOT NULL
So it becomes:
SELECT connect_by_root(id), Trunc(date_from, 'dd') + LEVEL - 1 AS the_date
FROM ranges
CONNECT BY PRIOR id = id
AND PRIOR dbms_random.value IS NOT NULL
AND Trunc(date_from, 'dd') + LEVEL - 1 <= Trunc(date_to, 'dd')
ORDER BY id, the_date;
CONNECT_BY_ROOT(ID) THE_DATE
------------------- ---------
1 01-JAN-17
1 02-JAN-17
1 03-JAN-17
2 05-FEB-17
2 06-FEB-17
2 07-FEB-17
2 08-FEB-17
7 rows selected.
There's an explanation of why it is necessary in this Oracle Community post; that uses sys_guid() instead of dbms_random.value, but the principle is the same.
If you're on 11gR2 or higher you could use recursive subquery factoring instead:
WITH rcte (root_id, the_date, date_to) AS (
SELECT id, date_from, date_to
FROM ranges
UNION ALL
SELECT root_id, the_date + 1, date_to
FROM rcte
WHERE the_date < date_to
)
SELECT root_id, the_date
FROM rcte
ORDER BY root_id, the_date;
ROOT_ID THE_DATE
---------- ---------
1 01-JAN-17
1 02-JAN-17
1 03-JAN-17
2 05-FEB-17
2 06-FEB-17
2 07-FEB-17
2 08-FEB-17
7 rows selected.