Select rows into columns and show a flag in the column - sql

Trying to get an output like the below:
| UserFullName | JAVA | DOTNET | C | HTML5 |
|--------------|--------|--------|--------|--------|
| Anne San | | | | |
| John Khruf | 1 | 1 | | 1 |
| Mary Jane | 1 | | | 1 |
| George Mich | | | | |
This shows the roles of a person. A person could have 0 or N roles. When a person has a role, I am showing a flag, like '1'.
Actually I have 2 blocks of code:
Block #1: The tables and a simple output which generates more than 1 rows per person.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
CREATE TABLE PersonalRoles
(
id int identity primary key,
UserID varchar(100),
RoleID varchar(5),
);
INSERT INTO PersonalRoles
(UserID, RoleID)
VALUES
('John.Khruf', '1'),
('John.Khruf', '2'),
('Mary.Jane', '1'),
('Mary.Jane', '4'),
('John.Khruf', '4');
CREATE TABLE Users
(
UserID varchar(20),
EmployeeType varchar(1),
EmployeeStatus varchar(1),
UserFullName varchar(500),
);
INSERT INTO Users
(UserID, EmployeeType, EmployeeStatus, UserFullName)
VALUES
('John.Khruf', 'E', 'A', 'John Khruf'),
('Mary.Jane', 'E', 'A', 'Mary Jane'),
('Anne.San', 'E', 'A', 'Anne San'),
('George.Mich', 'T', 'A', 'George Mich');
Query 1:
SELECT
A.UserFullName,
B.RoleID
FROM
Users A
LEFT JOIN PersonalRoles B ON B.UserID = A.UserID
WHERE
A.EmployeeStatus = 'A'
ORDER BY
A.EmployeeType ASC,
A.UserFullName ASC
Results:
| UserFullName | RoleID |
|--------------|--------|
| Anne San | (null) |
| John Khruf | 1 |
| John Khruf | 2 |
| John Khruf | 4 |
| Mary Jane | 1 |
| Mary Jane | 4 |
| George Mich | (null) |
Block #2: An attempt to convert the rows into columns to be used in the final result
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
Query 1:
SELECT
*
FROM
(
SELECT CodeID, Description
FROM AvailableRoles
) d
PIVOT
(
MAX(CodeID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Results:
| Java | DOTNET | C | HTML5 |
|--------|--------|-------|--------|
| 1 | 2 | 3 | 4 |
Any help in mixing both blocks to show the top output will be welcome. Thanks.

Another option without PIVOT operator is:
select u.UserFullName,
max(case when a.CodeID='1' then '1' else '' end) JAVA,
max(case when a.CodeID='2' then '1' else '' end) DOTNET,
max(case when a.CodeID='3' then '1' else '' end) C,
max(case when a.CodeID='4' then '1' else '' end) HTML5
from
Users u
LEFT JOIN PersonalRoles p on (u.UserID = p.UserID)
LEFT JOIN AvailableRoles a on (p.RoleID = a.CodeID)
group by u.UserFullName
order by u.UserFullName
SQLFiddle: http://sqlfiddle.com/#!3/630c3/19

You can try this.
SELECT *
FROM
(
select u.userfullname,
case when p.roleid is not null then 1 end as roleid,
a.description
from users u
left join personalroles p
on p.userid = u.userid
left join availableroles a
on a.codeid = p.roleid
) d
PIVOT
(
MAX(roleID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Fiddle

Related

SQL - Multiple records into one line with conditions

I have two tables that look like this
Account Table
| Account Number | Account Name |
| -------------- | -------------- |
| 12345 | Bob Jones |
| 12346 | Jenny Smith |
| 12347 | Mary Frederick |
Email Table
| Account Number | Email Type |Email | IsPrimary |
| ---------------------- | --------------|-----------------------|-----------------|
| 12345 | WORK | BOBSMITH#STACK.COM | 0 |
| 12345 | HOME | BOBSMITH#GMAIL.COM | 0 |
| 12345 | WORK | BOBSMITH#APPLE.COM | 1 |
| 12345 | HOME | BOBSMITH#HOTMAIL.COM | 0 |
| 12346 | HOME | JMSMITH#GMAIL.COM | 1 |
| 12346 | WORK | JMSMITH#DISNEY.COM | 0 |
| 12346 | HOME | JMSMITH#HOTMAIL.COM | 0 |
| 12347 | HOME | MFRED#HOTMAIL.COM | 0 |
I need to end up with results like this:
| Account Number | Account Name | Home Email | Work Email | Alternate Email |
| ------------------------ | ----------------- |--------------------|--------------------|----------------------|
| 12345 | Bob Jones | BOBSMITH#GMAIL.COM | BOBSMITH#APPLE.COM | BOBSMITH#HOTMAIL.COM |
| 12346 | Jenny Smith | JMSMITH#GMAIL.COM | JMSMITH#DISNEY.COM | JMSMITH#HOTMAIL.COM |
| 12347 | Mary Frederick | MFRED#HOTMAIL.COM | | |
I have all possible combinations of emails such as multiple work and no home, no primaries at all, multiple primaries... etc.
I need the logic to be:
Home Email: When type is Home. If there is a primary, choose the first one. If there is no primary, choose the first non-primary home email. Otherwise make it blank
Work Email: Same as above but for work
Alternate Email: Any third email on the list. If there are more than 3, choose home over work.
I have tried a ton of things and nothing has worked well. Here is my top attempt -two things don't work about it. The 'top 1' returns a lot of nulls and the code for alternates isn't always mutually exclusive to the home and work email, especially for people that have no primary checked:
select distinct
A01.AccountNumber,
A01.FirstName,
A01.LastName,
(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'HOME' and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'HOME')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as HomeEmail
,(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'WORK' and A07.EmailType is not null)--and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'WORK')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as WorkEmail
,(
select top 1 (A07.EmailAddress)
from A07_AccountEmails A07
where A07.AccountNumber = A01.AccountNumber
and A07.Active = 1 and A07.UseAsPrimary = 0
) as AlternateEmailMulti
from A01_AccountMaster A01
left outer join A07_AccountEmails A07 on A07.AccountNumber = A01.AccountNumber
where
A01.[Status] = 'A'
It seems a simple sub-query per email address should do the trick? I have added them in an OUTER APPLY to allow the values to be used twice, once in the select, and once to be excluded from the alternate email column.
declare #Account table (Number int, [Name] varchar(64));
declare #Email table (AccountNumber int, [Type] varchar(4), Email varchar(256), IsPrimary bit);
insert into #Account (Number, [Name])
values
(12345, 'Bob Jones'),
(12346, 'Jenny Smith'),
(12347, 'Mary Frederick');
insert into #Email (AccountNumber, [Type], Email, IsPrimary)
values
(12345, 'WORK', 'BOBSMITH#STACK.COM', 0),
(12345, 'HOME', 'BOBSMITH#GMAIL.COM', 0),
(12345, 'WORK', 'BOBSMITH#APPLE.COM', 1),
(12345, 'HOME', 'BOBSMITH#HOTMAIL.COM', 0),
(12346, 'HOME', 'JMSMITH#GMAIL.COM', 1),
(12346, 'WORK', 'JMSMITH#DISNEY.COM', 0),
(12346, 'HOME', 'JMSMITH#HOTMAIL.COM', 0),
(12347, 'HOME', 'MFRED#HOTMAIL.COM', 0);
select A.Number, A.[Name]
, E.Home [Home Email]
, E.Work [Work Email]
, (
select top 1 Email
from #Email
where AccountNumber = A.Number
and Email not in (E.Home, E.Work)
order by case when [Type] = 'HOME' then 1 else 0 end desc
)
from #Account A
outer apply (
select
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'HOME' order by IsPrimary desc),
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'WORK' order by IsPrimary desc)
) E (Home, Work);
Returns your desired results:
Account Number
Account Name
Home Email
Work Email
Alternate Email
12345
Bob Jones
BOBSMITH#HOTMAIL.COM
BOBSMITH#APPLE.COM
BOBSMITH#GMAIL.COM
12346
Jenny Smith
JMSMITH#GMAIL.COM
JMSMITH#DISNEY.COM
JMSMITH#HOTMAIL.COM
12347
Mary Frederick
MFRED#HOTMAIL.COM
NULL
NULL
Note: If you provide your sample data as DDL+DML (as I have here) it makes it much easier for people to assist.
Here is an alternative approach which list all e-mails, primaries first.
We use group_,concat to show all matching entries, order by primary prim inversed by desc and finally a newline separator \n to make a vertical list.
NB : It doesn't print correctly in the terminal because of the newlines.
USE test;
CREATE TABLE user (
acc INT,
nam CHAR(10)
);
INSERT INTO user VALUES ( 1, 'Andrew');
INSERT INTO user VALUES ( 2, 'Bernard');
INSERT INTO user VALUES ( 3, 'Charles');
CREATE TABLE telMail (
acc INT,
email CHAR(25),
type CHAR(5),
prim INT
);
INSERT INTO telMail VALUES(1,'a#w0','Work',0);
INSERT INTO telMail VALUES(1,'a#w1','Work',0);
INSERT INTO telMail VALUES(1,'ap#w2','Work',1);
INSERT INTO telMail VALUES(1,'a#w3','Work',0);
INSERT INTO telMail VALUES(1,'a#h1','Home',0);
INSERT INTO telMail VALUES(1,'a#h2','Home',0);
INSERT INTO telMail VALUES(2,'b#h1','Home',0);
INSERT INTO telMail VALUES(2,'b#w1','Work',0);
INSERT INTO telMail VALUES(2,'b#w2','Work',0);
INSERT INTO telMail VALUES(3,'c#h1','Home',0);
INSERT INTO telMail VALUES(3,'cp#h1','Home',1);
INSERT INTO telMail VALUES(3,'c#w1','Work',0);
SELECT
u.acc 'Account',
u.nam 'Name',
GROUP_CONCAT(DISTINCT w.email ORDER BY w.prim DESC SEPARATOR '\n' ) work_mail,
GROUP_CONCAT(DISTINCT h.email ORDER BY h.prim DESC SEPARATOR '\n' ) home_mail
FROM
(SELECT acc,email,prim FROM telMail WHERE type ='Home') h
JOIN
user u on u.acc=h.acc
JOIN
(SELECT acc,email,prim FROM telMail WHERE type ='Work') w ON u.acc = w.acc
GROUP BY u.acc, u.nam
ORDER BY u.acc;
DROP TABLE user;
DROP TABLE telMail;

How can I choose which column do I refer to?

I have 2 tables with some duplicate columns. I need to join them without picking which columns I want to select:
CREATE TABLE IF NOT EXISTS animals (
id int(6) unsigned NOT NULL,
cond varchar(200) NOT NULL,
animal varchar(200) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
INSERT INTO animals (id, cond, animal) VALUES
('1', 'fat', 'cat'),
('2', 'slim', 'cat'),
('3', 'fat', 'dog'),
('4', 'slim', 'dog'),
('5', 'normal', 'dog');
CREATE TABLE IF NOT EXISTS names (
id int(6) unsigned NOT NULL,
name varchar(200) NOT NULL,
animal varchar(200) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
INSERT INTO names (id, name, animal) VALUES
('1', 'LuLu', 'cat'),
('2', 'DoDo', 'cat'),
('3', 'Jack', 'dog'),
('4', 'Shorty', 'dog'),
('5', 'Stinky', 'dog');
SELECT *
FROM animals AS a
JOIN names as n
ON a.id = n.id;
Result:
| id | cond | animal | id | name | animal |
| --- | ------ | ------ | --- | ------ | ------ |
| 1 | fat | cat | 1 | LuLu | cat |
| 2 | slim | cat | 2 | DoDo | cat |
| 3 | fat | dog | 3 | Jack | dog |
| 4 | slim | dog | 4 | Shorty | dog |
| 5 | normal | dog | 5 | Stinky | dog |
But when I try to make another request from the resulting table like:
SELECT name
FROM
(
SELECT *
FROM animals AS a
JOIN names as n
ON a.id = n.id
) as res_tbl
WHERE name = 'LuLu';
I get:
Query Error: Error: ER_DUP_FIELDNAME: Duplicate column name 'id'
Is there any way of avoiding it except removing duplicate columns from the 1st request?
P.S. in fact I am using PostgreSQL, I create my schema as MySQL because I am more used to it
You have columns with the same name in both tables, which causes ambiguity.
If you just want the name column in the outer query, then select that column only in the subquery:
select name
from (
select n.name
from animals a
inner join names n using (id)
) t
where ...
If you want more columns, then you would typically alias the homonym columns to remove the ambiguity - as for the joining column (here, id), the using() syntax is sufficient. So, for example:
select ...
from (
select id, a.cond, a.animal as animal1, n.name, n.animal as animal2
from animals a
inner join names n using (id)
) t
where ...
You may also select the records themselves, instead of the columns from them, which you can then access in an outer query using the usual record.column syntax;
SELECT a.cond animal_cond,
n.name animal_name
FROM (
SELECT a, n
FROM animals AS a
JOIN names as n
ON a.id = n.id
) t

Select all records of one table that contain two records in another with certain id

I have two tables of 1:m relation. Need to select which People records have both records in Actions table whit id 1 and 2
People
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1 | John | 111111111111 |
+----+------+--------------+
| 3 | Jane | 222222222222 |
+----+------+--------------+
| 4 | Jack | 333333333333 |
+----+------+--------------+
Action
+----+------+------------+
| id | PplId| ActionId |
+----+------+------------+
| 1 | 1 | 1 |
+----+------+------------+
| 2 | 1 | 2 |
+----+------+------------+
| 3 | 2 | 1 |
+----+------+------------+
| 4 | 4 | 2 |
+----+------+------------+
Output
+----+------+--------------+----------
|PplId| name | Phone |ActionId |
+-----+------+-------------+----+-----
| 1 | John | 111111111111| 1 |
+-----+------+-------------+----+-----
| 1 | John | 111111111111| 2 |
+-----+------+-------------+----+-----
Return records of People that have both Have Actionid 1 and Action id 2(Have records in Actions).
Window functions are one method. Assuming actions are not duplicated for a person:
select pa.*
from (select p.*, a.action, count(*) over (partition by p.id) as num_actions
from people p join
action a
on p.id = a.pplid
where a.action in (1, 2)
) pa
where num_actions = 2;
In my opinion, getting two rows with the action detail seems superfluous -- you already know the actions. If you only want the people, then exists comes to mind:
select p.*
from people p
where exists (select 1 from actions where a.pplid = p.id and a.action = 1) and
exists (select 1 from actions where a.pplid = p.id and a.action = 2);
With the right index (actions(pplid, action)), I would expect two exists to be faster than group by.
Try this below query using subquery and join
select a.Pplid, name, phone, actionid from (
select a.pplid as Pplid, name, phone_number as phone
from People P
join Action A on a.pplid= p.id
group by a.pplid, name, phone_number
having count(*)>1 )P
join Action A on a.Pplid= p.Pplid
Try something like this
IF OBJECT_ID('tempdb..#People') IS NOT NULL DROP TABLE #People
CREATE TABLE #People (id INT, name VARCHAR(255), phone_number VARCHAR(50))
INSERT #People
SELECT 1, 'John', '111111111111' UNION ALL
SELECT 3, 'Jane', '222222222222' UNION ALL
SELECT 4, 'Jack', '333333333333'
IF OBJECT_ID('tempdb..#Action') IS NOT NULL DROP TABLE #Action
CREATE TABLE #Action (id INT, PplId INT, ActionId INT)
INSERT #Action
SELECT 1, 1, 1 UNION ALL
SELECT 2, 1, 2 UNION ALL
SELECT 3, 2, 1 UNION ALL
SELECT 4, 4, 2
GO
SELECT p.ID AS PplId
, p.name
, p.phone_number AS Phone
, a.ActionId
FROM #People p
JOIN #Action a
ON p.ID = a.PplId
WHERE p.ID IN ( SELECT PplId
FROM #Action
WHERE ActionId IN (1, 2)
GROUP BY PplId
HAVING COUNT(*) = 2 )
AND a.ActionId IN (1, 2)
GO

SQL - selecting only certain rows if they exist

I have a table that contains Home addresses and Mailing addresses. It looks like this:
ID Name StNum StName City State Zip Type
-- ---- ----- ------ ---- ----- --- ----
1 Joe 1234 Main St Waco TX 76767 HOM
1 Joe 2345 High St Waco TX 76763 MLG
2 Amy 3456 Broad St Athens GA 34622 HOM
3 Mel 987 Front St Cary NC 65331 HOM
3 Mel 1111 Main Ave Hilo HI 99779 MLG
I need to write an SQL statement that will only return the Mailing address (MLG record) if it exists, and if not, will return the Home address (HOM record).
The expected results from this table would be:
ID Name StNum StName City State Zip Type
-- ---- ----- ------ ---- ----- --- ----
1 Joe 2345 High St Waco TX 76763 MLG
2 Amy 3456 Broad St Athens GA 34622 HOM
3 Mel 1111 Main Ave Hilo HI 99779 MLG
Any help that you can provide would be much appreciated! Thanks!
use correlated subquery
select * from
(
select *,case when Type='MLG' then 1 else 0 end as typeval
from tablename
)A where typeval in (select max(case when Type='MLG' then 1 else 0 end) from tablename b
where a.name=b.name)
OR if your DB supports row_number() then u can try below -
select * from
(
select *, row_number() over(partition by name order by case when Type='MLG' then 1 else 0 end desc)
from tablename
)A where rn=1
In case you are using SQL Server, i would solve it with the ROW_NUMBER function.
SELECT ID, Name, StNum, StName, City, State, Zip, Type
FROM (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type DESC) AS Rn
FROM yourtable
)
WHERE Rn = 1
This can be done using a WHERE clause that exclude the ids of the users who have MLG
Schema (MySQL v5.7)
CREATE TABLE test (
`ID` INTEGER,
`Name` VARCHAR(3),
`StNum` INTEGER,
`StName` VARCHAR(8),
`City` VARCHAR(6),
`State` VARCHAR(2),
`Zip` INTEGER,
`Type` VARCHAR(3)
);
INSERT INTO test
(`ID`, `Name`, `StNum`, `StName`, `City`, `State`, `Zip`, `Type`)
VALUES
('1', 'Joe', '1234', 'Main St', 'Waco', 'TX', '76767', 'HOM'),
('1', 'Joe', '2345', 'High St', 'Waco', 'TX', '76763', 'MLG'),
('2', 'Amy', '3456', 'Broad St', 'Athens', 'GA', '34622', 'HOM'),
('3', 'Mel', '987', 'Front St', 'Cary', 'NC', '65331', 'HOM'),
('3', 'Mel', '1111', 'Main Ave', 'Hilo', 'HI', '99779', 'MLG');
Query #1
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t1
WHERE t1.`Type` = 'MLG'
OR t1.id NOT IN
(
SELECT id
FROM test t2
WHERE t2.`Type` = 'MLG'
);
Output :
| id | name | StNum | StName | City | State | Zip | Type |
| --- | ---- | ----- | -------- | ------ | ----- | ----- | ---- |
| 1 | Joe | 2345 | High St | Waco | TX | 76763 | MLG |
| 2 | Amy | 3456 | Broad St | Athens | GA | 34622 | HOM |
| 3 | Mel | 1111 | Main Ave | Hilo | HI | 99779 | MLG |
View on DB Fiddle
Or, my first dumb version :
This can be done using UNION
Schema (MySQL v5.7)
CREATE TABLE test (
`ID` INTEGER,
`Name` VARCHAR(3),
`StNum` INTEGER,
`StName` VARCHAR(8),
`City` VARCHAR(6),
`State` VARCHAR(2),
`Zip` INTEGER,
`Type` VARCHAR(3)
);
INSERT INTO test
(`ID`, `Name`, `StNum`, `StName`, `City`, `State`, `Zip`, `Type`)
VALUES
('1', 'Joe', '1234', 'Main St', 'Waco', 'TX', '76767', 'HOM'),
('1', 'Joe', '2345', 'High St', 'Waco', 'TX', '76763', 'MLG'),
('2', 'Amy', '3456', 'Broad St', 'Athens', 'GA', '34622', 'HOM'),
('3', 'Mel', '987', 'Front St', 'Cary', 'NC', '65331', 'HOM'),
('3', 'Mel', '1111', 'Main Ave', 'Hilo', 'HI', '99779', 'MLG');
Query #1
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t1
WHERE t1.`Type` = 'MLG'
UNION ALL
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t2
WHERE t2.id NOT IN (SELECT id FROM test t3 WHERE t3.`Type` = 'MLG')
ORDER BY id;
Output
| id | name | StNum | StName | City | State | Zip | Type |
| --- | ---- | ----- | -------- | ------ | ----- | ----- | ---- |
| 1 | Joe | 2345 | High St | Waco | TX | 76763 | MLG |
| 2 | Amy | 3456 | Broad St | Athens | GA | 34622 | HOM |
| 3 | Mel | 1111 | Main Ave | Hilo | HI | 99779 | MLG |
View on DB Fiddle
This is a prioritization query. With two values, often the simplest method is union all with not exists (or not in).
That does not generalize well For more values, using row_number() with case is convenient:
select t.*
from (select t.*,
row_number() over (partition by id
order by (case when type = 'MLG' then 1 else 2 end)
) as seqnum
from t
) t
where seqnum = 1;
In your particular case, you could use order by type desc, because the two types happen to be prioritized in reverse alphabetical ordering. However, I recommend using case because the intention is more explicit.

Using Oracle combine three tables to one with PIVOT

I have three Oracle SQL select queries which returns following results.
First select query returns result:
user_id | user_name |
---------|-----------|
1 | user_1 |
2 | user_2 |
3 | user_3 |
4 | user_4 |
second select query returns result:
exam_id | exam_name |
---------|-----------|
1 | exam_1 |
2 | exam_2 |
3 | exam_3 |
and the third select query returns result:
exam_id | user_id | exam_date |
---------|---------|-----------|
1 | 1 | 2017 |
1 | 2 | 2018 |
1 | 3 | 2017 |
2 | 3 | 2018 |
I would like to combine these queries to get result:
user_id | user_name | exam_1 | exam_2 | exam_3 |
---------|-----------|--------|--------|--------|
1 | user_1 | 2017 | | |
2 | user_2 | 2018 | | |
3 | user_3 | 2017 | 2018 | |
4 | user_4 | | | |
I would be grateful for any help?
Thank you #shrek for helping me out here. I managed to create the variable for pivot values but couldn't put the variable in the pivot. So I got help for that here and the final version (for 11g) looks like this:
variable x REFCURSOR
DECLARE
exam_ids VARCHAR2(255);
BEGIN
SELECT
LISTAGG(''''
|| exam_id
|| ''' AS "'
|| exam_name
|| '"',',') WITHIN GROUP(
ORDER BY
exam_id ASC
)
INTO exam_ids
FROM
exam;
OPEN :x FOR 'SELECT
*
FROM
(
SELECT
u.user_id,
u.user_name,
e.exam_id,
eu.exam_date
FROM
users u
LEFT JOIN exam_user eu ON u.user_id = eu.user_id
LEFT JOIN exam e ON e.exam_id = eu.exam_id
ORDER BY
u.user_id
)
PIVOT ( MAX ( exam_date )
FOR exam_id
IN ( ' || EXAM_IDS || ' )
)
ORDER BY
1';
END;
/
print x
So that works in the SQL Developer and SQL*Plus. But not when trying to use database with PHP file. For that I needed to create procedure which then could be called from PHP file. Here is problem when trying to use code above from PHP file and the resolution.
This should get you going -
CREATE TABLE users
(user_id varchar2(9), user_name varchar2(11))
;
INSERT ALL
INTO users (user_id, user_name)
VALUES ('1', 'user_1')
INTO users (user_id, user_name)
VALUES ('2', 'user_2')
INTO users (user_id, user_name)
VALUES ('3', 'user_3')
INTO users (user_id, user_name)
VALUES ('4', 'user_4')
SELECT * FROM dual
;
CREATE TABLE exam
(exam_id varchar2(9), exam_name varchar2(11))
;
INSERT ALL
INTO exam (exam_id, exam_name)
VALUES ('1', 'exam_1')
INTO exam (exam_id, exam_name)
VALUES ('2', 'exam_2')
INTO exam (exam_id, exam_name)
VALUES ('3', 'exam_3')
SELECT * FROM dual
;
CREATE TABLE exam_user
(exam_id varchar2(9), user_id varchar2(9), exam_date varchar2(11))
;
INSERT ALL
INTO exam_user (exam_id, user_id, exam_date)
VALUES ('1', '1', '2017')
INTO exam_user (exam_id, user_id, exam_date)
VALUES ('1', '2', '2018')
INTO exam_user (exam_id, user_id, exam_date)
VALUES ('1', '3', '2017')
INTO exam_user (exam_id, user_id, exam_date)
VALUES ('2', '3', '2018')
SELECT * FROM dual
;
Query -
SELECT * FROM (
SELECT U.USER_ID, U.USER_NAME, E.EXAM_NAME,EU.EXAM_DATE
FROM USERS U, EXAM E, EXAM_USER EU
WHERE U.USER_ID = EU.USER_ID(+)
AND E.EXAM_ID(+) = EU.EXAM_ID
ORDER BY U.USER_ID
)
PIVOT (MAX(EXAM_DATE) FOR EXAM_NAME IN ('exam_1' as exam_1, 'exam_2' as exam_2,'exam_3' as exam_3))
order by 1
;
Output -
USER_ID USER_NAME EXAM_1 EXAM_2 EXAM_3
1 user_1 2017 (null) (null)
2 user_2 2018 (null) (null)
3 user_3 2017 2018 (null)
4 user_4 (null) (null) (null)