How to split result of coalesce function in two columns in SQL - sql

We have three tables
Person_Table
Phone_Table
Primary_Phone_Table
All the phone number(primary and non-primary Table) is in format "areacode-Phonenumber" (123-5434) or Null. I want to extract area code from Phone_Number column and show it as a separate column of area code in my select query.
The query used is like this.
SELECT person_number,
COALESCE ((SELECT phone_number
FROM Primary_phone_table PPHT
WHERE PPHT.person_id = PT.person_id),
(SELECT phone_number
FROM phone_table PHT
WHERE PHT.person_id = PT.person_id)) Phone_Number
FROM Person_Table PT
Desired O/P:
Person_Number
Phone_Number
Area_Code

Why doing it in a more complex way than it should be? Why those subqueries instead of joins? Couldn't query (you posted) be rewritten to
SELECT pt.person_number,
coalesce(ppht.phone_number, pht.phone_number) phone_number
FROM person_table pt
LEFT JOIN primary_phone_table ppht ON ppht.person_id = pt.person_id
LEFT JOIN phone_table pht ON pht.person_id = pt.person_id;
I think it could, and it should. Code you wrote might raise TOO_MANY_ROWS if someone has more than a single phone number.
In order to extract area code and phone number (separated by a "minus" sign), a simple option is to use substr + instr functions combination. Current query can be used as a "source" (either as a subquery or a CTE (the WITH factoring clause)). For example:
WITH
current_query
AS
(SELECT pt.person_number,
COALESCE (ppht.phone_number, pht.phone_number) phone_number
FROM person_table pt
LEFT JOIN primary_phone_table ppht ON ppht.person_id = pt.person_id
LEFT JOIN phone_table pht ON pht.person_id = pt.person_id)
SELECT c.person_number,
SUBSTR (c.phone_number, 1, INSTR (c.phone_number, '-') - 1) area_code,
SUBSTR (c.phone_number, INSTR (c.phone_number, '-') + 1) phone_number
FROM current_query c;
With some sample data:
SQL> WITH
2 current_query (person_number, phone_number)
3 AS
4 (SELECT 1, '123-5454' FROM DUAL
5 UNION ALL
6 SELECT 2, NULL FROM DUAL)
7 SELECT c.person_number,
8 SUBSTR (c.phone_number, 1, INSTR (c.phone_number, '-') - 1) area_code,
9 SUBSTR (c.phone_number, INSTR (c.phone_number, '-') + 1) phone_number
10 FROM current_query c;
PERSON_NUMBER AREA_CODE PHONE_NUMBER
------------- ---------- ------------
1 123 5454
2
SQL>

you can use following query .
SELECT pt.person_number,
coalesce(ppht.phone_number, pht.phone_number) phone_number,
SUBSTR(coalesce(ppt.phone_number, pht.phone_number),1,regexp_instr(ppt.phone_number,'[-].*') - 1) area_code
FROM person_table pt
LEFT JOIN primary_phone_table ppt ON ppt.person_id = pt.person_id
LEFT JOIN phone_table pht ON pht.person_id = pt.person_id;

Related

how to compare two list values using oracle?

Select main.gr_number from
(
Select st.GR_NUMBER from student st where upper(st.class_id)=upper('jtm.online137') and st.is_active_flg='Y'
and st.status='STUDYING'
and upper(st.class_days) like '%'||TO_CHAR(to_date('31-OCT-2019'),'DY')||'%'
) main
where (Select GR_NUMBER from student_class_attend where upper(class_id)=upper('jtm.online137')
and attend_date ='31-OCT-2019') not in (main.GR_NUMBER);
it is giving me error
single-row subquery returns more than one row
Looks like NOT EXISTS to me, i.e.
SELECT main.gr_number
FROM (SELECT st.GR_NUMBER
FROM student st
WHERE UPPER (st.class_id) = UPPER ('jtm.online137')
AND st.is_active_flg = 'Y'
AND st.status = 'STUDYING'
AND UPPER (st.class_days) LIKE
'%'
|| TO_CHAR (TO_DATE ('31-OCT-2019', 'dd-mon-yyyy'),
'DY')
|| '%') main
WHERE NOT EXISTS
(SELECT GR_NUMBER
FROM student_class_attend
WHERE UPPER (class_id) = UPPER ('jtm.online137')
AND attend_date = TO_DATE ('31-OCT-2019', 'dd-mon-yyyy')
AND gr_number = main.GR_NUMBER);
Note that I modified your "date" values by applying missing format mask and TO_DATE function as you shouldn't compare dates to strings. Even better: use date literal, e.g. date '2019-10-31' instead.
You can't use WHERE with a subquery that returns more than a row.
Instead of WHERE try using a left join and (for NOT IN) check for null value in left joined key:
Select main.gr_number
from (
Select st.GR_NUMBER
from student st
where upper(st.class_id)=upper('jtm.online137')
and st.is_active_flg='Y'
and st.status='STUDYING'
and upper(st.class_days) like '%'||TO_CHAR(to_date('31-OCT-2019'),'DY')||'%'
) main
LEFT JOIN (
Select GR_NUMBER
from student_class_attend
where upper(class_id)=upper('jtm.online137')
and attend_date ='31-OCT-2019'
) t ON t.GR_NUMBER main.GR_NUMBER
AND t.GR_NUMBER is null;

