how to get separate value from same column on Oracle SQL - sql

apartment_Table
ID
update_Date
code
address1
address2
city
state_code
zip_code
1
2023/02/15
CO
Apt 320
null
NYC
NY
10012
1
2021/12/03
CO
Apt 105
null
NYC
NY
10012
1
2023/02/15
WK
5th Avenue
null
NYC
NY
10012
2
2023/02/15
CO
Apt 325
null
NYC
NY
10012
2
2022/01/12
CO
Apt 123
null
NYC
NY
10012
2
2023/02/14
WK
4th Avenue
null
NYC
NY
10012
2
2021/02/11
WK
5th Avenue
null
NYC
NY
10012
From the table, WK code is road_name and CO code is apartment number.
update_Date is the latest data when user update in the system. I would like to know How do I get result like below
(latest update_Date WK belong to latest update_Date CO)
ID
tenant_address
1
5th Avenue, Apt 320, NYC, NY, 10012
2
4th Avenue, Apt 325, NYC, NY, 10012
SQL I wrote:
SELECT
View_B_ID AS ViewID,
DECODE(P.address1, NULL, 'Street Not Available, ', SUBSTR(P.address1, 1, 50) || ', ')
|| DECODE(P.address2, NULL, '', SUBSTR(P.address2, 1, 50) || ', ')
|| DECODE(P.CITY, NULL, '', P.CITY || ', ')
|| DECODE(P.STAT_CODE, NULL, '', STAT_CODE || ', ')
|| DECODE(P.ZIP, NULL, '', P.ZIP) AS tenant_address
FROM View_B --assume I get data from View_B
LEFT JOIN (SELECT R.ID,
R.address1,
R.address2,
R.CITY,
R.STAT_CODE,
R.ZIP,
ROW_NUMBER() OVER(PARTITION BY R.ID ORDER BY R.update_Date DESC) AS RN
FROM apartment_Table R
WHERE R.code = 'CO'
) P
ON ViewID = P.ID
AND P.RN = 1
Output:
ID
tenant_address
1
Apt 320, NYC, NY, 10012
2
Apt 325, NYC, NY, 10012
Should I use LISTAGG function ? how to get the correct result ?
please help. thank you so much

You could use max keep dense_rank aggregate function to get the job done as below :
SELECT ID,
REGEXP_REPLACE(MAX(ADDRESS1) /* WK code road_name */
KEEP(DENSE_RANK FIRST ORDER BY
DECODE(CODE, 'WK', 1, 2) ASC,
UPDATE_DATE DESC) || ', ' ||
MAX(ADDRESS1) /* CO code apartment number */
KEEP(DENSE_RANK FIRST ORDER BY
DECODE(CODE, 'CO', 1, 2) ASC,
UPDATE_DATE DESC) || ', ' ||
MAX(ADDRESS2)
KEEP(DENSE_RANK FIRST ORDER BY
DECODE(CODE, 'WK', 1, 2) ASC,
UPDATE_DATE DESC) || ', ' ||
MAX(CITY)
KEEP(DENSE_RANK FIRST ORDER BY
DECODE(CODE, 'WK', 1, 2) ASC,
UPDATE_DATE DESC) || ', ' ||
MAX(STATE_CODE)
KEEP(DENSE_RANK FIRST ORDER BY
DECODE(CODE, 'WK', 1, 2) ASC,
UPDATE_DATE DESC) || ', ' ||
MAX(ZIP_CODE)
KEEP(DENSE_RANK FIRST ORDER BY
DECODE(CODE, 'WK', 1, 2) ASC,
UPDATE_DATE DESC)
, '(,[[:space:]])(,)'
, '\2'
) AS TENANT_ADDRESS
FROM YOUR_TABLE_NAME T
GROUP BY ID;
demo on db<>fiddle
See oracle documentation for more details

