Transpose a table using Oracle SQL - sql

I have some data in a table which I want to transpose using SQL. here is the sample data.
create table test_pivot(
Name varchar2(100),
DeptA varchar2(50),
DeptB varchar2(50),
DeptC varchar2(50),
DeptD varchar2(50)
);
insert all
into test_pivot(Name,DeptA,DeptB,DeptC,DeptD)
values('Asfakul','Y',NULL,NULL,NULL)
into test_pivot(Name,DeptA,DeptB,DeptC,DeptD)
values('Debmalya',NULL,'Y',NULL,NULL)
into test_pivot(Name,DeptA,DeptB,DeptC,DeptD)
values('Ranjan',NULL,NULL,'Y',NULL)
into test_pivot(Name,DeptA,DeptB,DeptC,DeptD)
values('santanu',NULL,NULL,NULL,'Y')
select 1 from dual;
I want the data to be displayed like below..
I am having a tough time figuring it out. please let me know.

Here an SELECT statement without PIVOT and UNPIVOT. As you can see, it's far more complex:
select dept,
nvl(max(case when name = 'Asfakul' then dept_val end), 'N') as Asfakul,
nvl(max(case when name = 'Debmalya' then dept_val end), 'N') as Debmalya,
nvl(max(case when name = 'Ranjan' then dept_val end), 'N') as Ranjan,
nvl(max(case when name = 'santanu' then dept_val end), 'N') as santanu
from(select name,
dept,
case when dept = 'depta' then depta
when dept = 'deptb' then deptb
when dept = 'deptc' then deptc
when dept = 'deptd' then deptd
end dept_val
from test_pivot
join(select 'depta' as dept from dual union all
select 'deptb' as dept from dual union all
select 'deptc' as dept from dual union all
select 'deptd' as dept from dual
)
on 1 = 1
)
group
by dept
order
by dept

If your DB version supports pivot and unpivot then you can use the same.
See the below query, I think this should help you..
SELECT *
FROM( SELECT *
FROM test_pivot
UNPIVOT (Check_val FOR DEPT IN (DEPTA, DEPTB, DEPTC, DEPTD))
)
PIVOT(MAX(check_val) FOR NAME IN ('Asfakul' AS Asfakul,
'Debmalya' AS Debmalya,
'Ranjan' AS Ranjan,
'santanu' AS santanu))
ORDER BY dept;

Related

Select data from tables when needed columns are stored as records in a different table

An app is developed where a user picks what data he wants to see in a report. Having data as
ReportDataValues
ID
TableName
ColumnName
1
customer
first_name
2
address
zip_code
Customer
ID
first_name
last_name
address_id
1
joe
powell
1
2
andy
smith
2
Address
ID
street
zip_code
1
main ave.
48521
2
central str.
56851
is it possible using generic SQL mechanisms (PIVOT, UNPIVOT or other way) to select such data from only specified table.column pairs in DataValues table as rows so the query is compatible with SQL Server and Oracle and is not using dynamic execution of generated statements (like EXEC(query) or EXECUTE IMMEDIATE (query) ), so the result would be like
Col1
Col2
joe
48521
andy
56851
Later SQL statement will be used in a SAP Crystal Reports reporting engine.
In Oracle, join the customer and address tables to every row of reportdatavalues and then use a CASE expression to correlate the expected value with the table columns and pivot:
SELECT col1, col2
FROM (
SELECT c.id,
r.id AS value_id,
CASE
WHEN r.tablename = 'customer' AND r.columnname = 'id'
THEN TO_CHAR(c.id)
WHEN r.tablename = 'customer' AND r.columnname = 'first_name'
THEN c.first_name
WHEN r.tablename = 'customer' AND r.columnname = 'last_name'
THEN c.last_name
WHEN r.tablename = 'address' AND r.columnname = 'street'
THEN a.street
WHEN r.tablename = 'address' AND r.columnname = 'zip_code'
THEN TO_CHAR(a.zip_code)
END AS value
FROM customer c
INNER JOIN address a
ON a.id = c.address_id
CROSS JOIN ReportDataValues r
)
PIVOT (
MAX(value) FOR value_id IN (1 AS col1, 2 AS col2)
)
Which, for the sample data:
CREATE TABLE ReportDataValues (ID, TableName, ColumnName) AS
SELECT 1, 'customer', 'first_name' FROM DUAL UNION ALL
SELECT 2, 'address', 'zip_code' FROM DUAL;
CREATE TABLE Customer (ID, first_name, last_name, address_id) AS
SELECT 1, 'joe', 'powell', 1 FROM DUAL UNION ALL
SELECT 2, 'andy', 'smith', 2 FROM DUAL;
CREATE TABLE Address (ID, street, zip_code) AS
SELECT 1, 'main ave.', 48521 FROM DUAL UNION ALL
SELECT 2, 'central str.', 56851 FROM DUAL;
Outputs:
COL1
COL2
joe
48521
andy
56851
fiddle