Select Value Match - SQL

Can you advise if it is possible, to select a count for numerous substrings in a query
so if I have a message field which contains for example, text messages and I could do
SELECT COUNT(1)
FROM MESSAGES
WHERE MESSAGE_BODY LIKE '%hello%'
but what I want to do is more:
SELECT STRING, COUNT(1)
FROM MESSAGES
WHERE MESSAGE_BODY IN (list of strings with wild card)
is this possible?
to break down example:
ID | Message_Body
1 | Hello, How Are You?
2 | Hi, Great Thanks
3 | Hello, How is things?
4 | Ciao
Output wanted:
hello , 2
ciao, 1
SELECT (input strings), COUNT(1)
FROM TABLE
WHERE (input strings) IN ('%hello%','%ciao%')
If I understood you correctly, you can try something like this:
SELECT t.string,
CASE WHEN t.MESSAGE_BODY LIKE '%laptop%' then 1 else 0 END +
CASE WHEN t.MESSAGE_BODY LIKE '%one%' then 1 else 0 END +
CASE WHEN t.MESSAGE_BODY LIKE '%two%' then 1 else 0 END as count_col
FROM YourTable t
If you just want multiple LIKE comaparison, use REGEXP_LIKE() :
SELECT STRING, COUNT(1)
FROM MESSAGES
where regexp_like(MESSAGE_BODY, 'one|two|laptop')
EDIT: You can use a derived table containing all strings you are intrested on and left join to the original table for count:
SELECT t.wrd,COUNT(s.id) as cnt
FROM (
SELECT 'hello' as wrd FROM DUAL
UNION ALL
SELECT 'ciao' as wrd FROM DUAL) t
LEFT OUTER JOIN messages s
ON(s.message_body LIKE '%' || t.wrd || '%')
GROUP BY t.wrd
Here is with looking for whole words:
SELECT a.word, COUNT (message.message_body)
FROM ( SELECT REGEXP_SUBSTR ('hello,ciao', '[^,]+', 1, LEVEL) word
FROM DUAL
CONNECT BY REGEXP_SUBSTR ('hello,ciao', '[^,]+', 1, LEVEL) IS NOT NULL) a
LEFT OUTER JOIN MESSAGES ON REGEXP_INSTR (MESSAGE_BODY, '(^|\s)' || a.word || '(\s|$)', 1, 1, 0, 'i') > 0
GROUP BY a.word

How to convert only first letter uppercase without using Initcap in Oracle?

Is there a way to convert the first letter uppercase in Oracle SQl without using the Initcap Function?
I have the problem, that I must work with the DISTINCT keyword in SQL clause and the Initcap function doesn´t work.
Heres is my SQL example:
select distinct p.nr, initcap(p.firstname), initcap(p.lastname), ill.describtion
from patient p left join illness ill
on p.id = ill.id
where p.deleted = 0
order by p.lastname, p.firstname;
I get this error message: ORA-01791: not a SELECTed expression
When SELECT DISTINCT, you can't ORDER BY columns that aren't selected. Use column aliases instead, as:
select distinct p.nr, initcap(p.firstname) fname, initcap(p.lastname) lname, ill.describtion
from patient p left join illness ill
on p.id = ill.id
where p.deleted = 0
order by lname, fname
this would do it, but i think you need to post your query as there may be a better solution
select upper(substr(<column>,1,1)) || substr(<column>,2,9999) from dual
To change string to String, you can use this:
SELECT
regexp_replace ('string', '[a-z]', upper (substr ('string', 1, 1)), 1, 1, 'i')
FROM dual;
This assumes that the first letter is the one you want to convert. It your input text starts with a number, such as 2 strings then it won't change it to 2 Strings.
You can also use the column number instead of the name or alias:
select distinct p.nr, initcap(p.firstname), initcap(p.lastname), ill.describtion
from patient p left join illness ill
on p.id = ill.id
where p.deleted = 0
order by 3, 2;
WITH inData AS
(
SELECT 'word1, wORD2, word3, woRD4, worD5, word6' str FROM dual
),
inRows as
(
SELECT 1 as tId, LEVEL as rId, trim(regexp_substr(str, '([A-Za-z0-9])+', 1, LEVEL)) as str
FROM inData
CONNECT BY instr(str, ',', 1, LEVEL - 1) > 0
)
SELECT tId, LISTAGG( upper(substr(str, 1, 1)) || substr(str, 2) , '') WITHIN GROUP (ORDER BY rId) AS camelCase
FROM inRows
GROUP BY tId;

