Sum all Flags in one row in Oracle - sql

I had to create 5 flags in my dataset, per member record. Now the final requirement is to sum all the flags for each member.
For eg, one member has - Y,Y,,,Y i.e 3 flags set to Y. I need sum of these as 3 in my last created sum field.
I am doing this in Oracle ( Proc SQL in SAS)
Please help somebody!!
Thanks a lot..

Use CATX to combine all into one field
Use COUNTC to count them all.
select countc(catx(', ', flag1, flag2, flag3, flag4, flag5), 'Y') as num_y

One method uses case:
select t.*,
((case when flag1 = 'Y' then 1 else 0 end) +
(case when flag2 = 'Y' then 1 else 0 end) +
(case when flag3 = 'Y' then 1 else 0 end) +
(case when flag4 = 'Y' then 1 else 0 end) +
(case when flag5 = 'Y' then 1 else 0 end)
) as num_ys
from t;

Concatenate the 5 flags, then use regexp_count() to count the "Y"s. the following also allows for upper/lower case if that is an issue.
select regexp_count(flag1||flag2||flag3||flag4||flag5,'[yY]')
Oracle 10g and up

I'd recommend a reconfiguration of the table to ingest 1,0 for Y,N. If possible.
If not, oracle has a regexp_count. Concatenate the fields then regexp_count.

user9026530 specifically said he is using SAS, here is a pure SQL solution involving no SAS functions:
WITH
aset
AS
(SELECT 'Y' flag1
, 'N' flag2
, 'Y' flag3
, 'Y' flag4
, NULL flag5
FROM DUAL)
SELECT aset.*, LENGTH (REGEXP_REPLACE (flag1 || flag2 || flag3 || flag4 || flag5, '[^Y]', NULL)) flag_count
FROM aset
FLAG1 FLAG2 FLAG3 FLAG4 FLAG5 FLAG_COUNT
Y N Y Y 3

Related

case statement to find one value

I'm trying to show one row per id but it is returning three.
If the id has a 'y' then it should show a 'y'.
If it shows a 'y' and 'r' it should be 'y'.
If it has 'y', 'r', 'n' it should be 'y'.
If it is just id and 'r' it should be 'r' and id and just 'n' then 'n'.
I can't seem to get it to work using a case statement. Any ideas? Thanks.
I've tried this:
,CASE WHEN result = 'Y' THEN 'Y'
WHEN result = 'Y' AND result = 'R') THEN 'Y'
WHEN result = 'R' THEN 'R'
ELSE 'N' END AS CARE_PLAN
What it is returning:
ID result
3434 'y'
3434 'r'
3434 'n'
You can use Listagg function,
Writing a subquery and DISTINCT then use Listagg function.
SELECT id, Listagg (result, ', ')
within GROUP (ORDER BY result) as CARE_PLAN
FROM (SELECT DISTINCT id,
( CASE
WHEN result = 'Y' THEN 'Y'
WHEN result = 'Y'
AND result = 'R' THEN 'Y'
WHEN result = 'R' THEN 'R'
ELSE 'N'
END ) AS result
FROM t) T
GROUP BY id
sqlfiddle:http://sqlfiddle.com/#!4/02cd5/2
[Results]:
| ID | CARE_PLAN |
|------|-----------|
| 1234 | N, R, Y |
It shall be proper to use ASCII and CHR functions for your case instead of using CASE .. WHEN, as in the following :
SELECT ID, CHR(MAX(ASCII(result))) AS CARE_PLAN
FROM TAB
GROUP BY ID
ORDER BY ID;
SQL Fiddle Demo
You would seem to want aggregation with some conditional logic:
select id,
(case when sum(case when result = 'y' then 1 else 0 end) > 0 then 'y'
when sum(case when result = 'r' then 1 else 0 end) > 0 then 'r'
when min(result) = max(result) and min(result) = 'n' then 'n'
else '?'
end) as new_result
from t
group by id;
If there are only those three values, then perhaps this simplified logic works:
select id, max(result) as new_result
from t
group by id;

Case expression with multiple results