SQL: Select records where value does not belong to a certain column

I want to select those Supervisors that are not supervising any employee e.g. Sup4
Note: All the supervisors are employee themselves so the are in Employee Column but as the supervisors are not supervised by any one so the corresponding Supervisors Column is null.
Table: EmpData
PK
Employee
Supervisor
SupOrEmpFlag
1
EmpA
Sup1
e
2
Sup1
null
s
3
EmpB
Sup2
e
4
Sup2
null
s
5
EmpC
Sup3
e
6
Sup3
null
s
7
Sup4
null
s
I know a better approach would be to create a separate table for both Employee and Supervisor but I am just curious if there is any approach using join that I am missing.
I have tried following but it returns 0 records.
Executed in Oracle Live SQL:
CREATE TABLE EmpData(
PK number(38) GENERATED ALWAYS as IDENTITY(START with 1 INCREMENT by 1) PRIMARY KEY,
Employee varchar2(100) NOT NULL,
Supervisor varchar2(100),
SupOrEmpFlag varchar2(100) NOT NULL
);
INSERT INTO EmpData (Employee , Supervisor, SupOrEmpFlag)
SELECT 'EmpA', 'Sup1', 'e' FROM dual UNION ALL
SELECT 'Sup1', null, 's' FROM dual UNION ALL
SELECT 'EmpB', 'Sup2', 'e' FROM dual UNION ALL
SELECT 'Sup2', null, 's' FROM dual UNION ALL
SELECT 'EmpC', 'Sup3', 'e' FROM dual UNION ALL
SELECT 'Sup3', null, 's' FROM dual UNION ALL
SELECT 'Sup4', null, 's' FROM dual
SELECT *
FROM EmpData sup
JOIN EmpData emp
on emp.Employee = sup.Supervisor
and sup.SupOrEmpFlag = 's'
JOIN EmpData nemp
on nemp.Employee = emp.Employee
and nemp.Employee <> emp.Employee
One option would be determining through use of hierarchical query such as
SELECT NVL(supervisor,employee) AS supervisor
FROM EmpData e
CONNECT BY PRIOR employee = supervisor
GROUP BY NVL(supervisor,employee)
HAVING MAX(level) = 1
or using a query having a conditional aggregation provided for HAVING clause such as
SELECT NVL(supervisor,employee) AS supervisor
FROM EmpData e
GROUP BY NVL(supervisor,employee)
HAVING MAX(CASE WHEN suporempflag = 's' AND supervisor IS NULL THEN 0 ELSE 1 END) = 0
Demo
SELECT *
FROM EmpData
WHERE Employee NOT IN
(
SELECT
DISTINCT(Supervisor)FROM EmpData
WHERE SupOrEmpFlag = 'e'
)
and SupOrEmpFlag = 's'
SELECT *
FROM Supervisors
WHERE Supervisor NOT IN (SELECT Supervisor FROM Employees)
First you must get list supervisor that in column Supervisor. Then get list of employee that not in the first list and have flag "s".
The query will be like this.
SELECT Employee
FROM EmpData
WHERE Employee NOT IN (SELECT DISTINCT Supervisor FROM EmpData) AND SupOrEmpFlag = "s";
Tyr this.
WITH SupervisorList AS
(
SELECT PK, EMPLOYEE
FROM EmpData
WHERE SupOrEmpFlag = 's'
)
SELECT s.*
FROM SupervisorList S
LEFT JOIN EmpData E
ON S.EMPLOYEE = E.Supervisor
WHERE E.PK IS NULL
You can do it with a hierarchical query without aggregating using:
SELECT *
FROM empdata
WHERE LEVEL = 1
AND CONNECT_BY_ISLEAF = 1
START WITH SupOrEmpFlag = 's'
CONNECT BY PRIOR employee = supervisor;
Which, for the sample data, outputs:
PK
EMPLOYEE
SUPERVISOR
SUPOREMPFLAG
7
Sup4
null
s
db<>fiddle here