You could create a query (tmax) with max dates per ID for both codes 'WK' and 'CO' having them in the same row. Then join your table twice by codes and just concat your tenant's latest address:
SELECT tmax.ID,
twk.ADDRESS1 || ', ' || tco.ADDRESS1 || ', ' || tco.CITY || ', ' || tco.STATE_CODE || ', ' || tco.ZIP_CODE "TENANT_ADDRESS"
FROM ( Select ID,
MAX(CASE WHEN CODE = 'WK' THEN UPD_DATE END) "WK_DATE",
MAX(CASE WHEN CODE = 'CO' THEN UPD_DATE END) "CO_DATE"
From tbl
Group By ID
) tmax
INNER JOIN tbl twk ON(twk.ID = tmax.ID And twk.UPD_DATE = tmax.WK_DATE and twk.CODE = 'WK')
INNER JOIN tbl tco ON(tco.ID = tmax.ID And tco.UPD_DATE = tmax.CO_DATE and tco.CODE = 'CO')
With your sample data:
WITH
tbl (ID, UPD_DATE, CODE, ADDRESS1, ADDRESS2, CITY, STATE_CODE, ZIP_CODE) AS
(
Select 1, To_Date('2023/02/15', 'yyyy/mm/dd'), 'CO', 'Apt 320', Null, 'NYC', 'NY', 10012 From Dual Union All
Select 1, To_Date('2021/12/03', 'yyyy/mm/dd'), 'CO', 'Apt 105', Null, 'NYC', 'NY', 10012 From Dual Union All
Select 1, To_Date('2023/02/15', 'yyyy/mm/dd'), 'WK', '5th Avenue', Null, 'NYC', 'NY', 10012 From Dual Union All
Select 2, To_Date('2023/02/15', 'yyyy/mm/dd'), 'CO', 'Apt 325', Null, 'NYC', 'NY', 10012 From Dual Union All
Select 2, To_Date('2022/01/12', 'yyyy/mm/dd'), 'CO', 'Apt 123', Null, 'NYC', 'NY', 10012 From Dual Union All
Select 2, To_Date('2023/02/14', 'yyyy/mm/dd'), 'WK', '4th Avenue', Null, 'NYC', 'NY', 10012 From Dual Union All
Select 2, To_Date('2021/02/11', 'yyyy/mm/dd'), 'WK', '5th Avenue', Null, 'NYC', 'NY', 10012 From Dual
)
... you should get this as the result
ID TENANT_ADDRESS
---------- --------------------------------------
1 5th Avenue, Apt 320, NYC, NY, 10012
2 4th Avenue, Apt 325, NYC, NY, 10012

Related

Merge rows which has same pattern

I have to combine rows into 1 which has same pattern. I am not sure how to acheive it.
I tried to merge with MAX but since its a string, not sure if there is way to combine.
SELECT ID,MAX(GRP1),MAX(GRP2),MAX(GRP3),MAX(GRP)
FROM GROUP_MAPPING
GROUP BY ID
From table:
To Table
Using the same principle as this answer:
SELECT DISTINCT
id,
grp1,
grp2,
grp3,
grp
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY NOCYCLE
PRIOR id = id
AND grp LIKE '%' || PRIOR grp1 || '%' || PRIOR grp2 || '%' || PRIOR grp3 || '%';
or
SELECT DISTINCT
id,
grp1,
grp2,
grp3,
grp
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY NOCYCLE
PRIOR id = id
AND (PRIOR grp1 = grp1 OR PRIOR grp1 IS NULL)
AND (PRIOR grp2 = grp2 OR PRIOR grp2 IS NULL)
AND (PRIOR grp3 = grp3 OR PRIOR grp3 IS NULL)
Which, for the sample data:
CREATE TABLE table_name (id, grp1, grp2, grp3, grp) AS
SELECT 'DEPT1', 10, 'AA', 'S1', '10AAAS1' FROM DUAL UNION ALL
SELECT 'DEPT1', NULL, 'AA', 'S1', 'AAS1' FROM DUAL UNION ALL
SELECT 'DEPT1', 10, NULL, NULL, '10' FROM DUAL UNION ALL
SELECT 'DEPT1', 10, 'BB', NULL, '10BB' FROM DUAL UNION ALL
SELECT 'DEPT1', NULL, 'BB', NULL, 'BB' FROM DUAL;
Both output:
ID
GRP1
GRP2
GRP3
GRP
DEPT1
10
AA
S1
10AAAS1
DEPT1
10
BB
null
10BB
db<>fiddle here

How to select records which have particular value if not then use different value in SQL?

