Oracle SQL - break 1 row into 2 rows - sql

I have the following table:
create table members
(
member_number number(10),
title varchar2(5),
lastname varchar2(30),
personal_email varchar2(20),
work_email varchar2(20)
)
with the data
Insert into MEMBERS
(MEMBER_NUMBER, TITLE, LASTNAME, PERSONAL_EMAIL, WORK_EMAIL)
Values
(11, 'MR', 'Dore', 'personal#email.com', 'work#email.com');
COMMIT;
select member_number, title, lastname,
decode (:pi_email,
'Work', work_email, personal_email) destination
from members
where member_number = 11
and decode (:pi_email,
'Work', work_email, personal_email) is not null
union
select member_number, title, lastname, work_email
from members
where member_number = 11
and :pi_email = 'Both'
and work_email is not null
Question:
Is it possible to re-write the code using 1 select and not using a UNION.
I want to be able to pass pi_email value of 'Work' and select the work_email, alternatively select the personal_email, however if I pass 'Both' I want to select both email addresses in 2 lines/rows.

If I understand correctly, you can unpivot the data and then use a where clause. In Oracle 12C+, you can do this using a lateral join:
select m.member_number, m.title, m.lastname, e.email
from members m cross join lateral
(select 'work' as which, m.work_email as email from dual union all
select 'personal' as which, m.personal_email from dual
) e
where :pi_email in (e.which, 'both');
In earlier versions of Oracle, you can do something similar with union all in the from clause.

How about a CTE with additional column (what in my example, which tells what kind of an e-mail address is that)?
Sample data:
SQL> select * from members;
MEMBER_NUMBER TITLE LASTNAME PERSONAL_EMAIL WORK_EMAIL
------------- ----- ---------- -------------------- --------------------
11 MR Dore personal#email.com work#email.com
Query (runs in SQL*Plus) - fetch work e-mail address:
SQL> set ver off
SQL> with data as
2 (select 'P' what, member_number, title, lastname, personal_email email
3 from members
4 union all
5 select 'W' what, member_number, title, lastname, work_email email
6 from members
7 )
8 select member_number,
9 title,
10 lastname,
11 email
12 from data
13 where member_number = &pi_memnum
14 and (what = '&&pi_email' or '&&pi_email' is null);
Enter value for pi_memnum: 11
Enter value for pi_email: W
MEMBER_NUMBER TITLE LASTNAME EMAIL
------------- ----- ---------- --------------------
11 MR Dore work#email.com
Another example, which fetches both e-mail addresses (what is left NULL):
SQL> undefine pi_email
SQL>
SQL> /
Enter value for pi_memnum: 11
Enter value for pi_email:
MEMBER_NUMBER TITLE LASTNAME EMAIL
------------- ----- ---------- --------------------
11 MR Dore personal#email.com
11 MR Dore work#email.com
SQL>
If you don't use SQL*Plus, the last two lines (i.e. the where clause) would be
where member_number = :pi_memnum
and (what = :pi_email or :pi_email is null);

Related

Using regexp_like in Oracle to match on multiple string conditions