Splitting rows to columns in oracle

I have data in a table which looks like:
I want to split its data and make it look like the following through a sql query in Oracle (without using pivot):
How can it be done?? is there any other way of doing so without using pivot?
You need to use a pivot query here to get the output you want:
SELECT Name,
MIN(CASE WHEN ID_Type = 'PAN' THEN ID_No ELSE NULL END) AS PAN,
MIN(CASE WHEN ID_Type = 'DL' THEN ID_No ELSE NULL END) AS DL,
MIN(CASE WHEN ID_Type = 'Passport' THEN ID_No ELSE NULL END) AS Passport
FROM yourTable
GROUP BY Name
You could also try using Oracle's built in PIVOT() function if you are running version 11g or later.
Since you mention without using PIVOT function, you can try to
use SQL within group for moving rows onto one line and listagg to display multiple column values in a single column.
In Oracle 11g, we can use the listagg built-in function :
select
deptno,
listagg (ename, ',')
WITHIN GROUP
(ORDER BY ename) enames
FROM
emp
GROUP BY
deptno
Which should give you the below result:
DEPTNO ENAMES
------ --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
You can find all the solution(s) to this problem here:
http://www.dba-oracle.com/t_converting_rows_columns.htm
For Oracle 11g and above, you could use PIVOT.
For pre-11g release, you could use MAX and CASE.
A common misconception, about PIVOT better in terms of performance than the old way of MAX and DECODE. But, under the hood PIVOT is same MAX + CASE. You can check it in 12c where Oracle added EXPAND_SQL_TEXT procedure to DBMS_UTILITY package.
For example,
SQL> variable c clob
SQL> begin
2 dbms_utility.expand_sql_text(Q'[with choice_tbl as (
3 select 'Jones' person,1 choice_nbr,'Yellow' color from dual union all
4 select 'Jones',2,'Green' from dual union all
5 select 'Jones',3,'Blue' from dual union all
6 select 'Smith',1,'Orange' from dual
7 )
8 select *
9 from choice_tbl
10 pivot(
11 max(color)
12 for choice_nbr in (1 choice_nbr1,2 choice_nbr2,3 choice_nbr3)
13 )]',:c);
14 end;
15 /
PL/SQL procedure successfully completed.
Now let's see what Oracle actually does internally:
SQL> set long 100000
SQL> print c
C
--------------------------------------------------------------------------------
SELECT "A1"."PERSON" "PERSON",
"A1"."CHOICE_NBR1" "CHOICE_NBR1",
"A1"."CHOICE_NBR2" "CHOICE_NBR2",
"A1"."CHOICE_NBR3" "CHOICE_NBR3"
FROM (
SELECT "A2"."PERSON" "PERSON",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=1) THEN "A2"."COLOR" END ) "CHOICE_NBR1",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=2) THEN "A2"."COLOR" END ) "CHOICE_NBR2",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=3) THEN "A2"."COLOR" END ) "CHOICE_NBR3"
FROM (
(SELECT 'Jones' "PERSON",1 "CHOICE_NBR",'Yellow' "COLOR" FROM "SYS"."DUAL" "A7") UNION ALL
(SELECT 'Jones' "'JONES'",2 "2",'Green' "'GREEN'" FROM "SYS"."DUAL" "A6") UNION ALL
(SELECT 'Jones' "'JONES'",3 "3",'Blue' "'BLUE'" FROM "SYS"."DUAL" "A5") UNION ALL
(SELECT 'Smith' "'SMITH'",1 "1",'Orange' "'ORANGE'" FROM "SYS"."DUAL" "A4")
) "A2"
GROUP BY "A2"."PERSON"
) "A1"
SQL>
Oracle internally interprets the PIVOT as MAX + CASE.
You're able to create a non-pivot query by understanding what the pivot query will do:
select *
from yourTable
pivot
(
max (id_no)
for (id_type) in ('PAN' as pan, 'DL' as dl, 'Passport' as passport)
)
What the pivot does is GROUP BY all columns not specified inside the PIVOT clause (actually, just the name column), selecting new columns in a subquery fashion based on the aggregations before the FOR clause for each value specified inside the IN clause and discarding those columns specified inside the PIVOT clause.
When I say "subquery fashion" I'm refering to one way to achieve the result got with PIVOT. Actually, I don't know how this works behind the scenes. This subquery fashion would be like this:
select <aggregation>
from <yourTable>
where 1=1
and <FORclauseColumns> = <INclauseValue>
and <subqueryTableColumns> = <PIVOTgroupedColumns>
Now you identified how you can create a query without the PIVOT clause:
select
name,
(select max(id_no) from yourTable where name = t.name and id_type = 'PAN') as pan,
(select max(id_no) from yourTable where name = t.name and id_type = 'DL') as dl,
(select max(id_no) from yourTable where name = t.name and id_type = 'Passport') as passport
from yourTable t
group by name
You can use CTE's to break the data down and then join them back together to get what you want:
WITH NAMES AS (SELECT DISTINCT NAME
FROM YOURTABLE),
PAN AS (SELECT NAME, ID_NO AS PAN
FROM YOURTABLE
WHERE ID_TYPE = 'PAN'),
DL AS (SELECT NAME, ID_NO AS DL
FROM YOURTABLE
WHERE ID_TYPE = 'DL'),
PASSPORT AS (SELECT NAME, ID_NO AS "Passport"
FROM YOURTABLE
WHERE ID_TYPE = 'Passport')
SELECT n.NAME, p.PAN, d.DL, t."Passport"
FROM NAMES n
LEFT OUTER JOIN PAN p
ON p.NAME = n.NAME
LEFT OUTER JOIN DL d
ON d.NAME = p.NAME
LEFT OUTER JOIN PASSPORT t
ON t.NAME = p.NAME'
Replace YOURTABLE with the actual name of your table of interest.
Best of luck.

