Pivot two columns - sql

I have data that looks likes:
stu_id
course_name
staff_name
1
Economics - 3
Kuzma, Brian
1
History
Hulings, Kreg
1
IHS IB Lit of Americ
Duncan, Amy
2
Marine Biology A
Crews, Erin
2
Pre-Calculus
Soderholm, Lodi
2
Environ & Wld Iss
Haberman, Helen
I am trying format it as such using SQL, so that each student's data is all in one row:
stu_id
course1
staff1
course2
staff2
etc ...
1
Economics - 3
Kuzma, Brian
History
Hulings, Kreg
etc ...
2
Marine Biology A
Crews, Erin
Pre-Calculus
Soderholm, Lodi
etc ...
Each student can have up to 6 courses and associated staff names all pivoted to a single row.
The basic query is:
SELECT dtbl_students.student_id stu_id,
course_name,
staff_name
FROM k12intel_dw.ftbl_student_schedules
INNER JOIN k12intel_dw.dtbl_students WITH (nolock)
ON ftbl_student_schedules.student_key = dtbl_students.student_key
INNER JOIN k12intel_dw.dtbl_staff WITH (nolock)
ON ftbl_student_schedules.staff_key = dtbl_staff.staff_key
INNER JOIN k12intel_dw.dtbl_courses WITH (nolock)
ON ftbl_student_schedules.course_key = dtbl_courses.course_key
INNER JOIN k12intel_dw.dtbl_schools WITH (nolock)
ON ftbl_student_schedules.school_key = dtbl_schools.school_key
INNER JOIN k12intel_dw.dtbl_school_dates period_date WITH (nolock)
ON ftbl_student_schedules.school_dates_key =
period_date.school_dates_key
WHERE local_school_year = '2019-2020'
AND local_semester = 3
I am attempting to pivot on course_name and staff_name.
I have managed to UNPIVOT as such:
WITH Courses
AS (SELECT dtbl_students.student_id stu_id,
course_name,
staff_name
FROM K12intel_dw.FTBL_STUDENT_SCHEDULES
INNER JOIN K12intel_dw.DTBL_STUDENTS WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.STUDENT_KEY =
DTBL_STUDENTS.STUDENT_KEY
INNER JOIN K12intel_dw.DTBL_staff WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.staff_KEY =
DTBL_staff.staff_KEY
INNER JOIN K12intel_dw.DTBL_COURSES WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.COURSE_KEY =
DTBL_COURSES.COURSE_KEY
INNER JOIN K12intel_dw.DTBL_SCHOOLS WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.SCHOOL_KEY =
DTBL_SCHOOLS.SCHOOL_KEY
INNER JOIN K12intel_dw.DTBL_SCHOOL_DATES period_date WITH (
Nolock)
ON FTBL_STUDENT_SCHEDULES.SCHOOL_DATES_KEY =
period_date.SCHOOL_DATES_KEY
WHERE local_school_year = '2019-2020'
AND local_semester = 3)
SELECT *
FROM Courses
UNPIVOT ( Course
FOR Value IN ([course_name],
[staff_name]) ) unpiv
Which produces something like:
stu_id
course
value
1
IHS IB Economics - 3
course_name
1
Kuzma, Brian
staff_name
1
IHS IB History of th
course_name
1
Hulings, Kreg
staff_name
I have been experimenting with an unpivot then pivot or two pivots, using all the examples I can find on stack Overflow and elsewhere, but have had no success.

If you have a know or maximum number of pairs AND you want to try the PIVOT
Example
Select *
From (
Select A.stu_id
,B.*
From (
Select *
,RN = row_number() over (partition by stu_id order by course_name)
from YourTable
) A
Cross Apply ( values (concat('course_',RN),course_name)
,(concat('staff_',RN),staff_name)
) B(Item,Value)
) src
Pivot (max(Value) for Item in ([Course_1],[Staff_1],[Course_2],[Staff_2],[Course_3],[Staff_3] )) pvt
Returns