I have a column called Keywords in my Oracle database, Basically Keywords column contains all the data of other columns in particular row separated by _.
Example Table:
NAME PHONE_NUMBER COMPANY ADDRESS ZIPCODE KEYWORD
ABCD 9849523459 MICRO RAJAHMU 532819 ABCD_9849523459_MICRO_RAJAHMU_532819
ABCD 8628738646 INFOS KAKINAD 532775 ABCD_8628738646_INFOS_KAKINAD_532775
ABCD 8473874381 ICUBE RAVULAP 537238 ABCD_8473874381_ICUBE_RAVULAP_537238
Now, How can i get exact string match by using REGEXP_LIKE. When i'm using the below query
SELECT * FROM USER_DATA WHERE 1=1 AND REGEXP_LIKE ('KEYWORD', 'ABCD_MICRO_RAVULAP', 'i'));
It's returning 0 records
My expected output should be
NAME PHONE_NUMBER COMPANY ADDRESS ZIPCODE KEYWORD
ABCD 9849523459 MICRO RAJAHMU 532819 ABCD_9849523459_MICRO_RAJAHMU_532819
ABCD 8473874381 ICUBE RAVULAP 537238 ABCD_8473874381_ICUBE_RAVULAP_537238
I would be very grateful if anyone help me out.
Thanks Inadvance
Something like this?
SQL> with user_data (name, keyword) as
2 (select 'ABCD', 'ABCD_9849523459_MICRO_RAJAHMU_532819' from dual union all
3 select 'DEFG', 'ABCD_8628738646_INFOS_KAKINAD_532775' from dual union all
4 select 'HIJK', 'ABCD_8473874381_ICUBE_RAVULAP_537238' from dual
5 )
6 select *
7 from user_data
8 where regexp_like(keyword, 'ABCD.+MICRO', 'i');
NAME KEYWORD
---- ------------------------------------
ABCD ABCD_9849523459_MICRO_RAJAHMU_532819
SQL>
If you want to search the string for two words in any order, use | operator:
SQL> with user_data (name, keyword) as
2 (select 'ABCD', 'ABCD_9849523459_MICRO_RAJAHMU_532819' from dual union all
3 select 'DEFG', 'ABCD_8628738646_INFOS_KAKINAD_532775' from dual union all
4 select 'HIJK', 'ABCD_8473874381_ICUBE_RAVULAP_537238' from dual union all
5 select 'LMNO', 'MICRO_241241242_ABCD_WHATEVER_241424' from dual
6 )
7 select *
8 from user_data
9 where regexp_like(keyword, '(ABCD).+(MICRO)|(MICRO).+(ABCD)', 'i');
NAME KEYWORD
---- ------------------------------------
ABCD ABCD_9849523459_MICRO_RAJAHMU_532819
LMNO MICRO_241241242_ABCD_WHATEVER_241424
SQL>
However, it isn't practical. Perhaps you should consider Oracle Text feature, then.
SQL> create table
2 user_data (name, keyword) as
3 (select 'ABCD', 'ABCD_9849523459_MICRO_RAJAHMU_532819' from dual union all
4 select 'DEFG', 'ABCD_8628738646_INFOS_KAKINAD_532775' from dual union all
5 select 'HIJK', 'ABCD_8473874381_ICUBE_RAVULAP_537238' from dual union all
6 select 'LMNO', 'MICRO_241241242_ABCD_WHATEVER_241424' from dual
7 );
Table created.
SQL> create index i1_ud on user_data(keyword) indextype is ctxsys.context;
Index created.
SQL> select *
2 from user_data
3 where contains (keyword, '$micro and abcd', 1) > 0;
NAME KEYWORD
---- ------------------------------------
ABCD ABCD_9849523459_MICRO_RAJAHMU_532819
LMNO MICRO_241241242_ABCD_WHATEVER_241424
SQL>
Use IN on the underlying columns rather than trying to parse the composite column:
SELECT *
FROM USER_DATA
WHERE (name, company, address) IN (
('ABCD', 'MICRO', 'RAVULAP'),
('MICRO', 'ABCD', 'RAVULAP')
);
If you want to compare case-insensitively then use UPPER:
SELECT *
FROM USER_DATA
WHERE (UPPER(name), UPPER(company), UPPER(address)) IN (
('ABCD', 'MICRO', 'RAVULAP'),
('MICRO', 'ABCD', 'RAVULAP')
);
If you want to match a single triplet of values to the terms in any order then you can reverse the IN clause:
SELECT *
FROM USER_DATA
WHERE ('ABCD', 'MICRO', 'RAVULAP') IN (
(UPPER(name), UPPER(company), UPPER(address)),
(UPPER(name), UPPER(address), UPPER(company)),
(UPPER(company), UPPER(name), UPPER(address)),
(UPPER(company), UPPER(address), UPPER(name)),
(UPPER(address), UPPER(name), UPPER(company)),
(UPPER(address), UPPER(company), UPPER(name))
);

How can I use SQL Pivot Table to turn my rows of data into columns

