Showing NULL on purpose when a NULL joined value is present in SQL - sql

I have a table with some input values and a table with lookup values like below:
select input.value, coalesce(mapping.value, input.value) result from (
select 'a' union all select 'c'
) input (value) left join (
select 'a', 'z' union all select 'b', 'y'
) mapping (lookupkey, value) on input.value = mapping.lookupkey
which gives:
value | result
--------------
a | z
c | c
i.e. I want to show the original values as well as the mapped value but if there is none then show the original value as the result.
The above works well so far with coalesce to determine if there is a mapped value or not. But now if I allow NULL as a valid mapped value, I want to see NULL as the result and not the original value, since it does find the mapped value, only that the mapped value is NULL. The same code above failed to achieve this:
select input.value, coalesce(mapping.value, input.value) result from (
select 'a' union all select 'c'
) input (value) left join (
select 'a', 'z' union all select 'b', 'y' union all select 'c', null
) mapping (lookupkey, value) on input.value = mapping.lookupkey
which gives the same output as above, but what I want is:
value | result
--------------
a | z
c | NULL
Is there an alternative to coalesce that can achieve what I want?

I think you just want a case expression e.g.
select input.[value]
, coalesce(mapping.[value], input.[value]) result
, case when mapping.lookupkey is not null then mapping.[value] else input.[value] end new_result
from (
select 'a'
union all
select 'c'
) input ([value])
left join (
select 'a', 'z'
union all
select 'b', 'y'
union all
select 'c', null
) mapping (lookupkey, [value]) on input.[value] = mapping.lookupkey
Returns:
value result new_result
a z z
c c NULL

Related

SQL - create new col based on value of other column group by third column

I have this table
Id
item
type
A
itemA1
X
A
itemA2
X
B
itemA1
X
B
itemA2
X
B
itemA3
Y
And i would like to create new indicator which contains the information about if the Id contains only item of type X or only tpye Y or both like this :
Id
Indicator
A
Only X
B
Both
EDIT: It's possible to have more than 2 kind of types
Thanks in advance for your help
Consider below generic approach
select id,
if(count(distinct type)=1,'Only ','') || string_agg(distinct type) indicator
from your_table
group by id
This will cover if both item are 'Y' as well:
with table_with_sample_data as (
select 'A' as Id ,'itemA1' as item, 'X' as type union all
select 'A', 'itemA2', 'X' union all
select 'B', 'itemA1', 'X' union all
select 'B', 'itemA2', 'X' union all
select 'B', 'itemA3', 'Y' union all
select 'C', 'itemA1', 'Y' union all
select 'C', 'itemA2', 'Y'
)
select id,
if(count(distinct type)=1,'Only '|| max(type), 'Both') indicator from table_with_sample_data
group by id

How to check in SQL if multi columnar set is in the table (without string concatenation)

Let's assume I've 3 columns in a table with values like this:
table_1:
A | B | C
-----------------------
'xx' | '' | 'y'
'x' | 'y' | 'x'
'x' | 'x' | 'y'
'x' | 'yy' | ''
'x' | '' | 'yy'
'x' | 'y' | 'y'
I've a result set (result of an SQL SELECT statement) which I want to identify in the above table if it exists there:
[
('x', 'x', 'y')
('x', 'y', 'y')
]
This result set would match for 5 (of 6) rows in instead of the 2 from the table above if I've compared the results of simple string concatenation, e.g. I would simply compare the results of this: SELECT concat(A, B, C) FROM table_1
I could solve this problem with comparing the results of more complex string concatenation functions like this: SELECT concat('A=', A, '_', 'B=', B, '_', 'C=', C )
BUT:
I don't want to use any hardcoded special separator in a string concatenation like _ or =
because any character might be in the data
e.g.: somewhere in column B there might be this value: xx_C=yy
it's not a clean solution
I don't want to use string concatenation at all, because it's an ugly solution
it makes the "distance" between the attributes disappear
not general enough
maybe I've columns with different datatypes I don't want to convert to a STRING based column
Question:
Is it possible to solve somehow this problem without using string concatenation?
Is there a simple solution for this multi column value checking problem?
I want to solve this in BiqQuery, but I'm interested in a general solution for every relational databse/datawarehouse.
Thank you!
CREATE TABLE test.table_1 (
A STRING,
B STRING,
C STRING
) AS
SELECT * FROM (
SELECT 'xx', '', 'y'
UNION ALL
SELECT 'x', 'y', 'x'
UNION ALL
SELECT 'x', 'x', 'y'
UNION ALL
SELECT 'x', 'yy', ''
UNION ALL
SELECT 'x', '', 'yy'
UNION ALL
SELECT 'x', 'y', 'y'
)
SELECT A, B, C
FROM test.table_1
WHERE (A, B, C) IN ( -> I need this functionality
SELECT 'x', 'x', 'y'
UNION ALL
SELECT 'x', 'y', 'y'
);
Below is the most generic way I can think of (BigQuery Standard SQL):
#standardSQL
SELECT *
FROM `project.test.table1` t
WHERE t IN (
SELECT t
FROM `project.test.table2` t
)
You can test, play with above using sample data from your question as in below example
#standardSQL
WITH `project.test.table1` AS (
SELECT 'xx' a, '' b, 'y' c UNION ALL
SELECT 'x', 'y', 'x' UNION ALL
SELECT 'x', 'x', 'y' UNION ALL
SELECT 'x', 'yy', '' UNION ALL
SELECT 'x', '', 'yy' UNION ALL
SELECT 'x', 'y', 'y'
), `project.test.table2` AS (
SELECT 'x' a, 'x' b, 'y' c UNION ALL
SELECT 'x', 'y', 'y'
)
SELECT *
FROM `project.test.table1` t
WHERE t IN (
SELECT t
FROM `project.test.table2` t
)
with output
Row a b c
1 x x y
2 x y y
Use join:
SELECT t1.*
FROM test.table_1 t1 JOIN
(SELECT 'x' as a, 'x' as b, 'y' as c
UNION ALL
SELECT 'x', 'y', 'y'
) t2
USING (a, b, c);