I would suggest row_number() to enumerate the columns, then conditional aggregation:
select
stu_id,
max(case when rn = 1 then course_name end) course1,
max(case when rn = 1 then staff_name end) staff1,
max(case when rn = 2 then course_name end) course2,
max(case when rn = 2 then staff_name end) staff2,
...
max(case when rn = 6 then course_name end) course6,
max(case when rn = 6 then staff_name end) staff6
from (
select t.*, row_number() over(partition by stu_id order by course_name) rn
from mytable t
) t
group by stu_id
This gives you 6 pairs of columns for each stu_id, with the corresponding course and staff names. Courses are sorted alphabetically over the columns. If a student has less than 6 courses, the final columns will be empty.
I am quite unclear on how your query and your sample data relate. This answer is based on the sample data and expected results.

Related

How to use 2 correlated subqueries with Join and Conditional Aggregation

I have 2 queries below for which I want the output to combine.
select distinct [User].UserID, fname, lname, JobTitle
from [User]
join [Order] on [User].UserID = [Order].UserID
UserID
fname
lname
JobTitle
1
John
Smith
Engineer
2
Terry
Doe
Engineer
select [order].UserID,
(
select avg(case when ReturnTypeID = 5 then 1.0 else 0 end) as DefectedRate
from RentalItems
where RentalItems.OrderID = [Order].OrderID
) as DefectedReturnRate
from [Order]
where OrderID in (select OrderID from RentalItems where ReturnTypeID=5)
UserID
DefectReturnRate
1
0.66
2
0.33
Desired Output
UserID
fname
lname
JobTitle
DefectReturnRate
1
John
Smith
Engineer
0.66
2
Terry
Doe
Engineer
0.33
The three tables I have are below:
User: userID, fname, lname, JobTitle
Order: OrderID, UserID
RentalItems: RentalItemsID, OrderID
Given you just appear to need the user information, how about just joining it on?
select U.UserID, U.fname, U.lname, U.JobTitle,
(
select avg(case when ReturnTypeID = 5 then 1.0 else 0 end) as DefectedRate
from RentalItems R
where R.OrderID = o.OrderID
) as DefectedReturnRate
from [Order] O
inner join [User] U on U.UserID = O.UserID
where O.OrderID in (select OrderID from RentalItems where ReturnTypeID = 5);
Note the use of nice short table aliases for clarity.
As an aside, should you find yourself needing to join 2 queries together you do it exactly the same as a table join e.g.
select *
from (
{sub-query 1)
)
inner join (
{sub-query 2)
) on {some condition}
Further to get a list of users who have an order, an exists clause is clearer than a join e.g.
select UserID, fname, lname, JobTitle
from [User] U
where exists (select 1 from [Order] O where U.UserID = O.UserID)

How to Use Count in Update Statement condition?

We have two tables, Bucket and Student. We have to update Bucket_id in Student table having same country and city. But in my Bucket table I have two Bucket Ids against same country and city we can differentiate them by Desc.
When we update Bucket_id in Student table keep in mind when can only assign 70 students to same bucket Id. After 70 Students have been assigned, it will update with another bucket Id
Bucket Table
BucketId Desc Country City
1 SOUTH PAK KHI
2 NORTH PAK KHI
Initially Bucket_Id in Student is null
Expected Result Below: After Update Bucket_Id in Student Table
Student Table
StudentId Country City BucketId
101 PAK KHI 1
102 PAK KHI 1
109 PAK KHI 1
201 PAK KHI 2
I have tried using
update student std set Bucket_Id = (
select bucket_id from Bucket b where b.country=std.country and b.city = std.city and count(std.bucket_id) < 70
);
How can we do that? We are using oracle 11g.
You can assign the buckets using a query:
select s.*, b.bucketid
from (select s.*,
row_number() over (partition by country, city order by studentid) - 1 as seqnum
from students s
) s join
(select b.*,
row_number() over (partition by country, city order by bucketid) - 1 as seqnum
from buckets b
) b
on s.seqnum between b.seqnum * 70 and (b.seqnum + 1) * 70 - 1;
Oracle doesn't have very good support in update for handling updates across tables, so you then need to use merge:
merge into
students s using
(select s.*, b.bucketid
from (select s.*,
row_number() over (partition by country, city order by studentid) - 1 as seqnum
from students s
) s join
(select b.*,
row_number() over (partition by country, city order by bucketid) - 1 as seqnum
from buckets b
) b
on s.seqnum between b.seqnum * 70 and (b.seqnum + 1) * 70 - 1
) ss
on s.studentid = ss.studentid
when matched then update
set s.bucketid = ss.bucketid;
See if this works for you:
update students std set BucketId = (
select min(bucketid) from Bucket b where b.country=std.country and b.city = std.city)
where bucketid is null
and std.STUDENTID in (
SELECT x.STUDENTID FROM
(
select StudentId,
rank() over(partition by country, city order by StudentId) rn
from students
) x where rn <= 3);
You can also try this, if I understood the problem correctly:
MERGE INTO student t
USING (
SELECT
studentid
, CAST(
ROW_NUMBER() OVER(
PARTITION BY country,city
ORDER BY studentid
) / 70 + 0.5
AS NUMBER(9,0)
) AS bucketid
FROM student
) s
ON (t.studentid=s.studentid)
WHEN MATCHED THEN UPDATE
SET bucketid = s.bucketid

