Case Statement in Oracle with multiple True expressions - oracle

I got a situation where I have more than one expression as True and I need to return all of them within the same colum defined in my report.
I tried applying a case statement or nested case statement but I only see one result since case statement is only picking up the first true expression.
How can I add multiple Case statement to retrieve all true conditions ?
** Extract Example from PL/SQL:**
SELECT CRN, CASE
WHEN CASE_1 = 'A' AND CASE_2 = 'B'
THEN FORMULA1 -- FORMULA1 is an output of a query
WHEN CASE_1 = 'A' AND CASE_2 = 'C'
THEN FORMULA2 -- FORMULA2 is an output of a query
WHEN CASE_3 THEN FORMULA3 -- FORMULA3 is an output of a query
END AS INV
FROM ACC;
Formula1 output: 100
Formula2 output: 500
Formula3 output: 400
What I'm getting:
CRN
INV
USD
100
What I'm expecting:
CRN
INV
USD
100
USD
500
USD
400
Example of Nested case used:
CASE
WHEN CASE_1 = 'A' THEN (
CASE WHEN CASE_2 = 'B' THEN FORMULA1
END)
WHEN CASE_1 = 'A' THEN (
CASE WHEN CASE_2 = 'C' THEN FORMULA1
END)
END AS INV;
This also returns one result only.

Building on #SadlyFullStack's comment I suggest this statement
SELECT CRN, FORMULA1 AS INV FROM ACC WHERE CASE_1 = 'A' AND CASE_2 = 'B'
UNION ALL
SELECT CRN, FORMULA2 AS INV FROM ACC WHERE CASE_1 = 'A' AND CASE_2 = 'C'
UNION ALL
SELECT CRN, FORMULA3 AS INV FROM ACC WHERE CASE_3

Related

Sum values of columns for multiple groups in Oracle table

I have a table with a list of people, gender, race, & their group. It has 2000 records divided into 8 groups. I am trying to use PL/SQL to get the sum of people per gender per race for each group. I was thinking I should have a cursor populate a variable called v_group with the 8 groups, and then loop through all the records and get the counts for each one. It will put each count into their own variable and then insert the new summed up counts and the corresponding group into a table.
Here is a sample of what I have so far.
OPEN cur_get_group;
LOOP
v_group := '';
FETCH cur_get_group
INTO v_group;
EXIT WHEN cur_get_group%NOTFOUND;
SELECT COUNT(*) INTO v_hisp_m FROM mytable WHERE sex = 'M' AND ethn_code = 'H';
SELECT COUNT(*) INTO v_hisp_f FROM mytable WHERE sex = 'F' AND ethn_code = 'H';
SELECT COUNT(*) INTO v_cauc_m FROM mytable WHERE sex = 'M' AND ethn_code = 'C';
SELECT COUNT(*) INTO v_cauc_f FROM mytable WHERE sex = 'F' AND ethn_code = 'C';
INSERT INTO mynewtable
(group, hisp_m, hisp_f, cauc_m, cauc_f)
VALUES
(v_group, v_hisp_m, v_hisp_f, v_cauc_m, v_cauc_f);
COMMIT;
END LOOP;
Am I on the right track here? Do I need to do the loop differently?
Relational data bases work on the set of data matching conditions and are very good at it. They are however very poor single item processing. Learn to think is terms of sets. Your process has to pass the source data table 5 times (1 for group and count process) While loops are sometimes a necessary evil they are a process of last resort. This can be done in 1 statement, passing the source 1 time.
insert into mynewtable
(group, hisp_m, hisp_f, cauc_m, cauc_f)
select group_code
, sum(hisp_m)
, sum(hisp_f)
, sum(cauc_m)
, sum(cauc_f)
from
( select group_code,
, case when sex = 'F' AND ethn_code = 'H' then 1 else 0 end hisp_f
, case when sex = 'M' AND ethn_code = 'H' then 1 else 0 end hisp_m
, case when sex = 'F' AND ethn_code = 'C' then 1 else 0 end cauc_f
, case when sex = 'M' AND ethn_code = 'C' then 1 else 0 end cauc_m
from youroldtable
)
group by group_code;
BTW DO NOT use group as a column name. It is a reserved word and doing so will lead to very difficult to find errors.
Looks to me like you want to use pivot:
with rws as (
select 'F' sex, 'H' eth from dual connect by level <= 3
union all
select 'F' sex, 'C' eth from dual connect by level <= 4
union all
select 'M' sex, 'H' eth from dual connect by level <= 2
union all
select 'M' sex, 'C' eth from dual
)
select * from rws
pivot (
count (*) for ( sex, eth ) in (
( 'F', 'H' ) fh,
( 'F', 'C' ) fc,
( 'M', 'H' ) mh,
( 'M', 'C' ) mc
)
);
FH FC MH MC
3 4 2 1
And insert the result of this query to the table.