I want to select some records from a table where phone_type ='PR' if record does not have phone_type ='PR' then use 'CL'
Table:
ID NAME Phone_Type Phone_Number
1 Austin PR 789-100-1000
1 Austin CL 762-100-1009
1 Austin PA 789-100-1000
2 Andrew PR 565-100-1000
2 Andrew CL 312-199-6754
2 Andrew FA 602-100-1000
3 Kathy CL 100-100-1000
3 Kathy PA 105-109-1002
The result should be like this:
ID NAME Phone_Type Phone_Number
1 Austin PR 789-100-1000
2 Andrew PR 565-100-1000
3 Kathy CL 100-100-1000
One of the SQL code I tried:
select id, name,phone_number_combined,case when phone_type = 'CL' then phone_number_combined end
from telephone where phone_type = 'PR'
I am not sure how to achieve this in SQL as I have tried CASE statements, exists and even tried joining the table with itself.
You should to use GROUP BY by and COALESCE:
WITH telephone (id, name, phone_type, phone_number) AS
( SELECT * FROM (VALUES
( 1, 'Austin', 'PR', '789-100-1000'),
( 1, 'Austin', 'CL', '762-100-1009'),
( 2, 'Andrew', 'PR', '565-100-1000'),
( 2, 'Andrew', 'CL', '312-199-6754'),
( 3, 'Kathy' , 'CL', '100-100-1000') ))
select
id as ID,
name as NAME,
COALESCE( max(case when phone_type = 'PR' then 'PR' end),
max(case when phone_type = 'CL' then 'CL' end) ) as PHONE_TYPE,
COALESCE( max(case when phone_type = 'PR' then phone_number end),
max(case when phone_type = 'CL' then phone_number end) ) as PHONE_NUMBER
from telephone
group by id, name
order by id, name
----
Results
ID NAME PHONE_TYPE PHONE_NUMBER
1 Austin PR 789-100-1000
2 Andrew PR 565-100-1000
3 Kathy CL 100-100-1000
Use row_number function.
-- Oracle
with s (id, name, phone_type, phone_number) as (
select 1, 'Austin', 'PR', '789-100-1000' from dual union all
select 1, 'Austin', 'CL', '762-100-1009' from dual union all
select 2, 'Andrew', 'PR', '565-100-1000' from dual union all
select 2, 'Andrew', 'CL', '312-199-6754' from dual union all
select 3, 'Kathy' , 'CL', '100-100-1000' from dual)
select id, name, phone_type, phone_number
from
(select
id, name, phone_type, phone_number,
row_number() over (partition by id order by phone_type desc) rn
from s
)
where rn = 1;
ID NAME PHONE_TYPE PHONE_NUMBER
---------- ------ ---------- ------------
1 Austin PR 789-100-1000
2 Andrew PR 565-100-1000
3 Kathy CL 100-100-1000
3 rows selected.

Concatenating Strings whilst catering for nulls

I have the following script :-
select
siteid
address1,
address2,
address3,
address4,
address5
from tblsites
...which may returning something like :-
siteid address1 address2 address3 address4 address5
123 1 New Street NULL New Town NULL Newvile
456 2 Elm Road NULL NULL New York New York
Is it possible in Oracle to reliably concatenate this data, catering for the nulls, and separating the strings with commas. So, the desired ouput for the data above is :-
siteid address
123 1 New Street, New Town, Newvile
456 2 Elm Road, New York, New York
You can use NVL2 to check whether the address components are non-null:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tblsites ( siteid, address1, address2, address3, address4, address5 ) AS
SELECT 123, '1 New Street', CAST( NULL AS VARCHAR2(50) ), 'New Town', NULL, 'Newvile' FROM DUAL UNION ALL
SELECT 456, '2 Elm Road', NULL, NULL, 'New York', 'New York' FROM DUAL;
Query 1:
SELECT siteid,
RTRIM(
NVL2( address1, address1 || ', ', NULL )
|| NVL2( address2, address2 || ', ', NULL )
|| NVL2( address3, address3 || ', ', NULL )
|| NVL2( address4, address4 || ', ', NULL )
|| NVL2( address5, address5 || ', ', NULL ),
', '
) AS address
FROM tblsites
Results:
| SITEID | ADDRESS |
|--------|---------------------------------|
| 123 | 1 New Street, New Town, Newvile |
| 456 | 2 Elm Road, New York, New York |
SELECT siteid,
REGEXP_REPLACE((address1||','||address2||','||address3||','||address4||','||address5),'[,]+',',') AS address
FROM tblsites
OUTPUT
SITEID ADDRESS
123 1 New Street,New Town,New vile
456 2 Elm Road,New York,New York
DEMO
http://sqlfiddle.com/#!4/414cc/13

How can I output all columns on a group by without putting them in the group by clause?

