SQL Beginner question: CASE AS END for multiple columns? - sql

I am making a stored procedure which creates a target data table (#tmp_target_table), does some checking on it, and outputs the results in a resultset table (#tmp_resultset_table). The resultset table needs to have multiple new columns: user_warning_id, user_warning_note, and user_warning_detail in addition to existing columns from #tmp_target_table.
I have a working stored procedure as in the following but this has some issue. I need to write conditionA, conditionB, and conditionB repeatedly but these conditions will need to be changed in the future. How would you write a code that is more extensible?
<Working code>
SELECT existing_col1, existing_col2,
CASE
WHEN conditionA
THEN user_warning_id_A
WHEN conditionB
THEN user_warning_id_B
WHEN conditionC
THEN user_warning_id_C
END AS user_warning_id,
CASE
WHEN conditionA
THEN user_warning_note_A
WHEN conditionB
THEN user_warning_note_B
WHEN conditionC
THEN user_warning_note_C
END AS user_warning_note,
CASE
WHEN conditionA
THEN user_warning_detail_A
WHEN conditionB
THEN user_warning_detail_B
WHEN conditionC
THEN user_warning_detail_C
END AS user_warning_detail,
existing_col3, existing_col4
INTO #tmp_resultset_table
FROM #tmp_target_table
SELECT * FROM #tmp_resultant_table

In SQL Server, you can use a lateral join (i.e., apply) to arrange the data so you can use a reference table:
select tt.*,
v2.user_warning_id, v2.user_warning_note, v2.user_warning_detail
from #tmp_target_table tt cross apply
(values (case when conditionA then 'a'
when conditionA then 'b'
when conditionA then 'c'
end)
) v(cond) left join
(values ('a', user_warning_id_A, user_warning_note_A, user_warning_detail_A),
('b', user_warning_id_B, user_warning_note_B, user_warning_detail_B),
('c', user_warning_id_C, user_warning_note_C, user_warning_detail_C)
) v2(cond, user_warning, user_warning_note, user_warning_detail)
on v2.cond = v.cond;
This also makes it pretty easy to add more levels, if you like.
Note: You could combine v and v2 into a single values list. I separated them, because you might want to consider making v2 an actual reference table.
EDIT:
DB2 supports lateral joins with the lateral keyword. I don't remember if DB2 supports values(). So try this:
select tt.*,
v2.user_warning_id, v2.user_warning_note, v2.user_warning_detail
from #tmp_target_table tt cross join lateral
(select (case when conditionA then 'a'
when conditionA then 'b'
when conditionA then 'c'
end)
from sysibm.sysdummy1
) v(cond) left join
(select 'a' as cond, user_warning_id_A as user_warning_id, user_warning_note_A as user_warning_note, user_warning_detail_A user_warning_detail
from sysibm.sysdummy1
union all
select 'b', user_warning_id_B, user_warning_note_B, user_warning_detail_B
from sysibm.sysdummy1
union all
select 'c', user_warning_id_C, user_warning_note_C, user_warning_detail_C
from sysibm.sysdummy1
) v2(cond, user_warning, user_warning_note, user_warning_detail)
on v2.cond = v.cond;

You could put the messages into a table and the condition logic into a function.
Just using temp tables so you can test it out.
Warnings
select warningID = 1, note = 'note 1', detail = 'notes on warning 1'
into #warning
union
select warningID = 2, note = 'note 2', detail = 'notes on warning 2'
union
select warningID = 3, note = 'note 3', detail = 'notes on warning 3'
union
select warningID = 4, note = 'note 4', detail = 'notes on warning 4'
Data values that have to meet conditions coming from some table ... #conditions
select condID = 1, val1 = 10, val2 = 1
into #conditions
union
select condID = 2, val1 = 20, val2 = 1
union
select condID = 3, val1 = 5, val2 = 2
union
select condID = 4, val1 = 30, val2 = 1
union
select condID = 4, val1 = 12, val2 = 1
Then a function that determines warnings based on conditions in the data. Takes values as input and returns a warningID
create function testWarningF
(
#val1In int
)
returns int
as
begin
declare #retVal int
select #retVal = case when #val1In <= 10 then 1
when #val1In > 10 and #val1In <=20 then 2
else 3
end
return #retVal
end
go
Then, the SQL ...
select *
from #conditions c
inner join #warning w on w.warningID = dbo.warningF(val1)
... returns this result
condID val1 val2 warningID note detail
1 10 1 1 note 1 notes on warning 1
2 20 1 2 note 2 notes on warning 2
3 5 2 1 note 1 notes on warning 1
4 12 1 2 note 2 notes on warning 2
4 30 1 3 note 3 notes on warning 3

Possibly the simplest method it to move the Conditions into a sub-select, then reference a token in the other select. E.g.
SELECT existing_col1
, existing_col2
, CASE CON
WHEN 'A' THEN user_warning_id_A
WHEN 'B' THEN user_warning_id_B
WHEN 'C' THEN user_warning_id_C END AS user_warning_id
, CASE CON
WHEN 'A' THEN user_warning_note_A
WHEN 'B' THEN user_warning_note_B
WHEN 'C' THEN user_warning_note_C END AS user_warning_note
, CASE CON
WHEN 'A' THEN user_warning_detail_A
WHEN 'B' THEN user_warning_detail_B
WHEN 'C' THEN user_warning_detail_C END AS user_warning_detail
, existing_col3
, existing_col4
FROM (
SELECT T.*
, CASE WHEN conditionA THEN 'A'
WHEN conditionB THEN 'B'
WHEN conditionC THEN 'C' END AS CON
FROM
#tmp_target_table T
)
although Gordon's answer is also neat, even though it adds two joins in the access plan. In Db2 Syntax, this works (on Db2 11.1.3.3 anyway)
select tt.*,
v2.user_warning_id, v2.user_warning_note, v2.user_warning_detail
from #tmp_target_table tt
, (values (case when conditionA then 'a'
when conditionB then 'b'
when conditionC then 'c'
end)
) v(cond) left join
(values ('a', 'user_warning_id_A', 'user_warning_note_A', 'user_warning_detail_A'),
('b', 'user_warning_id_B', 'user_warning_note_B', 'user_warning_detail_B'),
('c', 'user_warning_id_C', 'user_warning_note_C', 'user_warning_detail_C')
) v2(cond, user_warning_id, user_warning_note, user_warning_detail)
on v2.cond = v.cond;
testing with
create table #tmp_target_table(i int);
insert into #tmp_target_table(values 1);
create variable conditionA boolean;
create variable conditionB boolean default true;
create variable conditionC boolean;
returns
I USER_WARNING_ID USER_WARNING_NOTE USER_WARNING_DETAIL
- ----------------- ------------------- ---------------------
1 user_warning_id_B user_warning_note_B user_warning_detail_B

Related

SQL Server exclusive select on column value

Let's say I am returning the following table from a select
CaseId
DocId
DocumentTypeId
DocumentType
ExpirationDate
1
1
1
I797
01/02/23
1
2
2
I94
01/02/23
1
3
3
Some Other Value
01/02/23
I want to select ONLY the row with DocumentType = 'I797', then if there is no 'I797', I want to select ONLY the row where DocumentType = 'I94'; failing to find either of those two I want to take all rows with any other value of DocumentType.
Using SQL Server ideally.
I think I'm looking for an XOR clause but can't work out how to do that in SQL Server or to get all other values.
Similar to #siggemannen answer
select top 1 with ties
case when DocumentType='I797' then 1
when DocumentType='I94' then 2
else 3
end gr
,docs.*
from docs
order by
case when DocumentType='I797' then 1
when DocumentType='I94' then 2
else 3
end
Shortest:
select top 1 with ties
docs.*
from docs
order by
case when DocumentType='I797' then 1
when DocumentType='I94' then 2
else 3
end
Something like this perhaps:
select *
from (
select t.*, DENSE_RANK() OVER(ORDER BY CASE WHEN DocumentType = 'I797' THEN 0 WHEN DocumentType = 'I94' THEN 1 ELSE 2 END) AS prioorder
from
(
VALUES
(1, 1, 1, N'I797', N'01/02/23')
, (1, 2, 2, N'I94', N'01/02/23')
, (1, 3, 3, N'Some Other Value', N'01/02/23')
, (1, 4, 3, N'Super Sekret', N'01/02/23')
) t (CaseId,DocId,DocumentTypeId,DocumentType,ExpirationDate)
) x
WHERE x.prioorder = 1
The idea is to rank rows by 1, 2, 3 depending on document type. Since we rank "the rest" the same, you will get all rows if I797 and I94 is missing.
select * from YourTable where DocumentType = 'I797'
union
select * from YourTable t where DocumentType = 'I94' and (not exists (select * from YourTable where DocumentType = 'I797'))
union
select * from YourTable t where (not exists (select * from YourTable where DocumentType = 'I797' or DocumentType = 'I94' ))

Showing NULL on purpose when a NULL joined value is present in 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

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)