Check if string variations exists in another string

I need to check if a partial name matches full name. For example:
Partial_Name | Full_Name
--------------------------------------
John,Smith | Smith William John
Eglid,Timothy | Timothy M Eglid
I have no clue how to approach this type of matching.
Another thing is that name and last name may come in the wrong order, making it harder.
I could do something like this, but this only works if names are in the same order and 100% match
decode(LOWER(REGEXP_REPLACE(Partial_Name,'[^a-zA-Z'']','')), LOWER(REGEXP_REPLACE(Full_Name,'[^a-zA-Z'']','')), 'Same', 'Different')
you could use this pattern on the text provided - works for most engines
([^ ,]+),([^ ,]+)(?=.*\b\1\b)(?=.*\b\2\b)
Demo
WITH
/*
tab AS
(
SELECT 'Smith William John' Full_Name, 'John,Smith' Partial_Name FROM dual
UNION ALL SELECT 'Timothy M Eglid', 'Eglid,timothy' FROM dual
UNION ALL SELECT 'Tim M Egli', 'Egli,Tim,M2' FROM dual
UNION ALL SELECT 'Timot M Eg', 'Eg' FROM dual
),
*/
tmp AS (
SELECT Full_Name, Partial_Name,
trim(CASE WHEN instr(Partial_Name, ',') = 0 THEN Partial_Name
ELSE regexp_substr(Partial_Name, '[^,]+', 1, lvl+1)
END) token
FROM tab t CROSS JOIN (SELECT lvl FROM (SELECT LEVEL-1 lvl FROM dual
CONNECT BY LEVEL <= (SELECT MAX(LENGTH(Partial_Name) - LENGTH(REPLACE(Partial_Name, ',')))+1 FROM tab)))
WHERE LENGTH(Partial_Name) - LENGTH(REPLACE(Partial_Name, ',')) >= lvl
)
SELECT Full_Name, Partial_Name
FROM tmp
GROUP BY Full_Name, Partial_Name
HAVING count(DISTINCT token)
= count(DISTINCT CASE WHEN REGEXP_LIKE(Full_Name, token, 'i')
THEN token ELSE NULL END);
In the tmp each partial_name is splitted on tokens (separated by comma)
The resulting query retrieves only those rows which full_name matches all the corresponding tokens.
This query works with the dynamic number of commas in partial_name. If there can be only zero or one commas then the query will be much easier:
SELECT * FROM tab
WHERE instr(Partial_Name, ',') > 0
AND REGEXP_LIKE(full_name, substr(Partial_Name, 1, instr(Partial_Name, ',')-1), 'ix')
AND REGEXP_LIKE(full_name, substr(Partial_Name,instr(Partial_Name, ',')+1), 'ix')
OR instr(Partial_Name, ',') = 0
AND REGEXP_LIKE(full_name, Partial_Name, 'ix');
This is what I ended up doing... Not sure if this is the best approach.
I split partials by comma and check if first name present in full name and last name present in full name. If both are present then match.
CASE
WHEN
instr(trim(lower(Full_Name)),
trim(lower(REGEXP_SUBSTR(Partial_Name, '[^,]+', 1, 1)))) > 0
AND
instr(trim(lower(Full_Name)),
trim(lower(REGEXP_SUBSTR(Partial_Name, '[^,]+', 1, 2)))) > 0
THEN 'Y'
ELSE 'N'
END AS MATCHING_NAMES