Displaying the difference between rows in the same table

I have a table named Employee_audit with following schema,
emp_audit_id
eid
name
salary
1
1
Daniel
1000
2
1
Dani
1000
3
1
Danny
3000
My goal is to write a SQL query which will return in following format, considering the first row also as changed value from null.
columnName
oldValue
newValue
name
null
Daniel
salary
null
1000
name
Daniel
Dani
name
Dani
Danny
salary
1000
3000
I have written the below SQL query,
WITH cte AS
(
SELECT empid,
name,
salary,
rn=ROW_NUMBER()OVER(PARTITION BY empid ORDER BY emp_audit_id)
FROM Employee_audit
)
SELECT oldname=CASE WHEN c1.Name=c2.Name THEN '' ELSE C1.Name END,
newname=CASE WHEN c1.Name=c2.Name THEN '' ELSE C2.Name END,
oldsalary=CASE WHEN c1.salary=c2.salary THEN NULL ELSE C1.salary END,
newsalary=CASE WHEN c1.salary=c2.salary THEN NULL ELSE C2.salary END
FROM cte c1 INNER JOIN cte c2
ON c1.empid=c2.empid AND c2.RN=c1.RN + 1
But it gives the result in following format
oldname
newname
oldsalary
newsalary
Daniel
Dani
null
null
Dani
Danny
1000
3000
Could you please answer me, how can I get the required result.
The lead and lag functions are to help you out.
The "diffs" calculates differences for each column you need to find diff to
with diffs as (
select 'name' colName, emp_audit_id, eid, lag(name, 1, null) over (partition by eid order by emp_audit_id) oldValue, name newValue
from some_table
union all
select 'salary', emp_audit_id, eid, cast(lag(salary, 1, null) over (partition by eid order by emp_audit_id) as varchar), cast(salary as varchar) newValue
from some_table
)
select *
from diffs
where oldValue <> newValue or oldValue is null
order by emp_audit_id, eid
If you give each row a row number in a CTE then join on yourself to the next row you can compare the old and the new values. Unioning the 2 different column names is a bit clunky however, if you needed a more robust solution you might look at pivoting the data.
You also obviously have to convert all values to a common datatype e.g. a string.
declare #Test table (emp_audit_id int, eid int, [name] varchar(32), salary money);
insert into #Test (emp_audit_id, eid, [name], salary)
values
(1, 1, 'Daniel', 1000),
(2, 1, 'Dani', 1000),
(3, 1, 'Danny', 3000);
with cte as (
select emp_audit_id, eid, [name], salary
, row_number() over (partition by eid order by emp_audit_id) rn
from #Test
)
select C.emp_audit_id, 'name' columnName, P.[Name] oldValue, C.[name] newValue
from cte C
left join cte P on P.eid = C.eid and P.rn + 1 = C.rn
where coalesce(C.[name],'') != coalesce(P.[Name],'')
union all
select C.emp_audit_id, 'salary' columnName, convert(varchar(21),P.salary), convert(varchar(21),C.salary)
from cte C
left join cte P on P.eid = C.eid and P.rn + 1 = C.rn
where coalesce(C.salary,0) != coalesce(P.salary,0)
order by C.emp_audit_id, columnName;
Returns:
emp_audit_id
columnName
oldValue
newValue
1
name
NULL
Daniel
1
salary
NULL
1000.00
2
name
Daniel
Dani
3
name
Dani
Danny
3
salary
1000.00
3000.00
I highly encourage you to add DDL+DML (as show above) to all your future questions as it makes it much easier for people to assist.