How to select varying count of items per colum value?

I work with Postgresql.
I have a sql code
SELECT lp."RegionId", COUNT(w."Id") FROM public.workplace w
GROUP BY lp."RegionId"
that returns to me
RegionId | Count
1 | 3
2 | 12
3 | 5
I have table 'person'. Each person have RegionId.
So i for region 1 i want to select first 3 persons, for region 2 select first 12 persons, for region 3 select first 5 persons.
So how can i use it as subquery to table 'person'?
WITH (SELECT lp."RegionId", COUNT(w."Id") FROM public.workplace w
GROUP BY lp."RegionId") AS pc
SELECT * FROM public.person p
???????
limit pc."Count"
???
Something like:
SELECT p.*
FROM (SELECT *, row_number() OVER (PARTITION BY RegionId ORDER BY PersonId) AS rn
FROM person) AS p
JOIN (SELECT RegionId, count(*) AS cnt
FROM workplace
GROUP BY RegionId) AS r ON p.RegionId = r.RegionId
WHERE p.rn <= r.cnt
ORDER BY p.RegionId, p.PersonId;

Join Tables and Return data in single row different columns with count of join

We have 3 tables in Oracle 11g, need to left join them and return the data in single row different columns with count of the join, Is there any way We can acheive the same.
Example:
Table1: (Employee_Data)
Table2: (Employee_Address)
Table3: (Employee_Role)
Expected Result:
Mack has 2 addresses and 2 roles so Emp_Addr_Count is 2, Emp_Role_Count is 2 and the related data is in same row different column.
Kindly note that EMP_ID is unique in Employee_Data table and Employee_Address and Employee_Role could be multiple or zero for a Employee.
Thanks in Advance.
Try this:
SELECT E.Emp_Id
,E.Emp_Name
,E.Emp_Age
,NVL(MAX(EA.RN),0)Addr_Count
,NVL(MAX(CASE WHEN EA.RN = 1 THEN EA.Emp_Address END),' ')Emp_Address_1
,NVL(MAX(CASE WHEN EA.RN = 1 THEN EA.Emp_City END),' ')Emp_City_1
,NVL(MAX(CASE WHEN EA.RN = 2 THEN EA.Emp_Address END),' ')Emp_Address_2
,NVL(MAX(CASE WHEN EA.RN = 2 THEN EA.Emp_City END),' ')Emp_City_2
,NVL(MAX(ER.RN1),0)Role_Count
,NVL(MAX(CASE WHEN ER.RN1 = 1 THEN ER.Emp_task END),' ')Emp_task_1
,NVL(MAX(CASE WHEN ER.RN1 = 2 THEN ER.Emp_task END),' ')Emp_task_2 FROM Employee_Data E JOIN(
SELECT Emp_Id
,ROW_NUMBER() OVER(PARTITION BY Emp_Id ORDER BY Emp_City desc) RN
,Emp_City
,Emp_Address
FROM Employee_Address
)EA ON EA.Emp_Id = E.Emp_Id left JOIN(
SELECT Emp_Id
,ROW_NUMBER() OVER(PARTITION BY Emp_Id ORDER BY Emp_Task) RN1
,Emp_task
FROM Employee_Role
)ER ON ER.Emp_Id = E.Emp_Id GROUP BY E.Emp_Id,E.Emp_Name,E.Emp_Age
Output:
EMP_ID EMP_NAME EMP_AGE ADDR_COUNT EMP_ADDRESS_1 EMP_CITY_1 EMP_ADDRESS_2 EMP_CITY_2 ROLE_COUNT EMP_TASK_1 EMP_TASK_2
1 MACK 45 2 HOME PARADISE MUM TINDER ONCLAVE DEL 2 Manage Task Resource Manage
2 JACK 30 1 BLUE PLAZA MUM 1 Code
3 ANGEL 27 1 HOME PARADISE MUM 0
You can join them as in the following statement :
WITH t AS
(
SELECT d.*, a.emp_address, a.emp_city, r.emp_task
FROM employee_data d
JOIN employee_address a on ( d.emp_id = a.emp_id )
FULL OUTER JOIN employee_role r on ( d.emp_id = r.emp_id )
)
SELECT emp_id, emp_name, emp_age, count(distinct emp_address) emp_addr_count,
min(emp_address) emp_address_1, max(emp_city) emp_city_1,
decode(min(emp_address),max(emp_address),null,max(emp_address)) emp_address_2,
decode(min(emp_city),max(emp_city),null,min(emp_city)) emp_city_2,
count(distinct emp_task) emp_role_count, min(emp_task) emp_task_1,
decode(min(emp_task),max(emp_task),null,max(emp_task)) emp_task_2
FROM t
GROUP BY emp_id, emp_name, emp_age
ORDER BY emp_id;
SQL Fiddle Demo