In SQL 2008, I need to flatten a table and show extra rows as columns. All I can find are queries with calculations. I just want to show the raw data. The data is like as below (simplified):
ID# Name Name_Type
1 Mary Jane Legal
1 MJ Nickname
1 Smith Maiden
2 John Legal
3 Suzanne Legal
3 Susie Nickname
I want the data to show as:
ID# Legal Nickname Maiden
1 Mary Jane MJ Smith
2 John
3 Suzanne Susie
where nothing shows in the column if there is not a row existing for that column. I'm thinking the Pivot Table method should work.
PIVOT requires you to use an aggregate. See this post for a better explanation of how it works.
CREATE TABLE #MyTable
(
ID# INT
, Name VARCHAR(50)
, Name_Type VARCHAR(50)
);
INSERT INTO #MyTable VALUES
(1, 'Mary Jane', 'Legal')
, (1, 'MJ', 'Nickname')
, (1, 'Smith', 'Maiden')
, (2, 'John', 'Legal')
, (3, 'Suzanne', 'Legal')
, (3, 'Susie', 'Nickname');
SELECT *
FROM
(
SELECT * FROM #MyTable
) AS Names
PIVOT (MAX(NAME)
FOR Name_Type IN ([Legal], [Nickname], [Maiden]))
AS PVT;
DROP TABLE #MyTable;
Try this (replace "new_Table" with your table - name and "ID_" with your id - column):
SELECT ID_ AS rootID, (
SELECT Name
FROM new_table
WHERE Name_type = 'legal'
AND new_table.ID_ = rootID
) AS legal,
(
SELECT Name
FROM new_table
WHERE Name_type = 'Nickname'
AND ID_ = rootID
) AS Nickname,
(
SELECT Name
FROM new_table
WHERE Name_type = 'Maiden'
AND ID_ = rootID
) AS Maiden
FROM new_table
GROUP BY rootID;

ms-access 2010: count duplicate names per household address