Oracle pl/sql order by case

I need to arrange the below data in a particular format
Code Qty
R 200
R 0
A 100
A 0
Required Output Format
Active (A) with stock (Qty > 0)
Reserve (R) with stock (Qty > 0)
Active (A) without stock (Qty = 0)
Reserve (R) without stock (Qty = 0)
In above case
A 100
R 200
A 0
R 0
Please help me on this guys. i tried using case in order by but couldnt eventually figure out the answer.
You were right to try an ORDER BY CASE. The following will give the results you're looking for:
ORDER BY CASE
WHEN CODE = 'A' AND QTY > 0 THEN 1
WHEN CODE = 'R' AND QTY > 0 THEN 2
WHEN CODE = 'A' AND QTY = 0 THEN 3
WHEN CODE = 'R' AND QTY = 0 THEN 4
END;
dbfiddle here
Best of luck.
EDIT
To accomplish the additional requirement mentioned by OP in a comment a second sort could be added to the ORDER BY:
ORDER BY CASE
WHEN CODE = 'A' AND QTY > 0 THEN 1
WHEN CODE = 'R' AND QTY > 0 THEN 2
WHEN CODE = 'A' AND QTY = 0 THEN 3
WHEN CODE = 'R' AND QTY = 0 THEN 4
END ASC,
CASE
WHEN CODE = 'A' AND QTY > 0 THEN QTY
ELSE NULL
END DESC;
new dbfiddle here
For the specific data and requirement you presented, you don't really need a case expression. (Actually I am lying - sign() is a form of case, of course.)
There is ambiguity if you have more than one row with the same combination of code and either positive qty or qty = 0; exactly the same ambiguity you left open with your problem statement.
with
inputs as (
select 'R' code, 200 qty from dual union all
select 'R' , 0 from dual union all
select 'A' , 100 from dual union all
select 'A' , 0 from dual
)
select *
from inputs
order by sign(qty) desc, code
;
CODE QTY
---- ----
A 100
R 200
A 0
R 0

PL/SQL: Need help creating calculated columns based on conditions. to be done in select query

I am trying to count all the distinct ids based on conditions. But I am unable to figure out where I am going wrong with the syntax. The logic is
COUNTD(IF ([column_name1] = 1) THEN [DATAPAGEID] END)
This is the formula I used in Tableau. However when writing it in a PL/SQL query as
Select FT.NAME, COUNT(DISTINCT FT.pageID IF FT."column_name" = 1 )
as total_expected
FROM
( Sub Query) FT
Group by FT.Name
Order by FT.Name
Needless to say its throwing errors. Now I can write separate queries which can give me each number using a where condition. For example, if I wanted a count of distinct pageid where column_name1 = 1, I would write something like this
Select FT.SITENAME, COUNT(DISTINCT DATAPAGEID) as Datapage
from
(sub query)
WHERE FT."column_name" = 1
but the problem with that is that I have other calculated columns in the query which will all need to be part of the same row. To illustrate here's what the table would look like
name Calculated_Column1 Calculated_Column2 Calculated_column3
abc 781 811 96.54%
pqr 600 800 75.00%
where calculated_column3 is the result of 781/811. Therefore I can't have a new query for each column. I thought using an if condition when calculating columns will solve this, but I can't get the syntax right somehow.
Therefore, I need to know how can I create conditional calculated columns within the select query. If I have not explained this well, please let me know and I will try to clarify further.
You can use a CASE block inside the count (DISTINCT ) as shown.
SELECT FT.NAME,
COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 1
THEN 1
ELSE 0
END ) Calculated_Column1,
COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 2
THEN 1
ELSE 0
END ) Calculated_Column2,
( COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 1
THEN 1
ELSE 0
END ) / COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 2
THEN 1
ELSE 0
END ) ) * 100||'%' Calculated_Column3
FROM
( SELECT 'abc' name, 1 DATAPAGEID FROM dual
UNION ALL
SELECT 'abc' name, 1 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 2 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 2 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 3 DATAPAGEID FROM dual
) FT
GROUP BY FT.Name
ORDER BY FT.Name;
Output is
abc 1 1 100%
pqr 1 2 50%