left join without duplicate values using MIN()

I have a table_1:
id custno
1 1
2 2
3 3
and a table_2:
id custno qty descr
1 1 10 a
2 1 7 b
3 2 4 c
4 3 7 d
5 1 5 e
6 1 5 f
When I run this query to show the minimum order quantities from every customer:
SELECT DISTINCT table_1.custno,table_2.qty,table_2.descr
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno AND qty = (SELECT MIN(qty) FROM table_2
WHERE table_2.custno = table_1.custno )
Then I get this result:
custno qty descr
1 5 e
1 5 f
2 4 c
3 7 d
Customer 1 appears twice each time with the same minimum qty (& a different description) but I only want to see customer 1 appear once. I don't care if that is the record with 'e' as a description or 'f' as a description.
First of all... I'm not sure why you need to include table_1 in the queries to begin with:
select custno, min(qty) as min_qty
from table_2
group by custno;
But just in case there is other information that you need that wasn't included in the question:
select table_1.custno, ifnull(min(qty),0) as min_qty
from table_1
left outer join table_2
on table_1.custno = table_2.custno
group by table_1.custno;
"Generic" SQL way:
SELECT table_1.custno,table_2.qty,table_2.descr
FROM table_1, table_2
WHERE table_2.id = (SELECT TOP 1 id
FROM table_2
WHERE custno = table_1.custno
ORDER BY qty )
SQL 2008 way (probably faster):
SELECT custno, qty, descr
FROM
(SELECT
custno,
qty,
descr,
ROW_NUMBER() OVER (PARTITION BY custno ORDER BY qty) RowNum
FROM table_2
) A
WHERE RowNum = 1
If you use SQL-Server you could use ROW_NUMBER and a CTE:
WITH CTE AS
(
SELECT table_1.custno,table_2.qty,table_2.descr,
RN = ROW_NUMBER() OVER ( PARTITION BY table_1.custno
Order By table_2.qty ASC)
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno
)
SELECT custno, qty,descr
FROM CTE
WHERE RN = 1
Demolink