ORACLE MAX GROUP BY

I am using Oracle (SQL Developer). Please find below the example and an outcome which would like to get (purpose of select is to find out people who submitted project A and have not done any activities in project B yet):
Data table:
CREATE TABLE "XXX"."TABLE1"
( "STATUS" VARCHAR2(20 BYTE),
"PROJECT_NAME" VARCHAR2(20 BYTE),
"VERSION_NUMBER" NUMBER,
"PERSON" VARCHAR2(20 BYTE)
);
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('SUBMITTED','A','0','PETER');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('SUBMITTED','A','0','JOHN');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('SUBMITTED','A','1','JOHN');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('NEW','A','2','JOHN');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('SUBMITTED','A','0','MARY');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('SUBMITTED','B','0','PETER');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('NEW','B','1','PETER');
Insert into XXX.TABLE1 (STATUS,PROJECT_NAME,VERSION_NUMBER,PERSON) values ('SUBMITTED','B','0','JOHN');
Created table should look like this:
TABLE1:
TABLE1.STATUS TABLE1.PROJECT_NAME TABLE1.VERSION_NUMBER TABLE1.PERSON
SUBMITTED A 0 PETER
SUBMITTED A 0 JOHN
SUBMITTED A 1 JOHN
NEW A 2 JOHN
SUBMITTED A 0 MARY
SUBMITTED B 0 PETER
NEW B 1 PETER
SUBMITTED B 0 JOHN
Result what I want get is this:
STATUS PROJECT_NAME VERSION_NUMBER PERSON STATUS_1 PROJECT_NAME_1 VERSION_NUMBER_1 PERSON_1
SUBMITTED A 0 PETER NEW B 1 PETER
SUBMITTED A 1 JOHN SUBMITTED B 0 JOHN
SUBMITTED A 0 MARY
Select which I am using now is:
select t.*,v.*
from TABLE1 t
left outer join ( select u.*
from TABLE1 u
where exists (select max(z.VERSION_NUMBER)
,z.PERSON
,z.PROJECT_NAME
from TABLE1 z
where z.PROJECT_NAME = 'B'
and u.PROJECT_NAME = z.PROJECT_NAME
and u.PERSON = z.PERSON
group by z.PERSON, z.PROJECT_NAME
having u.VERSION_NUMBER = max(z.VERSION_NUMBER))) v
on t.PERSON = v.PERSON
where exists (select max (w.VERSION_NUMBER)
,w.PERSON
,w.PROJECT_NAME
from TABLE1 w
where w.PROJECT_NAME = 'A'
and w.STATUS = 'SUBMITTED'
and t.PROJECT_NAME = w.PROJECT_NAME
and t.PERSON = w.PERSON
group by w.PERSON, w.PROJECT_NAME
having t.VERSION_NUMBER = max (w.VERSION_NUMBER))
QUESTION: What would be best(right) way to write such select (best practice), should I better use Analytic functions or use something else instead of EXISTS?
I think you've over-complicated this...
WITH
project_status (status, project_name, version_number, person)
AS
(SELECT 'SUBMITTED','A','0','PETER' FROM dual UNION ALL
SELECT 'SUBMITTED','A','0','JOHN' FROM dual UNION ALL
SELECT 'SUBMITTED','A','1','JOHN' FROM dual UNION ALL
SELECT 'NEW','A','2','JOHN' FROM dual UNION ALL
SELECT 'SUBMITTED','A','0','MARY' FROM dual UNION ALL
SELECT 'SUBMITTED','B','0','PETER' FROM dual UNION ALL
SELECT 'NEW','B','1','PETER' FROM dual UNION ALL
SELECT 'SUBMITTED','B','0','JOHN' FROM dual
)
SELECT DISTINCT
ps.person
,ps.project_name
,ps.status
FROM
project_status ps
WHERE 1=1
AND ps.project_name = 'A'
AND ps.status = 'SUBMITTED'
AND NOT EXISTS
(SELECT 1
FROM project_status ps2
WHERE ps2.person = ps.person
AND ps2.project_name = 'B'
)
;
purpose of select is to find out people who submitted project A and
have not done any activities in project B yet
If your purpose is just to get the people, then you don't need the complete rows. One method to answer this is to use group by and having:
select t1.person
from "XXX"."TABLE1" t1
group by t1.person
having sum(case when project_name = 'A' and status = 'New' then 1 else 0 end) > 0 and
sum(case when project_name = 'B' then 1 else 0 end) = 0;
If you need the complete rows, then Christian has a reasonable solution.