how to convert condition IN list into string oracle using parameter plsql?

I need to convert a condition IN list to a list of string using parameter.
from:
:p_parameter in ('a','b','c')
to:
:p_parameter = (a,b,c)
For example
select kod from dual where kod in(:p_parameter)
if the input is :p_parameter in (a,b,c)
then the output show
kod
------
a
b
c
Anyone got the idea?
Maybe LISTAGG will help you
SELECT col1
LISTAGG(p, ',') WITHIN GROUP (ORDER BY p) AS p
FROM p_tgable
WHERE p in ('a','b','c')
GROUP BY
col1;
Can be done with Nested Table collection type.
First declare a custom nested type, let's name it STRINGS_TYPE:
create type STRINGS_TYPE AS TABLE OF VARCHAR2(100);
Then you can compare collections for equality, for example this will return 1 because these two collections are equal:
select 1 from dual
where STRINGS_TYPE('a', 'b' , 'c' ) = STRINGS_TYPE('a', 'b', 'c')
Order does not matter, collections are equal, returns 1:
select 1 from dual
where STRINGS_TYPE( 'b','a', 'c' ) = STRINGS_TYPE('a', 'b', 'c')
These two collections are not equal, nothing is returned:
select 1 from dual
where STRINGS_TYPE( 'x','y', 'z', 'a', 'b', 'c' ) = STRINGS_TYPE('a', 'b', 'c')
In your code bind :p_parameter parameter like this:
String[] pArray = new String[]{"a", "b", "c"};
String selectSQL = "select 1 from dual where :p_parameter = STRINGS_TYPE('a', 'b', 'c')";
Connection connection = database.getConnection();
oracle.jdbc.OraclePreparedStatement preparedStatement = (oracle.jdbc.OraclePreparedStatement )connection.prepareStatement(selectSQL);
oracle.sql.ArrayDescriptor descriptor = oracle.sql.ArrayDescriptor.createDescriptor("YOUR_SCHEMA.STRINGS_TYPE", connection);
oracle.sql.ARRAY pOracleARRAY = new oracle.sql.ARRAY(descriptor, connection, pArray);
preparedStatement.setARRAY(1, pOracleARRAY);
resultSet = preparedStatement.executeQuery();
// ...

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;