I am looking for a way to actually create some duplication in my results in MS SQL Server. I understand that typically you are looking for ways to not create duplication, but in this examples I need all the individual rows returned.
I am working with a table with about 10 million rows and 33 columns. The table consists of an ID in the first column and the remainder of the columns have either 'Y' or 'NULL' in them - a HUGE majority of the columns are NULL.
Of the 10 million rows 8 million of them only have a single 'Y' per row with the remaining 2 million rows having more than one column with a 'Y' in a row.
For the rows with a single 'Y' a basic case expression works perfectly fine to create a single column of results .
Here is my problem though - I want two rows, one for each 'Y' if there is more than one 'Y' in a row.
Below is a small de-identified sample.
ID FLAG1 FLAG2 FLAG3 FLAG4 FLAG5
188 NULL NULL NULL NULL NULL
194 Y NULL NULL NULL NULL
200 Y NULL NULL NULL Y
I am attempting to use a Case Expression like this.
Select
ID
,Case
When [FLAG1] = 'Y'
Then 'FLAG1'
When [FLAG2] = 'Y'
Then 'FLAG2'
End as 'Service_Line'
What I want is a result that looks like this.
ID Service_Line
194 FLAG1
200 FLAG1
200 FLAG5
My problem is that the Case expression only returns the first result so I end up with this.
ID Service_Line
194 FLAG1
200 FLAG1
Is a Case Expression appropriate for what I am trying to accomplish or should I be trying to go about this some other way?
A case is not appropriate. A general SQL approach would be:
select id, 'FLAG1' as flag
from t
where flag1 = 'Y'
union all
select id, 'FLAG2' as flag
from t
where flag2 = 'Y'
union all
select id, 'FLAG3' as flag
from t
where flag3 = 'Y'
. . .
There are other (more efficient) methods, but those depend on the database you are using.
I should note: case is the right approach if you want the values in a single row:
select id,
concat( case when flag1 = 'Y' then 'FLAG1 ' else '' end,
case when flag2 = 'Y' then 'FLAG2 ' else '' end,
case when flag3 = 'Y' then 'FLAG3 ' else '' end,
. . .
) as flags
from t;
Of course, the syntax for concat() can vary among databases (concat() itself is ANSI standard). You can also trim off the last space.

To get one record if mre than one record exists for same id ,then get only one record based on below condition