How can I join these two select queries into one query?

this is my first time posting, so I am sure I will get a number of things wrong. Do not hesitate to correct me and I will do everything I can to clarify.
In Oracle SQL Developer, I am trying to take two separate SELECT statements and combine them to get one row of results. Unfortunately, because this is sensitive data, I am unable to give any results from the statements individually, but instead, just the SQL statements themselves. I suspect I should be able to join these two on the field "emplid" but just cannot get there. Any help is greatly appreciated! Here is the code below, please mind the syntax :)
1st Select statement is giving me a list of people that were paid in 2017:
SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
ORDER BY C.COMPANY, C.EMPLID
And 2nd Select statement would be a list of the deductions taken for the employees that were identified in the first statement:
SELECT G.EMPLID, G.DEDCD,
CASE
WHEN DC.DED_CLASS IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "EEAmt",
CASE
WHEN DC.DED_CLASS NOT IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "ERAmt",
DC.DED_CLASS,
G.DED_ADDL_AMT,
G.GOAL_AMT
FROM PS_GENL_DEDUCTION G,
PS_DED_CLASS_VW DC
WHERE G.EFFDT =
(SELECT MAX(G_ED.EFFDT)
FROM PS_GENL_DEDUCTION G_ED
WHERE G.EMPLID = G_ED.EMPLID
AND G.COMPANY = G_ED.COMPANY
AND G.DEDCD = G_ED.DEDCD
AND G_ED.EFFDT <= SYSDATE
)
AND ( G.DEDUCTION_END_DT IS NULL
OR G.DEDUCTION_END_DT > SYSDATE)
AND ( G.GOAL_AMT = 0.00
OR G.GOAL_AMT <> G.GOAL_BAL)
AND G.DED_ADDL_AMT > 0
AND DC.PLAN_TYPE = '00'
AND DC.DEDCD = G.DEDCD
AND DC.EFFDT =
(SELECT MAX(V1.EFFDT)
FROM PS_DED_CLASS_VW V1
WHERE V1.PLAN_TYPE = DC.PLAN_TYPE
AND V1.DEDCD = DC.DEDCD
)
AND G.EMPLID = 'XXXXXX'
Ideally, what I'd like to do is put in a value in place of 'XXXXXX' and get one row of data with the two combined statements.
Thanks everyone!
To do this, we stick each SELECT statement into a subquery and give that subquery an alias to refer to in the main queries select statement:
SELECT t1.*, t2.*
FROM
(
SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
ORDER BY C.COMPANY, C.EMPLID
) t1
INNER JOIN
(
SELECT G.EMPLID, G.DEDCD,
CASE
WHEN DC.DED_CLASS IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "EEAmt",
CASE
WHEN DC.DED_CLASS NOT IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "ERAmt",
DC.DED_CLASS,
G.DED_ADDL_AMT,
G.GOAL_AMT
FROM PS_GENL_DEDUCTION G,
PS_DED_CLASS_VW DC
WHERE G.EFFDT =
(SELECT MAX(G_ED.EFFDT)
FROM PS_GENL_DEDUCTION G_ED
WHERE G.EMPLID = G_ED.EMPLID
AND G.COMPANY = G_ED.COMPANY
AND G.DEDCD = G_ED.DEDCD
AND G_ED.EFFDT <= SYSDATE
)
AND ( G.DEDUCTION_END_DT IS NULL
OR G.DEDUCTION_END_DT > SYSDATE)
AND ( G.GOAL_AMT = 0.00
OR G.GOAL_AMT <> G.GOAL_BAL)
AND G.DED_ADDL_AMT > 0
AND DC.PLAN_TYPE = '00'
AND DC.DEDCD = G.DEDCD
AND DC.EFFDT =
(SELECT MAX(V1.EFFDT)
FROM PS_DED_CLASS_VW V1
WHERE V1.PLAN_TYPE = DC.PLAN_TYPE
AND V1.DEDCD = DC.DEDCD
)
AND G.EMPLID = 'XXXXXX'
) t2 ON
t1.empid = t2.empid
We just treat each derived table/subquery as it's own table and join them on empid. You can tweak the SELECT statement at the top as needed.
This is similar to creating a view for both sql statements and then referencing the views in a third sql statement.
An alternative way of doing this is to use CTE (Common Table Expressions) to house the separate sql. There's no performance advantage here, but you might find it easier to read.
WITH t1 as
(
SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
ORDER BY C.COMPANY, C.EMPLID
),
t2 AS
(
SELECT G.EMPLID, G.DEDCD,
CASE
WHEN DC.DED_CLASS IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "EEAmt",
CASE
WHEN DC.DED_CLASS NOT IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "ERAmt",
DC.DED_CLASS,
G.DED_ADDL_AMT,
G.GOAL_AMT
FROM PS_GENL_DEDUCTION G,
PS_DED_CLASS_VW DC
WHERE G.EFFDT =
(SELECT MAX(G_ED.EFFDT)
FROM PS_GENL_DEDUCTION G_ED
WHERE G.EMPLID = G_ED.EMPLID
AND G.COMPANY = G_ED.COMPANY
AND G.DEDCD = G_ED.DEDCD
AND G_ED.EFFDT <= SYSDATE
)
AND ( G.DEDUCTION_END_DT IS NULL
OR G.DEDUCTION_END_DT > SYSDATE)
AND ( G.GOAL_AMT = 0.00
OR G.GOAL_AMT <> G.GOAL_BAL)
AND G.DED_ADDL_AMT > 0
AND DC.PLAN_TYPE = '00'
AND DC.DEDCD = G.DEDCD
AND DC.EFFDT =
(SELECT MAX(V1.EFFDT)
FROM PS_DED_CLASS_VW V1
WHERE V1.PLAN_TYPE = DC.PLAN_TYPE
AND V1.DEDCD = DC.DEDCD
)
AND G.EMPLID = 'XXXXXX'
)
SELECT t1.*, t2.*
FROM t1 INNER JOIN t2 ON
t1.empid = t2.empid
Basically you do something like
select blah, blah.. (your second query )
AND G.EMPLID IN ( SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
)
So basically I have used your first query in you second query. I removed the DISTINCT, as its not needed.
I hope that makes sense.