Suppose I have a table like this:
Name Priority Country SomeData
Paul 1 USA 456
Paul 2 England 7898
Paul 3 Austria 56
Fred 2 Belgium 156
Fred 4 France 8979
Carl 3 Canada 569
Jane 1 Peru 69
I want to do something like this:
select Name, min(Priority), Country, SomeData
from dual
group by Name
But I don't want to group by all the columns.
Result should look like this:
Name Priority Country SomeData
Paul 1 USA 456
Fred 2 Belgium 156
Carl 3 Canada 569
Jane 1 Peru 69
I know it's something simple but I couldn't find anything.
How can I do it?
You can use an inline view (or CTE) to add an extra column ranking the data in the order you want, and then an outer query that only gets the highest rank:
select name, priority, country, somedata
from (
select t.*, dense_rank() over (partition by name order by priority) as rn
from your_table t
)
where rn = 1;
Quick demo with your data in a CTE:
with t(Name, Priority, Country, SomeData) as (
select 'Paul', 1, 'USA', 456 from dual
union all select 'Paul', 2, 'England', 7898 from dual
union all select 'Paul', 3, 'Austria', 56 from dual
union all select 'Fred', 2, 'Belgium', 156 from dual
union all select 'Fred', 4, 'France', 8979 from dual
union all select 'Carl', 3, 'Canada', 569 from dual
union all select 'Jane', 1, 'Peru', 69 from dual
)
select name, priority, country, somedata
from (
select t.*, row_number() over (partition by name order by priority) as rn
from t
)
where rn = 1;
NAME PRIORITY COUNTRY SOMEDATA
---- ---------- ------- ----------
Carl 3 Canada 569
Fred 2 Belgium 156
Jane 1 Peru 69
Paul 1 USA 456
or if you want to stick with grouping you can use keep dense_rank first syntax:
with your_table (name, priority, country, somedata) as (
select 'Paul', 1, 'USA', 456 from dual
union all select 'Paul', 2, 'England', 7898 from dual
union all select 'Paul', 3, 'Austria', 56 from dual
union all select 'Fred', 2, 'Belgium', 156 from dual
union all select 'Fred', 4, 'France', 8979 from dual
union all select 'Carl', 3, 'Canada', 569 from dual
union all select 'Jane', 1, 'Peru', 69 from dual
)
select name,
min(priority) as priority,
min(country) keep (dense_rank first order by priority) as country,
min(somedata) keep (dense_rank first order by priority) as somedata
from your_table
group by name;
NAME PRIORITY COUNTRY SOMEDATA
---- ---------- ------- ----------
Carl 3 Canada 569
Fred 2 Belgium 156
Jane 1 Peru 69
Paul 1 USA 456

How to split NAME into 3 different fields FIRST_NAME, MIDDLE_NAME & LAST_NAME when there is a space