e.g. If I get 2 null data in a column for same id, then pass null.
ii) If I get 2 same not null data in a column for same id, then pass not null.
ii) If I get 1 null and 1 not null data in a column for same id, then pass not null.
ii) If I get 2 different not null data in a column for same id, then pass '?'.
Sample data
Please find the sample data in the image.
Thanks in advance.
Output obtained after new code:
Result
You can try following :
SELECT (CASE WHEN PRTY_KEY_ID_NN= 0 THEN NULL /* IF BOTH/ALL VALUES ARE NULL */)
WHEN PRTY_KEY_ID_NN <> TOTAL_COUNT THEN PRTY_KEY_ID_NN_MX
ELSE
CASE WHEN TOTAL_COUNT=PRTY_KEY_ID_NN_DIST THEN NULL /* IF BOTH HAVING DIFFERENT NOT NULL VALUE */
CASE WHEN TOTAL_COUNT<>PRTY_KEY_ID_NN_DIST THEN PRTY_KEY_ID_NN_MX
END
END)PRTY_KEY_ID_VAL,
(CASE WHEN TOTAL_ASSET_A_NN= 0 THEN NULL /* IF BOTH/ALL VALUES ARE NULL */)
WHEN TOTAL_ASSET_A_NN <> TOTAL_COUNT THEN TOTAL_ASSET_A_MX
ELSE
CASE WHEN TOTAL_COUNT=TOTAL_ASSET_A_DIST THEN NULL /* IF BOTH HAVING DIFFERENT NOT NULL VALUE */
CASE WHEN TOTAL_COUNT<>TOTAL_ASSET_A_DIST THEN TOTAL_ASSET_A_MX
END
END)TOTAL_ASSET_VAL,
(CASE WHEN DBUS_NUM_NN= 0 THEN NULL /* IF BOTH/ALL VALUES ARE NULL */)
WHEN DBUS_NUM_NN <> TOTAL_COUNT THEN DBUS_NUM_MX
ELSE
CASE WHEN TOTAL_COUNT=DBUS_NUM_DIST THEN NULL /* IF BOTH HAVING DIFFERENT NOT NULL VALUE */
CASE WHEN TOTAL_COUNT<>DBUS_NUM_DIST THEN DBUS_NUM_MX
END
END)DBUS_NUM_DIST_VAL,
(CASE WHEN BUS_NATURE_DE_NN= 0 THEN NULL /* IF BOTH/ALL VALUES ARE NULL */)
WHEN BUS_NATURE_DE_NN <> TOTAL_COUNT THEN BUS_NATURE_DE_MX
ELSE
CASE WHEN TOTAL_COUNT=BUS_NATURE_DE_DIST THEN NULL /* IF BOTH HAVING DIFFERENT NOT NULL VALUE */
CASE WHEN TOTAL_COUNT<>BUS_NATURE_DE_DIST THEN BUS_NATURE_DE_MX
END
END)BUS_NATURE_DE_VAL
FROM
(SELECT COUNT(*)TOTAL_COUNT,
SUM(CASE WHEN PRTY_KEY_ID IS NULL THEN 0 ELSE 1 END)PRTY_KEY_ID_NN,
SUM(CASE WHEN TOTAL_ASSET_A IS NULL THEN 0 ELSE 1 END)TOTAL_ASSET_A_NN,
SUM(CASE WHEN TOTAL_LIAB_A IS NULL THEN 0 ELSE 1 END)TOTAL_LIAB_A_NN,
SUM(CASE WHEN DBUS_NUM IS NULL THEN 0 ELSE 1 END)DBUS_NUM_NN,
SUM(CASE WHEN BUS_NATURE_DE IS NULL THEN 0 ELSE 1 END)BUS_NATURE_DE_NN,
MAX(PRTY_KEY_ID_NN)PRTY_KEY_ID_NN_MX,
MAX(TOTAL_ASSET_A)TOTAL_ASSET_A_MX,
MAX(TOTAL_LIAB_A)TOTAL_LIAB_A_MX,
MAX(DBUS_NUM)DBUS_NUM_MX,
MAX(BUS_NATURE_DE)BUS_NATURE_DE_MX,
COUNT(DISTINCT PRTY_KEY_ID_NN) PRTY_KEY_ID_NN_DIST,
COUNT(DISTINCT TOTAL_ASSET_A)TOTAL_ASSET_A_DIST,
COUNT(DISTINCT TOTAL_LIAB_A)TOTAL_LIAB_A_DIST,
COUNT(DISTINCT DBUS_NUM)DBUS_NUM_DIST,
COUNT(DISTINCT BUS_NATURE_DE)BUS_NATURE_DE_DIST
FROM YOUR_TABLE)
You can find out the sample data like image in below SQL.If the column data type is varchar(char) please delete the TO_CHAR expression, and replace the table name TEST_TABLE to your table's.
WITH TEST_TABLE AS (
SELECT 1056059 AS PRTY_KEY_ID, NULL AS TOT_ASSET_AM, NULL AS TOT_LIABILITIES_AM, NULL AS DBUS_NM, 'Pediatrics' AS BUS_NATURE_DE FROM DUAL
UNION ALL
SELECT 1056059 AS PRTY_KEY_ID, 5000 AS TOT_ASSET_AM, '300000' AS TOT_LIABILITIES_AM, NULL AS DBUS_NM, 'Medicine' AS BUS_NATURE_DE FROM DUAL
)
SELECT
PRTY_KEY_ID,
MIN(CASE WHEN TOT_ASSET_AM_CNT > 1 THEN '?' ELSE TO_CHAR(TOT_ASSET_AM) END) AS TOT_ASSET_AM,
MIN(CASE WHEN TOT_LIABILITIES_AM_CNT > 1 THEN '?' ELSE TO_CHAR(TOT_LIABILITIES_AM) END) AS TOT_LIABILITIES_AM,
MIN(CASE WHEN DBUS_NM_CNT > 1 THEN '?' ELSE TO_CHAR(DBUS_NM) END) AS DBUS_NM,
MIN(CASE WHEN BUS_NATURE_DE_CNT > 1 THEN '?' ELSE TO_CHAR(BUS_NATURE_DE) END) AS BUS_NATURE_DE
FROM (
SELECT
TEST_TABLE.*,
COUNT(TOT_ASSET_AM) OVER(PARTITION BY PRTY_KEY_ID) AS TOT_ASSET_AM_CNT,
COUNT(TOT_LIABILITIES_AM) OVER(PARTITION BY PRTY_KEY_ID) AS TOT_LIABILITIES_AM_CNT,
COUNT(DBUS_NM) OVER(PARTITION BY PRTY_KEY_ID) AS DBUS_NM_CNT,
COUNT(BUS_NATURE_DE) OVER(PARTITION BY PRTY_KEY_ID) AS BUS_NATURE_DE_CNT
FROM TEST_TABLE
)
GROUP BY PRTY_KEY_ID

select single row based on count of rows present oracle

