is not distinct from in join on clause oracle - sql

I have a query structured with the left outer join like so:
left outer JOIN GA_LOAN GA
ON LOAN.LOAN_TYPE = GA.LOAN_TYP
AND LOAN.DT = GA.GUARANTY_DT
AND LOAN.FFEL_DUP_ID = GA.SEP_LOAN_IND
AND LOAN.SCH_BR_CODE = GA.ORIG_SCHL_CD
AND STU.CURR_SSN = GA.STU_SSN
AND STU.DOB = GA.DOB
and stu.curr_fst = ga.stu_first_nam
--and (plus_bor.curr_ssn is not distinct from ga.plus_brwr_ssn )
When I add the commented out line, I get the following error.
ORA-00908: missing NULL keyword
00908. 00000 - "missing NULL keyword"
*Cause:
*Action:
is not distinct from works fine in this structure in DB2, but Oracle is giving me issues. Any suggestions?
I get no errors if I replaced is not distinct from with a = but that isn't the same logically.
is not distinct from with give a match if both values are null, where as = would not match in this case.

The simplest way to emulate IS [ NOT ] DISTINCT FROM in Oracle is by using DECODE:
-- a IS DISTINCT FROM b
DECODE(a, b, 1, 0) = 0
-- a IS NOT DISTINCT FROM b
DECODE(a, b, 1, 0) = 1
This is what you're getting when you're using jOOQ's SQL dialect translator. A dbfiddle for this:
WITH t (x) AS (
SELECT 1 FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT null FROM dual
)
SELECT
t1.x AS x1,
t2.x AS x2,
DECODE(t1.x, t2.x, 1, 0) AS not_distinct
FROM t t1, t t2
ORDER BY 1, 2
Yields:
X1 | X2 | NOT_DISTINCT
-----+------+-------------
1 | 1 | 1
1 | 2 | 0
1 | null | 0
2 | 1 | 0
2 | 2 | 1
2 | null | 0
null | 1 | 0
null | 2 | 0
null | null | 1

You could emulate IS DISTINCT FROM by using NOT EXISTS combined with INTERSECT:
plus_bor.curr_ssn IS DISTINCT FROM ga.plus_brwr_ssn
<=>
NOT EXISTS (SELECT plus_bor.curr_ssn FROM dual INTERSECT
SELECT ga.plus_brwr_ssn FROM dual);
Example:
WITH cte(a,b) AS (
SELECT 1, NULL FROM dual UNION ALL
SELECT 1,2 FROM dual UNION ALL
SELECT 1,1 FROM dual UNION ALL
SELECT NULL, 1 FROM dual UNION ALL
SELECT NULL, NULL FROM dual
)
SELECT *
FROM cte
WHERE NOT EXISTS (SELECT a FROM dual INTERSECT
SELECT b FROM dual)
Rextester Demo
Output:
A B
------------
1 NULL
1 2
NULL 1
And in your case IS NOT DISTINCT FROM is simply EXISTS:
plus_bor.curr_ssn IS NOT DISTINCT FROM ga.plus_brwr_ssn
<=>
EXISTS (SELECT plus_bor.curr_ssn FROM dual INTERSECT
SELECT ga.plus_brwr_ssn FROM dual);
Example:
WITH cte(a,b) AS (
SELECT 1, NULL FROM dual UNION ALL
SELECT 1,2 FROM dual UNION ALL
SELECT 1,1 FROM dual UNION ALL
SELECT NULL, 1 FROM dual UNION ALL
SELECT NULL, NULL FROM dual
)
SELECT *
FROM cte
WHERE EXISTS (SELECT a FROM dual INTERSECT
SELECT b FROM dual);
Output:
A B
1 1
NULL NULL
Rextester Demo2
ADDENDUM
This approach has one big advantage over COALESCE/NVL approach as proposed in comments.
You don't have to think about default neutral value dependent on datatype.
For example if column is datatype DATE/INT/TEXT then you have to write something like:
coalesce(col1,DATE '1900-01-01') = coalesce(col2,DATE '1900-01-01')
coalesce(col1, 0) = coalesce(col2, 0)
coalesce(col1, ' ') = coalesce(col2, ' ')
There is of course slight chance of collision. For example:
coalesce(col1, 0) = coalesce(col2, 0)
=>
col1 = NULL
col2 = 0
and we have incorrect match!!!