SQL displaying the specific column

How should I write a query that only shows the inside column values with "YES" ?
Hospital_ID MATERINITY ENT DERMATOLOGY ORTHOPEDICS
1 YES NO NO NO
2 YES YES NO NO
3 YES YES NO YES
The result I'm looking for is Hospital_ID "3":
Hospital_ID MATERINITY ENT ORTHOPEDICS
3 YES YES YES
that won't show the column with value "NO".
Well if you're only looking to exclude the dermatology column when it's NO and you don't care about any of the other columns being NO (unless they're all NO) then it would be:
SELECT Hospital_ID, MATERNITY, ENT, ORTHOPEDICS,
FROM DepartmentTable
WHERE DERMATOLOGY = 'NO' AND (MATERNITY = 'YES' OR ENT = 'YES' OR ORTHOPEDICS = 'YES');
This would only give you the rows where DERMATOLOGY is NO but there is another column with a 'YES' value. I'm assuming you don't want results where all columns are 'NO'.
You're going to get into trouble trying to drop columns, especially when you include multiple hospitals. I would recommend doing the yesses as rows with a union query, then you can crosstab in your report.
SELECT Hospital_ID, 'MATERNITY' Dept FROM myTable t WHERE t.MATERNITY='YES'
UNION SELECT Hospital_ID, 'ENT' Dept FROM myTable t WHERE t.ENT='YES'
UNION SELECT Hospital_ID, 'DERMATOLOGY' Dept FROM myTable t WHERE t.DERMATOLOGY='YES'
UNION SELECT Hospital_ID, 'ORTHOPEDICS' Dept FROM myTable t WHERE t.ORTHOPEDICS='YES'
If you need to filter for hospital id = 3 then do:
SELECT Hospital_ID, Dept FROM (
SELECT Hospital_ID, 'MATERNITY' Dept FROM myTable t WHERE t.MATERNITY='YES'
UNION SELECT Hospital_ID, 'ENT' Dept FROM myTable t WHERE t.ENT='YES'
UNION SELECT Hospital_ID, 'DERMATOLOGY' Dept FROM myTable t WHERE t.DERMATOLOGY='YES'
UNION SELECT Hospital_ID, 'ORTHOPEDICS' Dept FROM myTable t WHERE t.ORTHOPEDICS='YES') x
WHERE Hospital_ID=3
Try
SELECT *
FROM TableName
WHERE MATERINITY = "YES" AND
DERMATOLOGY = "YES" AND
ORTHOPEDICS = "YES"