Comparing 2 lists in Oracle

I have 2 lists which I need to compare. I need to find if at least one element from List A is found in List B. I know IN doesn't work with 2 lists. What are my other options?
Basically something like this :
SELECT
CASE WHEN ('A','B','C') IN ('A','Z','H') THEN 1 ELSE 0 END "FOUND"
FROM DUAL
Would appreciate any help!
You are probably looking for something like this. The WITH clause is there just to simulate your "lists" (whatever you mean by that); they are not really part of the solution. The query you need is just the last three lines (plus the semicolon at the end).
with
first_list (str) as (
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
),
second_list(str) as (
select 'A' from dual union all
select 'Z' from dual union all
select 'H' from dual
)
select case when exists (select * from first_list f join second_list s
on f.str = s.str) then 1 else 0 end as found
from dual
;
FOUND
----------
1
In Oracle you can do:
select
count(*) as total_matches
from table(sys.ODCIVarchar2List('A', 'B', 'C')) x,
table(sys.ODCIVarchar2List('A', 'Z', 'H')) y
where x.column_value = y.column_value;
You need to repeat the conditions:
SELECT (CASE WHEN 'A' IN ('A', 'Z', 'H') OR
'B' IN ('A', 'Z', 'H') OR
'C' IN ('A', 'Z', 'H')
THEN 1 ELSE 0
END) as "FOUND"
FROM DUAL
If you are working with collection of String you can try Multiset Operators.
create type coll_of_varchar2 is table of varchar2(4000);
and:
-- check if exits
select * from dual where cardinality (coll_of_varchar2('A','B','C') multiset intersect coll_of_varchar2('A','Z','H')) > 0;
-- list of maching elments
select * from table(coll_of_varchar2('A','B','C') multiset intersect coll_of_varchar2('A','Z','H'));
Additionally:
-- union of elemtns
select * from table(coll_of_varchar2('A','B','C') multiset union distinct coll_of_varchar2('A','Z','H'));
select * from table(coll_of_varchar2('A','B','C') multiset union all coll_of_varchar2('A','Z','H'));
-- eelemnt from col1 not in col2
select * from table(coll_of_varchar2('A','A','B','C') multiset except all coll_of_varchar2('A','Z','H'));
select * from table(coll_of_varchar2('A','A','B','C') multiset except distinct coll_of_varchar2('A','Z','H'));
-- check if col1 is subset col2
select * from dual where coll_of_varchar2('B','A') submultiset coll_of_varchar2('A','Z','H','B');
I am trying to do something very similar but the first list is another field on the same query created with listagg and containing integer numbers like:
LISTAGG(my_first_list,', ') WITHIN GROUP(
ORDER BY
my_id
) my_first_list
and return this with all the other fields that I am already returning
SELECT
CASE WHEN my_first_list IN ('1,2,3') THEN 1 ELSE 0 END "FOUND"
FROM DUAL

Select a table based on input condition

I am using oracle 10g and i need to write a query where in the table that is to be considered for producing the output is based on the user input.
i have written in the following manner, but getting an error.
UNDEFINE CDR
SELECT F.EMPLOYEE_ID FROM
( SELECT DECODE(&&CDR,25,'TABLE 1' ,22,'TABLE 2' ,19,'TABLE 3' ,16,'TABLE 4') FROM DUAL ) F
WHERE F.FLAG='G';
The closest that you can come without dynamic SQL is:
select EMPLOYEE_ID
from table1
where flag = 'G' and &&CDR = 25
union all
select EMPLOYEE_ID
from table2
where flag = 'G' and &&CDR = 19
union all
select EMPLOYEE_ID
from table4
where flag = 'G' and &&CDR = 16
union all
select EMPLOYEE_ID
from table1
where flag = 'G' and &&CDR not in (25, 19, 16)

Stuck on this union / except