Related

Need to get the mismatcted cells in specific partition

ID
TC_No
Result
1
tc_1
PASS
1
tc_2
PASS
1
tc_3
FAIL
1
tc_4
PASS
1
tc_5
FAIL
2
tc_1
FAIL
2
tc_2
PASS
2
tc_3
FAIL
2
tc_4
FAIL
2
tc_5
FAIL
I'm trying to find all records that have conflicting "Result" on the same "TC_No" and among different "ID" values, filtered by ID IN (1,2).
Here's the expected output:
ID
TC_No
Result
1
tc_1
PASS
1
tc_4
PASS
2
tc_1
FAIL
2
tc_4
FAIL
and my attempted query:
SELECT * From
(SELECT * from Excel As T1
UNION
SELECT * from Excel As T2)
As c
where ID in(1,2) order By TC_NO
find the distinct count of result for each tc_no and then select the records having count greater than 1
Query
select * from your_tbl_name a
where exists(
select 1 from (
select tc_no, count(distinct result) as cnt
from your_tbl_name
where result in ('PASS','FAIL')
group by c_no
) b
where a.tc_no = b.tc_no
and b.cnt > 1
)
You can simply INNER JOIN the table onto itself and add a predicate in the WHERE clause to return only mismatched results.
SQL:
SELECT
a.ID,
a.TC_No,
a.Result
FROM
Excel a
INNER JOIN Excel b ON a.TC_No = b.TC_No
WHERE
a.Result <> b.Result;
Result:
| ID | TC_No | Result |
|----|-------|--------|
| 1 | tc_1 | PASS |
| 1 | tc_4 | PASS |
| 2 | tc_1 | FAIL |
| 2 | tc_4 | FAIL |
SQL Fiddle Demo:
Here
Check when the maximum result is different than the minimum one, in your filtered data, using window functions.
WITH cte AS (
SELECT tab.*,
MAX(Result_) OVER(PARTITION BY TC_No) AS max_result,
MIN(Result_) OVER(PARTITION BY TC_No) AS min_result
FROM tab
WHERE ID IN (1,2)
)
SELECT Id, Tc_No, Result_
FROM cte
WHERE min_result < max_result
Check the demo here.
You could use analytic function COUNT() OVER() to find the rows with different content and then just filter the result in Where clause:
SELECT ID, TC_NO, RESULT
FROM ( Select ID, TC_NO, RESULT,
CASE WHEN Count(DISTINCT RESULT) OVER(Partition By TC_NO) = 2 THEN 'Y' END "IS_DIFF"
From tbl
)
WHERE IS_DIFF = 'Y'
ORDER BY ID
With your sample data:
WITH
tbl (ID, TC_NO, RESULT) AS
(
Select 1, 'tc_1', 'PASS' From Dual Union All
Select 1, 'tc_2', 'PASS' From Dual Union All
Select 1, 'tc_3', 'FAIL' From Dual Union All
Select 1, 'tc_4', 'PASS' From Dual Union All
Select 1, 'tc_5', 'FAIL' From Dual Union All
Select 2, 'tc_1', 'FAIL' From Dual Union All
Select 2, 'tc_2', 'PASS' From Dual Union All
Select 2, 'tc_3', 'FAIL' From Dual Union All
Select 2, 'tc_4', 'FAIL' From Dual Union All
Select 2, 'tc_5', 'FAIL' From Dual
)
... the result is:
ID
TC_NO
RESULT
1
tc_1
PASS
1
tc_4
PASS
2
tc_1
FAIL
2
tc_4
FAIL

Identify only when value matches