Select statement inside NVL

I'm trying to run the following query:
select a.*,
case when NVL (SELECT max(b.field1)
FROM b
where b.field2 = a.tbl_a_PK , 'TRUE') = 'TRUE'
then 'has no data in b'
else 'has data in b' end as b_status
from a
I checked and the select inside the nvl returns only 1 value (so there shouldn't be a problem there).
However I'm getting 'ORA-00936: missing expression'
NVL() requires 2 parameters: expression to test and default value e.g. nvl(some_field, 111). You just need to isolate query parameter by braces and provide second parameter like in this statement:
select nvl( (select 1 from dual), 34) from dual
In your variant parser expects comma after SELECT keyword and can't parse remaining string.
Exactly your statement must look like this:
select
a.*,
case when NVL(
( SELECT max(b.field1)
FROM b
where b.field2 = a.tbl_a_PK
),
'TRUE'
) = 'TRUE'
then 'has no data in b'
else 'has data in b' end as b_status
from a
Hope this helps ...
Update
In terms of performance is better to use exists rather then max :
select
a.*,
case when exists
( SELECT null
FROM b
where b.field2 = a.tbl_a_PK
and
b.field2 is not null
and
rownum = 1
),
then 'has data in b'
else 'has no data in b' end as b_status
from a
If you're searching for records in a which have/don't have associated records in b
select a.*,
case when b.field2 is null then 'has no data in b'
else 'has data in b'
as b_status
from a left outer join b
on a.tbl_a_PK = b.field2;
Should do it
the NVL(string1, replace_with) function requires 2 parameters, see docs here:
http://www.techonthenet.com/oracle/functions/nvl.php
Ora 10g docs: http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions105.htm
Since you know the problem, this query can fix it:
select a.*,
case
when (SELECT NVL(b.field2, 0) FROM b where b.field2 = a.tbl_a_PK and rownum = 1) > 0 then
'has data in b'
else
'has no data in b'
end b_status
from a
and runs faster.
You don't need max() to check if the value exists in another table, simply check if the primary key is not null.

Resources