Trying to find the best way to proceed with this, for some reason it is really tripping me up.
I have data like this:
transaction_id(pk) decision_id(pk) accepted_ind
A 1 NULL
A 2 <blank>
A 4 Y
B 1 <blank>
B 2 Y
C 1 Y
D 1 N
D 2 O
D 3 Y
Each transaction is guaranteed to have decision 1
There can be multiple decision possibilities (what-if's) type of scenarios
Accepted can have multiple values or be blank or NULL but only one can be accepted_ind = Y
I am trying to write a query to:
Return one row for each transaction_id
Return the decision_id where the accepted_ind = Y or if the transaction has no rows accepted_ind = Y, then return the row with decision_id = 1 (regardless of value in the accepted_ind)
I have tried:
1. Using logical "or" to pull the records, kept getting duplicates.
2. Using a union and except but can not quite get the logic down correctly.
Any assistance is appreciated. I am not sure why this is tripping me up so much!
Adam
Try this. Basically the WHERE clause says:
Where Accepted = 'Y'
OR
There is no accepted row for this transaction and the decision_id = 1
SELECT Transaction_id, Decision_ID, Accepted_id
FROM MyTable t
WHERE Accepted_ind = 'Y'
OR (NOT EXISTS (SELECT 1 FROM MyTable t2
WHERE Accepted_ind = 'Y'
and t2.Transaction_id = t.transaction_id)
AND Decision_id = 1)
This approach uses ROW_NUMBER() and therefore will only work on SQL Server 2005 or later
I have modified your sample data as as it stands, all transaction_id have a Y indicator!
DECLARE #t TABLE (
transaction_id NCHAR(1),
decision_id INT,
accepted_ind NCHAR(1) NULL
)
INSERT #t VALUES
( 'A' , 1 , NULL ),
( 'A' , 2 , '' ),
( 'A' , 4 , 'Y' ),
( 'B' , 1 , '' ),
( 'B' , 2 , 'N' ), -- change from your sample data
( 'C' , 1 , 'Y' ),
( 'D' , 1 , 'N' ),
( 'D' , 2 , 'O' ),
( 'D' , 3 , 'Y' )
And here is the query itself:
SELECT transaction_id, decision_id, accepted_ind FROM (
SELECT transaction_id, decision_id, accepted_ind,
ROW_NUMBER() OVER (
PARTITION BY transaction_id
ORDER BY
CASE
WHEN accepted_ind = 'Y' THEN 1
WHEN decision_id = 1 THEN 2
ELSE 3
END
) rn
FROM #t
) Raw
WHERE rn = 1
Results:
transaction_id decision_id accepted_ind
-------------- ----------- ------------
A 4 Y
B 1
C 1 Y
D 3 Y
The ROW_NUMBER() clause gives a 'priority' to each criterion you mention; we then ORDER BY to pick the best, and take the first row.
There's probably a neater/more efficient query, but I think this will get the job done. It assumes the table name is Decision:
SELECT CASE
WHEN accepteddecision.transaction_id IS NOT NULL THEN
accepteddecision.transaction_id
ELSE firstdecision.transaction_id
END AS transaction_id,
CASE
WHEN accepteddecision.decision_id IS NOT NULL THEN
accepteddecision.decision_id
ELSE firstdecision.decision_id
END AS decision_id,
CASE
WHEN accepteddecision.accepted_ind IS NOT NULL THEN
accepteddecision.accepted_ind
ELSE firstdecision.accepted_ind
END AS accepted_ind
FROM decision
LEFT OUTER JOIN (SELECT *
FROM decision AS accepteddecision
WHERE accepteddecision.accepted_ind = 'Y') AS
accepteddecision
ON accepteddecision.transaction_id = decision.transaction_id
LEFT OUTER JOIN (SELECT *
FROM decision AS firstdecision
WHERE firstdecision.decision_id = 1) AS firstdecision
ON firstdecision.transaction_id = decision.transaction_id
GROUP BY accepteddecision.transaction_id,
firstdecision.transaction_id,
accepteddecision.decision_id,
firstdecision.decision_id,
accepteddecision.accepted_ind,
firstdecision.accepted_ind
Out of interest, the following uses UNION and EXCEPT (plus a JOIN) as specified in the question title:
WITH T AS (SELECT * FROM (
VALUES ('A', 1, NULL),
('A', 2, ''),
('A', 4, 'Y'),
('B', 1, ''),
('B', 2, 'Y'),
('C', 1, 'Y'),
('D', 1, 'N'),
('D', 2, 'O'),
('D', 3, 'Y'),
('E', 2, 'O'), -- smaple data extended
('E', 1, 'N') -- smaple data extended
) AS T (transaction_id, decision_id, accepted_ind)
)
SELECT *
FROM T
WHERE accepted_ind = 'Y'
UNION
SELECT T.*
FROM (
SELECT transaction_id
FROM T
WHERE decision_id = 1
EXCEPT
SELECT transaction_id
FROM T
WHERE accepted_ind = 'Y'
) D
JOIN T
ON T.transaction_id = D.transaction_id
AND T.decision_id = 1;