I am currently working with a spreadsheet in MS Access 2010 which contains about 130k rows of information about people who voted in a local election recently. Each row has their residential information (street name, number, postcode etc.) and personal information (title, surname, forename, middle name, DOB etc.). Each row represents an individual person rather than a household (therefore in many cases the same residential address appears more than once as more than one person resides in a particular household).
What I want to achieve is basically to create a new field in this dataset called 'count'. I want this field to give me a count of how many different surnames reside at a single address.
Is there an SQL script that will allow me to do this in Access 2010?
+------------------+----------+-------+---------+----------+-------------+
| PROPERTYADDRESS1 | POSTCODE | TITLE | SURNAME | FORENAME | MIDDLE_NAME |
+------------------+----------+-------+---------+----------+-------------+
FAKEADDRESS1 EEE 5GG MR BLOGGS JOE N
FAKEADDRESS2 EEE 5BB MRS BLOGGS SUZANNE P
FAKEADDRESS3 EEE 5RG MS SMITH PAULINE S
FAKEADDRESS4 EEE 4BV DR JONES ANNE D
FAKEADDRESS5 EEE 3AS MR TAYLOR STUART A
The following syntax has got me close so far:
SELECT COUNT(electoral.SURNAME)
FROM electoral
GROUP BY electoral.UPRN
However, instead of returning me all 130k odd rows, it only returns me around 67k rows. Is there anything I can do to the syntax to achieve the same result, but just returning every single row?
Any help is greatly appreciated!
Thanks
You could use something like this:
select *,
count(surname) over (partition by householdName)
from myTable
If you have only one column which contains the name,
ex: Rob Adams
then you can do this to have all the surnames in a different column so it will be easier in the select:
SELECT LEFT('HELLO WORLD',CHARINDEX(' ','HELLO WORLD')-1)
in our example:
select right (surmane, charindex (' ',surname)-1) as surname
example on how to use charindex, left and right here:
http://social.technet.microsoft.com/wiki/contents/articles/17948.t-sql-right-left-substring-and-charindex-functions.aspx
if there are any questions, leave a comment.
EDIT: I edited the query, had a syntax error, please try it again. This works on sql server.
here is an example:
create table #temp (id int, PropertyAddress varchar(50), surname varchar(50), forname varchar(50))
insert into #temp values
(1, 'hiddenBase', 'Adamns' , 'Kara' ),
(2, 'hiddenBase', 'Adamns' , 'Anne' ),
(3, 'hiddenBase', 'Adamns' , 'John' ),
(4, 'QueensResidence', 'Queen' , 'Oliver' ),
(5, 'QueensResidence', 'Queen' , 'Moira' ),
(6, 'superSecretBase', 'Diggle' , 'John' ),
(7, 'NandaParbat', 'Merlin' , 'Malcom' )
select * from #temp
select *,
count (surname) over (partition by PropertyAddress) as CountMembers
from #temp
gives:
1 hiddenBase Adamns Kara 3
2 hiddenBase Adamns Anne 3
3 hiddenBase Adamns John 3
7 NandaParbat Merlin Malcom 1
4 QueensResidence Queen Oliver 2
5 QueensResidence Queen Moira 2
6 superSecretBase Diggle John 1
Your query should look like this:
select *,
count (SURNAME) over (partition by PropertyAddress) as CountFamilyMembers
from electoral
EDIT
If over partition by isn't supported, then I guess you can get to your desired result by using group by
select *,
count (SURNAME) over (partition by PropertyAddress) as CountFamilyMembers
from electoral
group by -- put here the fields in the select (one by one), however you can't write group by *
GROUP BY creates an aggregate query, so it's by design that you get fewer records (one per UPRN).
To get the count for each row in the original table, you can join the table with the aggregate query:
SELECT electoral.*, elCount.NumberOfPeople
FROM electoral
INNER JOIN
(
SELECT UPRN, COUNT(*) AS NumberOfPeople
FROM electoral
GROUP BY UPRN
) AS elCount
ON electoral.UPRN = elCount.UPRN
Given the update I want to post another answer. Try it like this:
create table #temp2 ( PropertyAddress1 varchar(50), POSTCODE varchar(20), TITLE varchar (20),
surname varchar(50), FORENAME varchar(50), MIDDLE_NAME varchar (50) )
insert into #temp2 values
('FAKEADDRESS1', 'EEE 5GG', 'MR', 'BLOGGS', 'JOE', 'N'),
('FAKEADDRESS1', 'EEE 5BB', 'MRS', 'BLOGGS', 'SUZANNE', 'P'),
('FAKEADDRESS2', 'EEE 5RG', 'MS', 'SMITH', 'PAULINE', 'S'),
('FAKEADDRESS3', 'EEE 4BV', 'DR', 'JONES', 'ANNE', 'D'),
('FAKEADDRESS4', 'EEE 3AS', 'MR', 'TAYLOR', 'STUART', 'A')
select PropertyAddress1, surname,count (#temp2.surname) as CountADD
into #countTemp
from #temp2
group by PropertyAddress1, surname
select * from #temp2 t2
left join #countTemp ct
on t2.PropertyAddress1 = ct.PropertyAddress1 and t2.surname = ct.surname
This yields:
PropertyAddress1 POSTCODE TITLE surname FORENAME MIDDLE_NAME PropertyAddress1 surname CountADD
FAKEADDRESS1 EEE 5GG MR BLOGGS JOE N FAKEADDRESS1 BLOGGS 2
FAKEADDRESS1 EEE 5BB MRS BLOGGS SUZANNE P FAKEADDRESS1 BLOGGS 2
FAKEADDRESS2 EEE 5RG MS SMITH PAULINE S FAKEADDRESS2 SMITH 1
FAKEADDRESS3 EEE 4BV DR JONES ANNE D FAKEADDRESS3 JONES 1
FAKEADDRESS4 EEE 3AS MR TAYLOR STUART A FAKEADDRESS4 TAYLOR 1

UPDATE and return some rows twice

I try to update and return rows. The problem is I use a nested select with UNION to get some rows twice and I want to get them returned twice. Example:
Table:
First_name | last_name | ready
-----------+-----------+------
john | doe | false
| smith | false
jane | | false
Query:
With list(name) as (
Select First_name
from table1
where First_name Not null and ready=false
union
Select last_name
from table1
where last_name Not null and ready=false
)
Select * from list
This returns:
John
jane
doe
smith
Now I want to update the rows found by the select and use update ... returning instead. But the update only returns the three affected rows, while I want it to return the rows as the select in the example does. Is there any way?
Rewrite to:
WITH cte AS (
UPDATE table1
SET ready = true
WHERE (first_name IS NOT NULL OR last_name IS NOT NULL)
AND NOT ready
RETURNING first_name, last_name
)
SELECT first_name FROM cte WHERE first_name IS NOT NULL
UNION ALL
SELECT last_name FROM cte WHERE last_name IS NOT NULL;
Same result, just shorter and faster: This query accesses table1 a single time instead of three times like in your original.
(Verify the superior performance with EXPLAIN ANALYZE on a test table.)
UNION ALL like #Clodoaldo already mentioned. UNION would eliminate duplicates, which is substantially slower (and probably wrong here).
with list(name) as (
select first_name
from table1
where first_name is not null and ready=false
union all
select last_name
from table1
where last_name is not null and ready=false
), u as (
update table1
set ready = true
where
(first_name is not null or last_name is not null)
and
not ready
)
select * from list
You need union all to have the four rows. It is is [not] null

SQL - How do i get multiple values from another table to fit in a single row in a select statement?

I'm sure there's a simple answer to this but i just spent the last 3 hours searching google to no avail. So, we have two three tables - courses, students, and courses_students
courses_students contains the primary keys of courses and students and is there to break up the m:m relationship.
My homework wants me to write a query to show all the details of a particular course.. including a list of all students in that course. I tried using every type of join possible but ended up getting multiple rows with course information.
i.e. it should show the details for the course once, and include all students e.g.
courseid coursename student
------------ ---------------- ---------------
1 math john jackson
jack johnson
john smith
2 english jane doe
michael thomas
etc... Please help!
thanks!
p.s. i'm using oracle
SQL doesn't really deal in hierarchical data, it deals in sets. This is something better handled in 2 queries - one that returns the course information, and one that returns the students in the course.
look up user defined aggregate functions.
if you really need to list them all in one column, you can set up an aggregate function, and it will do that for you.
Declare
sql_txt Varchar2(4000);
Rec_cnt Number;
Begin
Select Count(*)
Into Rec_Cnt
From User_Types
Where Type_Name = 'VCARRAY'
And Typecode = 'COLLECTION';
If Rec_Cnt = 0 Then
EXECUTE IMMEDIATE 'CREATE OR REPLACE TYPE vcArray as table of varchar2(32000)';
END IF;
END;
/
CREATE OR REPLACE TYPE comma_list_agr_type as object
(
data vcArray,
static function
ODCIAggregateInitialize(sctx IN OUT comma_list_agr_type )
return number,
member function
ODCIAggregateIterate(self IN OUT comma_list_agr_type ,
value IN varchar2 )
return number,
member function
ODCIAggregateTerminate(self IN comma_list_agr_type,
returnValue OUT varchar2,
flags IN number)
return number,
member function
ODCIAggregateMerge(self IN OUT comma_list_agr_type,
ctx2 IN comma_list_agr_type)
return number
);
/
CREATE OR REPLACE TYPE BODY comma_list_agr_type
is
static function ODCIAggregateInitialize(sctx IN OUT comma_list_agr_type)
return number
is
begin
sctx := comma_list_agr_type( vcArray() );
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self IN OUT comma_list_agr_type,
value IN varchar2 )
return number
is
begin
data.extend;
data(data.count) := value;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self IN comma_list_agr_type,
returnValue OUT varchar2,
flags IN number)
return number
is
l_data varchar2(32000);
begin
for x in ( select column_value from TABLE(data) order by 1 )
loop
l_data := l_data || ',' || x.column_value;
end loop;
returnValue := ltrim(l_data,',');
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self IN OUT comma_list_agr_type,
ctx2 IN comma_list_agr_type)
return number
is
begin -- not really tested ;)
for i in 1 .. ctx2.data.count
loop
data.extend;
data(data.count) := ctx2.data(i);
end loop;
return ODCIConst.Success;
end;
end;
/
CREATE OR REPLACE FUNCTION comma_list(input varchar2 )
RETURN varchar2
PARALLEL_ENABLE AGGREGATE USING comma_list_agr_type;
/
GRANT EXECUTE ON COMMA_LIST to someuser
/
There are several different ways of approaching this. The simplest is presentational: solve it in the front end display. In SQL*Plus that would be the BREAK keyword:
SQL> BREAK ON courseid ON coursename
SQL>
SQL> select c.courseid
2 , c.coursename
3 , s.studentname
4 from courses c
5 join course_students cs
6 on ( cs.courseid = c.courseid )
7 join students s
8 on ( s.studentid = cs.studentid )
9 /
COURSEID COURSENAME STUDENTNAME
---------- ---------- --------------------
1 math john smith
jack jackson
john jackson
2 english michael thomas
jane doe
SQL>
Another approach is to use an embedded cursor:
SQL> select c.courseid
2 , c.coursename
3 , cursor (select s.studentname
4 from course_students cs
5 join students s
6 on ( s.studentid = cs.studentid )
7 where cs.courseid = c.courseid
8 )
9 from courses c
10 /
COURSEID COURSENAME CURSOR(SELECTS.STUDE
---------- ---------- --------------------
1 math CURSOR STATEMENT : 3
CURSOR STATEMENT : 3
STUDENTNAME
--------------------
john smith
john jackson
jack jackson
2 english CURSOR STATEMENT : 3
CURSOR STATEMENT : 3
STUDENTNAME
--------------------
jane doe
michael thomas
SQL>
We can debate whether that truly counts as "a single row" :)
Finally we have string aggregation techniques. There are a number of different ways of slicing this particular cabbage, because - unbelievably - it wasn't until the very latest release that Oracle provided a standard built-in to do it. Because I'm not on 11gR2 I'll use WM_CONCAT() instead of LISTAGG():
SQL> select c.courseid
2 , c.coursename
3 , wm_concat(s.studentname) as studentnames
4 from courses c
5 join course_students cs
6 on ( cs.courseid = c.courseid )
7 join students s
8 on ( s.studentid = cs.studentid )
9 group by c.courseid
10 , c.coursename
11 /
COURSEID COURSENAME STUDENTNAMES
---------- ---------- ---------------------------------------------
1 math john smith,john jackson,jack jackson
2 english jane doe,michael thomas
SQL>
Tim Hall's Oracle-Base site has a round up of all of the string aggreation options. Find out more.
if you simply need the results in a query why not this?
with courses as
(select 'biology' coursename, 1 courseid from dual
union
select 'chemistry' coursename, 2 courseid from dual)
,
students as
(select 'Sally' studentName, 1 studentId from dual
union
select 'Jonny' studentName, 2 studentId from dual
union
select 'Tom' studentName, 3 studentId from dual
union
select 'Jane' studentName, 4 studentId from dual
) ,
courses_students as
(select 1 studentId, 1 courseId from dual
union
select 1 studentId, 2 courseId from dual
union
select 2 studentId, 1 courseId from dual
union
select 3 studentId, 2 courseId from dual
)
select c.courseName ,
cursor(select s.StudentName
from students s
inner join
courses_students cs
on s.studentId = cs.studentId
where cs.courseId = c.courseId) students
from courses c ;
granted there are no types but this'll work.
COURSENAME STUDENTS
---------- --------
biology STUDENTNAME
-----------
Sally
Jonny
chemistry STUDENTNAME
-----------
Sally
Tom
all in one query and literally doing nothing too fancy (just using the CURSOR statement)
if you are using 11gr2 ListAgg works great
Which version of Oracle you are in? By chance if you are using Oracle DB 11g R2, take look at listagg.
SQL> select deptno,
2 listagg( ename, '; ' )
3 within group
4 (order by ename) enames
5 from hr.employees
6 group by deptno
7 order by deptno
8 /
DEPTNO ENAMES
--------- --------------------
10 CLARK; KING; MILLER
20 ADAMS; FORD; JONES;
SCOTT; SMITH
30 ALLEN; BLAKE;
JAMES; MARTIN;
TURNER; WARD
Your case you need to do it for course table. In prior versions you can do with CONNECT BY clause. More details on listagg.
sql suck (really..???!!),
The first thing you should clarify is whether the intention of the question is to present the data in that format (or) the query itself should show the data in this format.
a) if it is just the presentation, take a look at SQLPLUS "break on". What this allows you to do is break on a particular column and not repeat the same values if the value hasn't changed.
b) 1) If it is the query that should output data in this format, then look at the approach suggested by "tanging" above. if you want to explore more options, 2) see the lead and lag functions. you can see the value of a column in the previous row using these functions. If they are the same , you'll display null, otherwise the actual value.
Imp : For options a) and b(2) , you should order the results using the order by clause. (Why..?)
Also, check this link : How do I remove repeated column values from report