I need to return only rows that have the match e.g Value = A, but I only need the rows that have A and with no other values.
T1:
ID Value
1 A
1 B
1 C
2 A
3 A
3 B
4 A
5 B
5 D
5 E
5 F
Desired Output:
2
4
how can I achieve this?
when I try the following, 1&3 are also returned:
select ID from T1 where Value ='A'
With NOT EXISTS:
select t.id
from tablename t
where t.value = 'A'
and not exists (
select 1 from tablename
where id = t.id and value <> 'A'
)
From the sample data you posted there is no need to use:
select distinct t.id
but if you get duplicates then use it.
Another way if there are no null values:
select id
from tablename
group by id
having sum(case when value <> 'A' then 1 else 0 end) = 0
Or if you want the rows where the id has only 1 value = 'A':
select id
from tablename
group by id
having count(*) = 1 and max(value) = 'A'
I think the simplest way is aggregation with having:
select id
from tablename
group by id
having min(value) = max(value) and
min(value) = 'A';
Note that this ignores NULL values so it could return ids with both NULL and A. If you want to avoid that:
select id
from tablename
group by id
having count(value) = count(*) and
min(value) = max(value) and
min(value) = 'A';
Oracle Setup:
CREATE TABLE test_data ( ID, Value ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 1, 'B' FROM DUAL UNION ALL
SELECT 1, 'C' FROM DUAL UNION ALL
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 3, 'A' FROM DUAL UNION ALL
SELECT 3, 'B' FROM DUAL UNION ALL
SELECT 4, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 5, 'D' FROM DUAL UNION ALL
SELECT 5, 'E' FROM DUAL UNION ALL
SELECT 5, 'F' FROM DUAL
Query:
SELECT ID
FROM test_data
GROUP BY ID
HAVING COUNT( CASE Value WHEN 'A' THEN 1 END ) = 1
AND COUNT( CASE Value WHEN 'A' THEN NULL ELSE 1 END ) = 0
Output:
| ID |
| -: |
| 2 |
| 4 |
db<>fiddle here

SQL query to return data only if ALL necessary columns are present and not NULL

ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
2 Purchase null
2 Return 5
2 Exchange 1
3 Purchase 34
3 Return 4
3 Exchange 2
4 Purchase 12
4 Exchange 2
Above is sample data. What I want to return is:
ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
3 Purchase 34
3 Return 4
3 Exchange 2
So if a field is null in total or the values of Purchase, Return and Exchange are not all present for that ID, ignore that ID completely. How can I go about doing this?
You can use exists. I think you intend:
select t.*
from t
where exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Purchase' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Exchange' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Return' and t2.total is not null
);
There are ways to "simplify" this:
select t.*
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.total is not null
);
I would write this as a join, without subqueries:
SELECT pur.id, pur.total AS Purchase, exc.total AS Exchange, ret.total AS Return
FROM MyTable as pur
INNER JOIN MyTable AS exc ON exc.id=pur.id AND exc.type='Exchange'
INNER JOIN MyTable AS ret ON ret.id=pur.id AND ret.type='Return'
WHERE pur.type='Purchase'
The inner join means that if any of the three rows with different values are not found for a given id, then no row is included in the result.
Analytic functions are a good way to solve this kind of problems. The base table is read just once, and no joins (explicit or implicit, as in EXISTS conditions or correlated subqueries) are needed.
In the solution below, we count distinct values of 'Purchase', 'Exchange' and 'Return' for each id while ignoring other values (assuming that is indeed the requirement), and separately count total nulls in the total column for each id. Then it becomes a trivial matter to select just the "desired" rows in an outer query.
with
test_data ( id, type, total ) as (
select 1, 'Purchase', 12 from dual union all
select 1, 'Return' , 2 from dual union all
select 1, 'Exchange', 5 from dual union all
select 2, 'Purchase', null from dual union all
select 2, 'Return' , 5 from dual union all
select 2, 'Exchange', 1 from dual union all
select 3, 'Purchase', 34 from dual union all
select 3, 'Return' , 4 from dual union all
select 3, 'Exchange', 2 from dual union all
select 4, 'Purchase', 12 from dual union all
select 4, 'Exchange', 2 from dual
)
-- end of test data; actual solution (SQL query) begins below this line
select id, type, total
from ( select id, type, total,
count( distinct case when type in ('Purchase', 'Return', 'Exchange')
then type end
) over (partition by id) as ct_type,
count( case when total is null then 1 end
) over (partition by id) as ct_total
from test_data
)
where ct_type = 3 and ct_total = 0
;
Output:
ID TYPE TOTAL
-- -------- -----
1 Exchange 5
1 Purchase 12
1 Return 2
3 Exchange 2
3 Purchase 34
3 Return 4
This also should work fine even if new values are added to type column
select * from t where
ID not in(select ID from t where
t.total is null or t.[Type] is null)

Cannot figure out logic to update a table