I've got below table using a query, Now I want to fetch single record based on conditions explained below and assign it to two variable i.e. v_dte_meeting and v_status_meeting declared in my stored procedure,
Dte_Meeting| Ststus_Meeting
########################
15-Oct-14 | Due
30-Oct-14 | Due
15-Dec-14 | Init
30-Dec-14 | Init
30-Nov-15 | Approved
I want to assign value to these variables based on below conditions:
If a a single or multiple records present with Status_Meeting as 'Due' Then assign v_dte_meeting the greatest date with 'Due' status and assign v_status_meeting with value 'Due'
If above condition fails then, check If a single or multiple records present with Ststus_Meeting as 'Init' If it does, Then assign v_dte_meeting the greatest date with 'Init' status and assign v_status_meeting with value 'Init'
If both condition fails then assign both variables NULL value
Please help me to do it the best way in Oracle
Hope this helps. I am only showing the SELECT statement (I didn't create variables, so I am not selecting INTO, but that wasn't your difficulty, you know how to do that.) I use subquery factoring (the WITH clause), available only in versions >= 11 I believe, otherwise you can rewrite to put the subqueries where they belong.
Note the use of rank(); in Gordon's solution, he will pick up max(dte) over ALL rows, not just those with status = 'Due', so it can't be as simple as what he wrote. EDIT: I also don't see where he selects NULL, NULL if neither 'Due' nor 'Init' are present. (Sorry for the abuse, this should be a comment to his answer, I lack privileges.)
WITH t (Date_Meeting, Status_Meeting) AS
(
SELECT TO_DATE('15-OCT-14', 'DD-MON-YY'), 'Due' FROM dual UNION ALL
SELECT TO_DATE('30-OCT-14', 'DD-MON-YY'), 'Due' FROM dual UNION ALL
SELECT TO_DATE('15-DEC-14', 'DD-MON-YY'), 'Init' FROM dual UNION ALL
SELECT TO_DATE('30-DEC-14', 'DD-MON-YY'), 'Init' FROM dual UNION ALL
SELECT TO_DATE('15-NOV-15', 'DD-MON-YY'), 'Approved' FROM dual
),
s (Date_Meeting, Status_Meeting) AS
(
SELECT Date_Meeting, Status_Meeting FROM t
WHERE Status_Meeting = 'Due' OR Status_Meeting = 'Init'
UNION ALL SELECT NULL, NULL FROM dual -- To ensure you have the nulls if needed
),
r (Date_Meeting, Status_Meeting, rk) AS
(
SELECT Date_Meeting, Status_Meeting,
RANK() OVER (ORDER BY DECODE(Status_Meeting, 'Due', 0, 'Init', 1, 2),
Date_Meeting DESC) -- make sure you understand this
FROM s
)
SELECT Date_Meeting, Status_Meeting FROM r WHERE rk = 1
/
Result:
DATE_MEET STATUS_M
--------- --------
30-OCT-14 Due
1 row selected.
One method uses aggregation:
select (case when sum(case when status_meeting = 'Due' then 1 else 0 end) > 0
then max(case when status_meeting = 'Due' dte_meeting end)
when sum(case when status_meeting = 'Init' then 1 else 0 end) > 0
then max(case when status_meeting = 'Init' then dte_meeting end)
end),
(case when sum(case when status_meeting = 'Due' then 1 else 0 end) > 0
then'Due'
when sum(case when status_meeting = 'Init' then 1 else 0 end) > 0
then 'Init'
end)
into v_dte_meeting, v_status_meeting
from t;
However, I think a simpler version just uses order by:
select max(dte_meeting), max(status_meeting)
into v_dte_meeting, v_status_meeting
from (select t.*
from t
where status_meeting in ('Due', 'Init')
order by (case when status_meeting = 'Due' then 1
when status_meeting = 'Init' then 2
end)
) t
where rownum = 1;
The max() is just to ensure that exactly one row is returned.

Average and case in SQL Server 2012

I'd like to have the average of a column when its bigger than zero.
Select Avg(Case when Column > 0 then Column else 0 end) as Avg
but I'm afraid the else clause is not correct. I want to ignore the zero values in the average.
Remove else part from case statement so the values less than 1 will be NULL.
Null values will be eliminated by the Avg aggregate. So you will get the average of values which are greater then 0. Try this.
Select Avg(Case when [Column]>0 then [Column] end) as [Avg]
DEMO
Without else part in case statement (Expected Average)
SELECT Avg(CASE WHEN a > 0 THEN a END) [Avg]
FROM (SELECT 2 a UNION ALL SELECT 2 UNION ALL SELECT -1) bb
Result : 2
With else part in case statement.
SELECT Avg(CASE WHEN a > 0 THEN a ELSE 0 END) [Avg]
FROM (SELECT 2 a UNION ALL SELECT 2 UNION ALL SELECT -1) bb
Result : 1