How to split NAME into 3 different fields FIRST_NAME, MIDDLE_NAME & LAST_NAME.
Currently the NAME filed is in the following format
[FIRST_NAME || ' ' || MIDDLE_NAME|| ' ' ||LAST_NAME]. To be more clear suppose a person name is
FIRST_NAME: SHERRY
MIDDLE_NAME: L
LAST_NAME : CLEAVES
then the NAME field has "SHERRY L CLEAVES", I want it in 3 different fields. Also, How do I handle if there are only two names?
some of the sample data as follows:
('William Sears',
'PETER E LABBE',
'Edith Roberts',
'SHERRY L CLEAVES',
'Sharon Matthes',
'TAMMY L PELLETIER',
'STACIE M KINER',
'MICHAEL C THOMAS',
'CHESTER R DAVIS',
'MICHAEL D HUTCHINSON',
'Paul Mikkelsen'
)
Here's one way to do it:
with data_qry
as
(
select 'William Sears' as name from dual union all
select 'PETER E LABBE' as name from dual union all
select 'Edith Roberts' as name from dual union all
select 'SHERRY L CLEAVES' as name from dual union all
select 'Sharon Matthes' as name from dual union all
select 'TAMMY L PELLETIER' as name from dual union all
select 'STACIE M KINER' as name from dual union all
select 'MICHAEL C THOMAS' as name from dual union all
select 'CHESTER R DAVIS' as name from dual union all
select 'MICHAEL D HUTCHINSON' as name from dual union all
select 'Paul Mikkelsen' as name from dual
)
select name
, substr(name, 1, instr(name, ' ', 1)) as first
, case when instr(name, ' ', 1, 2) > 0 then substr(name, instr(name, ' ', 1, 1) + 1, instr(name, ' ', 1, 2) - instr(name, ' ', 1, 1) - 1) else null end as middle
, case when instr(name, ' ', 1, 2) > 0 then substr(name, instr(name, ' ', 1, 2) + 1) else substr(name, instr(name, ' ', 1, 1) + 1) end as last
from data_qry
Output:
NAME FIRST MIDDLE LAST
William Sears William Sears
PETER E LABBE PETER E LABBE
Edith Roberts Edith Roberts
SHERRY L CLEAVES SHERRY L CLEAVES
Sharon Matthes Sharon Matthes
TAMMY L PELLETIER TAMMY L PELLETIER
STACIE M KINER STACIE M KINER
MICHAEL C THOMAS MICHAEL C THOMAS
CHESTER R DAVIS CHESTER R DAVIS
MICHAEL D HUTCHINSON MICHAEL D HUTCHINSON
Paul Mikkelsen Paul Mikkelsen
You could do something like this. I took care of a few possibilities, like an arbitrary number of spaces between first and last name (no middle initial) or between first name and initial, or between initial and last name. Also multi-letter initials. There will still be plenty of other, unanticipated problems, like people with two first names or two last names, etc.
with input_strings ( str ) as (
select 'William Sears' from dual union all
select 'PETER E LABBE' from dual union all
select 'Edith Roberts' from dual union all
select 'SHERRY L CLEAVES' from dual union all
select 'Sharon Matthes' from dual union all
select 'TAMMY L PELLETIER' from dual union all
select 'STACIE M KINER' from dual union all
select 'MICHAEL C THOMAS' from dual union all
select 'CHESTER R DAVIS' from dual union all
select 'MICHAEL D HUTCHINSON' from dual union all
select 'Paul Mikkelsen' from dual
)
select initcap(substr(str, 1, instr(str, ' ') - 1)) as first_name,
upper(trim(substr(str, instr(str, ' '),
instr(str, ' ', -1) - instr(str, ' ')))) as mid_in,
initcap(substr(str, instr(str, ' ', -1) + 1)) as last_name
from input_strings;
FIRST_NAME MID_IN LAST_NAME
------------------------------ ------ ------------------------------
William Sears
Peter E Labbe
Edith Roberts
Sherry L Cleaves
Sharon Matthes
Tammy L Pelletier
Stacie M Kiner
Michael C Thomas
Chester R Davis
Michael D Hutchinson
Paul Mikkelsen
11 rows selected.
Hey don't forget regular expressions! Assuming your (really too simple for names) given specs for names which assume a space for a delimiter, and the fact you are not dealing with the usual name issues, this is really just parsing a delimited string which can be done with a regex that can handle NULL list elements (the middle initial):
SQL> with tbl(name) as (
select 'William Sears' from dual union all
select 'PETER E LABBE' from dual union all
select 'Edith Roberts' from dual union all
select 'SHERRY L CLEAVES' from dual union all
select 'Sharon Matthes' from dual union all
select 'TAMMY L PELLETIER' from dual union all
select 'STACIE M KINER' from dual union all
select 'MICHAEL C THOMAS' from dual union all
select 'CHESTER R DAVIS' from dual union all
select 'MICHAEL D HUTCHINSON' from dual union all
select 'Paul Mikkelsen' from dual
)
select regexp_substr(name, '(.*?)( |$)', 1, 1, NULL, 1) first_name,
regexp_substr(name, '(.*?)( |$)', 1, 2, NULL, 1) mid_init,
regexp_substr(name, '(.*?)( |$)', 1, 3, NULL, 1) last_name
from tbl;
FIRST_NAME MID_INIT LAST_NAME
-------------------- -------------------- --------------------
William Sears
PETER E LABBE
Edith Roberts
SHERRY L CLEAVES
Sharon Matthes
TAMMY L PELLETIER
STACIE M KINER
MICHAEL C THOMAS
CHESTER R DAVIS
MICHAEL D HUTCHINSON
Paul Mikkelsen
11 rows selected.
SQL>
Yes, it's been said a million times but I'll save you the trouble: substr()/instr() is faster but I find the nested substr()/instr() syntax more ghastly than the ghastly regex syntax. I'm probably in the minority. Flame away anyway, you know you can't help yourself! :-)