I have below table.
TABLE: ABCD
B column have value 1 whenever there is a change in A column. Now I have to update the table like below. How can I do that?
You can do this using a correlated subquery:
update t
set b = (select sum(t2.b) from t t2 where t2.A <= t.A);
This is standard SQL and should work in either Oracle or Teradata.
Lets have a slightly more complicated example (where the changes in B are not correlated to the changes in A):
Oracle Setup:
CREATE TABLE ABCD( A, B ) AS
SELECT 1, 0 FROM DUAL UNION ALL
SELECT 1, 0 FROM DUAL UNION ALL
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 2, 0 FROM DUAL UNION ALL
SELECT 3, 0 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 3, 0 FROM DUAL;
Update:
UPDATE ABCD t1
SET b = (
SELECT sm
FROM (
SELECT ROWID AS id,
SUM( b ) OVER ( ORDER BY a, ROWNUM ) AS sm
FROM ABCD
) t2
WHERE t1.ROWID = t2.ID
);
Output:
SELECT * FROM ABCD;
A B
- -
1 0
1 0
1 1
2 2
2 2
3 2
3 3
3 3
(Note: This is an Oracle solution; I have no idea if it will or won't work in Teradata.)

Oracle sql group sum

I have table With ID,Sub_ID and value coloumns
ID SUB_ID Value
100 1 100
100 2 150
101 1 100
101 2 150
101 3 200
102 1 100
SUB ID can vary from 1..maxvalue( In this example it is 3). I need Sum of values for each Sub_ID. If SUB_ID is less than MAXVALUE for a particlaur ID then it should take MAX(SUB_ID) of each ID As shown below ( In this example for ID=100 for SUB_ID 3 it should take 150 i.e 2<3 so value=150))
SUB_ID SUM(values) Remarks
1 300 (100+100+100)
2 400 (150+150+100)
3 450 (150+200+100)
This can be easily done in PL/SQL . Can we use SQL for the same using Model Clause or any other options
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableA ( ID, SUB_ID, Value ) AS
SELECT 100, 1, 100 FROM DUAL
UNION ALL SELECT 100, 2, 150 FROM DUAL
UNION ALL SELECT 101, 1, 100 FROM DUAL
UNION ALL SELECT 101, 2, 150 FROM DUAL
UNION ALL SELECT 101, 3, 200 FROM DUAL
UNION ALL SELECT 102, 1, 100 FROM DUAL
Query 1:
WITH sub_ids AS (
SELECT LEVEL AS sub_id
FROM DUAL
CONNECT BY LEVEL <= ( SELECT MAX( SUB_ID ) FROM TableA )
),
max_values AS (
SELECT ID,
MAX( VALUE ) AS max_value
FROM TableA
GROUP BY ID
)
SELECT s.SUB_ID,
SUM( COALESCE( a.VALUE, m.max_value ) ) AS total_value
FROM sub_ids s
CROSS JOIN
max_values m
LEFT OUTER JOIN
TableA a
ON ( s.SUB_ID = a.SUB_ID AND m.ID = a.ID )
GROUP BY
s.SUB_ID
Results:
| SUB_ID | TOTAL_VALUE |
|--------|-------------|
| 1 | 300 |
| 2 | 400 |
| 3 | 450 |
Try this
SELECT SUB_ID,SUM(values),
(SELECT DISTINCT SUBSTRING(
(
SELECT '+'+ CAST(values AS VARCHAR)
FROM table_Name AS T2
WHERE T2.SUB_ID = d.SUB_ID
FOR XML PATH ('')
),2,100000)[values]) as values
FROm table_Name d
GROUP BY SUB_ID
How about something like this:
select max_vals.sub_id, sum(nvl(table_vals.value,max_vals.max_value)) as sum_values
from (
select all_subs.sub_id, t1.id, max(t1.value) as max_value
from your_table t1
cross join (select sub_id from your_table) all_subs
group by all_subs.sub_id, t1.id
) max_vals
left outer join your_table table_vals
on max_vals.id = table_vals.id
and max_vals.sub_id = table_vals.sub_id
group by max_vals.sub_id;
The inner query gets you a list of all sub_id/id combinations and their fall-back values. The out query uses an nvl to use the table value if it exists and the fall-back value if it doesn't.