Dynamic pivot table for every single row (MySql) - mysql

I have a table Customers like:
ID Type Date Address SSN
RT124 MASTER 12/15/2005 7 Hill st 12345
RT542 MASTER 06/14/2006 7 Hill st 12345
HT457 UNIQUE 10/27/2009 10 PARK WAY 24569
QA987 UNIQUE 08/28/2010 10 PARK WAY 24569
AH825 UNIQUE 10/12/2012 10 PARK WAY 24569
14837 SINGLE 05/05/2010 2 TED ROAD 11111
24579 MARRIED 06/24/2014 2 TED ROAD 11111
What I want is to create a new column +# for every duplicate address and SSN and always the ID #1 should be the Date most recent.
Note: this table only has duplicate rows based on the address and SSN but unique ID and it doesn't require any sum.
So the output should be like this (Click on the image to zoom):
I have done some research and tried some examples but nothing work to get this output.
I will appreciate any help !

You need to enumerate the rows and aggregate. In MySQL (pre V8), it looks like:
select address, ssn,
max(case when rn = 1 then id end) as id1,
max(case when rn = 1 then type end) as type1,
max(case when rn = 1 then date end) as date1,
max(case when rn = 2 then id end) as id2,
max(case when rn = 2 then type end) as type2,
max(case when rn = 2 then date end) as date2
. . .
from (select c.*,
(#rn := if(#as = concat_ws(':', address, ssn), #rn + 1,
if(#as := concat_ws(':', address, ssn), 1, 1)
)
) as rn
from (select c.* from customers c order by address, ssn, date desc) c cross join
(select #as := '', #rn := 0) params
) c
group by address, ssn;
Note that this doesn't repeat address and ssn. That doesn't seem useful, but you can of course repeat those columns in each group.

Is there a limit to the number of times an address can be duplicated? If there is a known limit, you could have a number of left joins for each duplicate. The following would be a solution if you knew there would only ever be 6 or fewer duplicates:
with a as (
select
ID
,type
,date
,address
,SSN
row_number() over(partition by address, SSN order by date desc) as R
from Customers
)
select
a.id ID1
,a.type TYPE1
,a.date DATE1
,a.address ADDRESS1
,a.ssn SSN1
,b.id ID2
,b.type TYPE2
,b.date DATE2
,b.address ADDRESS2
,b.ssn SSN2
,c.id ID3
,c.type TYPE3
,c.date DATE3
,c.address ADDRESS3
,c.ssn SSN3
,d.id ID4
,d.type TYPE4
,d.date DATE4
,d.address ADDRESS4
,d.ssn SSN4
,e.id ID5
,e.type TYPE5
,e.date DATE5
,e.address ADDRESS5
,e.ssn SSN5
,f.id ID6
,f.type TYPE6
,f.date DATE6
,f.address ADDRESS6
,f.ssn SSN6
from a
left join
(select * from a
where r=2
) b
on a.address=b.address and a.ssn=b.ssn
left join
(select * from a
where r=3
) c
on a.address=c.address and a.ssn=c.ssn
left join
(select * from a
where r=4
) d
on a.address=d.address and a.ssn=d.ssn
left join
(select * from a
where r=5
) e
on a.address=e.address and a.ssn=e.ssn
left join
(select * from a
where r=6
) f
on a.address=f.address and a.ssn=f.ssn
where r=1
If you have more than 6, just add another set of columns to the select statement:
,f.id ID6
,f.type TYPE6
,f.date DATE6
,f.address ADDRESS6
,f.ssn SSN6
and a new left join to the from statement:
left join
(select * from a
where r=6
) f
on a.address=f.address and a.ssn=f.ssn

Related

How to use IN in sql query when table has key , values pairs

I am working on a query which is having key value pairs
student
StdId StuName phnNum
1 John 87678
student_meta_data
S.NO field_name field_value StdId
1 college St.Anns 1
2 Address Arizona 1
3 IdNum 321 1
4 Subject Maths 1
5 Marks 90 1
6 Subject Physics 1
7 Marks 80 1
I would like to fetch data from student_meta_data table, for this I had written query like the below,
select
case when student_meta_data.field_name = 'Subject' Then field_value end as subject
case when student_meta_data.field_name ='Marks' Then field_value end as marks
case when student_meta_data.field_name = 'IdNum' Then field_value end as IdNum
from student_meta_data
where student_meta_data.StdId=1
&& student_meta_data.field_name in ('Subject')
for the above query I am fetching records like the below,
subject marks IdNum
null null null
I am expecting to fetch records like below,
subject marks IdNum
Maths 90 321
Physics 80 321
can you one suggest in this. Thanks in advance.
SQL DEMO
SELECT rn,
MAX(subject) as subject,
MAX(case when field_name = 'Marks' Then field_value end) as marks,
MAX(idNum) as idNum
FROM ( SELECT m.*,
#idNum := if(`field_name` = 'IdNum', `field_value`, #idNum) as idNum,
#subject := if(`field_name` = 'Subject', `field_value`, #subject) as subject,
#rn := if (#s = #subject,
#rn,
if(#s := #subject, #rn+1, #rn+1)
) as rn
FROM student_meta_data m
CROSS JOIN (SELECT #idNum := 0, #rn := 0, #subject := '', #s := '' ) as var
ORDER BY `SNO` ) as T
WHERE rn > 0
GROUP BY rn;
OUTPUT
Using variable to track idNum and creating the groups for each subject. First query is just the inner subquery for debug propose, the final is your desire result
Your data model leaves a lot to be desired. Nevertheless, here's something to think about...
SELECT a.field_value subject
, b.field_value marks
FROM
( SELECT x.*
, MIN(y.id) y_id
FROM student_meta_data x
JOIN student_meta_data y
ON y.id > x.id
AND y.field_name = 'marks'
WHERE x.field_name = 'subject'
GROUP
BY x.id
) a
JOIN student_meta_data b
ON b.id = a.y_id;
+---------+-------+
| subject | marks |
+---------+-------+
| Maths | 90 |
| Physics | 80 |
+---------+-------+
I can't stress this enough, but your design needs a change. Normalize it.
Derived from #Juan's answer.
You can do this using user variables and simple filtering on that.
select idnum, subject, marks
from (
select
#idnum := if(field_name='IdNum', field_value, #idnum) as idnum,
#subject := if(field_name='Subject', field_value, #subject) as subject,
#marks := if(field_name='Marks', field_value, #marks) as marks,
m.field_name
from student_meta_data
cross join (
select #idnum := 0,
#subject := null,
#marks := 0
) x
order by sno
) t
where field_name = 'Marks';
I'm filtering based on just Marks field_name because it comes last in the given order and we'll have all the other required values set by then.
Demo

How to copy records (distinct in one column but keep data in other fields) to a new table and assign new id in MySQL

For example, I have one table here:
id, firstname, lastname, age, country
1, john , doe , 40 , usa
2, mary , kay , 30 , uk
3, john , doe , 41 , usa
4, peter , pan , 50 , australia
I would like to copy the rows with distinct(firstname) also with latest id to a new table, but keep the data in other field, also reassign the id, like:
id, firstname, lastname, age, country
1, mary , kay , 30 , uk
2, john , doe , 41 , usa
3, peter , pan , 50 , australia
Please advise how can I do it with MySQL query, thanks!
All you need is to insert with a select MySQL INSERT-SELECT
INSERT INTO new_table ( firstname, lastname, age, country )
SELECT firstname, lastname, age, country
FROM old_table
GROUP BY old_table.firstname
ORDER BY old_table.id DESC
Note: You need auto increment field for new_table(id)for this approach
First find the rows with max id for same firstname in subquery and then use user variables to reassign them the id starts with 1.
Try this if you just want to select it:
select
#rn := #rn + 1 id,
firstname,
lastname,
age,
country
from (
select a.*
from t a
left join t b
on a.firstname = b.firstname and a.id < b.id
where b.firstname is null
) t cross join (select #rn := 0) x order by id;
Demo
You can easily create an insert from this:
insert into some_table(id,firstname,lastname,age,country)
select
#rn := #rn + 1 id,
firstname,
lastname,
age,
country
from (
select a.*
from t a
left join t b
on a.firstname = b.firstname and a.id < b.id
where b.firstname is null
) t cross join (select #rn := 0) x order by id;
If you want to use autoincrement on id in the new table, then simply use:
insert into some_table(firstname,lastname,age,country)
select
a.firstname,
a.lastname,
a.age,
a.country
from t a
left join t b
on a.firstname = b.firstname and a.id < b.id
where b.firstname is null
order by a.id;

Multiple Alias, one Column

I am trying to give multiple Aliases to the same column, basically, i want these two queries to be one:
SELECT name AS singlePeople FROM People
JOIN ID FROM Numbers
ON People.ID=Numbers.ID
WHERE People.isMarried=f;
SELECT name AS marriedPeople FROM People
JOIN ID FROM Numbers
ON People.ID=Numbers.ID
WHERE People.isMarried=t;
I want my results to look like:
singlePeople marriedPeople
------------- --------------
Bob Kelly John SMith
John Adams
Is this sufficient?
SELECT (CASE WHEN p.isMarried THEN 'Married' ELSE 'Single' END) as which,
name
FROM People p JOIN
Numbers n
ON p.ID = n.ID;
If not, you can do this with variables:
select max(case when not ismarried then name end) as single,
max(case when ismarried then name end) as married
from (select name, p.ismarried,
(#rn := if(#i = ismarried, #rn + 1,
if(#i := ismarried, 1, 1)
)
) as seqnum
from people p join
numbers n
on p.id = n.id cross join
(select #i := NULL, #rn := 0) params
order by ismarried
) pn
group by rn;

How to select rows as a column for View in TSQL?

Assume I have 3 tables: Animal, CareTaker, and Apppointment. Schema, with some data like so:
Create Table Animal (Id int identity, Name varchar(25))
Create Table CareTaker(Id int identity, Name varchar(50))
Create Table Appointments(Id int identity, AnimalId int, CareTakerId int, AppointmentDate DateTime, BookingDate DateTime)
Insert into Animal(Name) Values('Ghost'), ('Nymeria'), ('Greywind'), ('Summer')
Insert into CareTaker(Name) Values ('Jon'), ('Arya'), ('Rob'), ('Bran')
Insert into Appointments(AnimalId, CareTakerId, AppointmentDate, BookingDate) Values
(1, 1, GETDATE() + 7, GetDate()), -- Ghost cared by Jon
(1, 2, GETDATE() + 6, GetDate()), -- Ghost cared by Arya
(4, 3, GETDATE() + 8, GetDate()) -- Summer cared by Rob
I want to select only 3 caretakers for each animal as a columns. Something like this:
I don't care about other appointments, just the next three, for each animal. If there aren't three appointments, it can be blank / null.
I'm quite confused about how to do this.
I tried it with Sub queries, something like so:
select Name,
-- Care Taker 1
(Select Top 1 C.Name
From Appointments A
Join CareTaker C on C.Id = A.CareTakerId
Where A.AppointmentDate > GETDATE()
And A.AnimalId = Animal.Id
Order By AppointmentDate) As CareTaker1,
-- Appointment Date 1
(Select Top 1 AppointmentDate
From Appointments
Where AppointmentDate > GETDATE()
And AnimalId = Animal.Id
Order By AppointmentDate) As AppointmentDate1
From Animal
But for the second caretaker, I would have to go second level select on where clause to exclude the id from top 1 (because not sure how else to get second row), something like select top 1 after excluding first row id; where first row id is (select top 1) situtation.
Anyhow, that doesn't look like a great way to do this.
How can I get the desired output please?
You can get all the information in rows using:
select an.name as animal, ct.name as caretaker, a.appointmentdate
from appointments a join
animals an
on a.id = an.animalid join
caretaker c
on a.caretakerid = c.id;
Then, you basically want to pivot this. One method uses the pivot keyword. Another conditional aggregation. I prefer the latter. For either, you need a pivot column, which is provided using row_number():
select animal,
max(case when seqnum = 1 then caretaker end) as caretaker1,
max(case when seqnum = 1 then appointmentdate end) as appointmentdate1,
max(case when seqnum = 2 then caretaker end) as caretaker2,
max(case when seqnum = 2 then appointmentdate end) as appointmentdate2,
max(case when seqnum = 3 then caretaker end) as caretaker3,
max(case when seqnum = 3 then appointmentdate end) as appointmentdate3
from (select an.name as animal, ct.name as caretaker, a.appointmentdate,
row_number() over (partition by an.id order by a.appointmentdate) as seqnum
from appointments a join
animals an
on a.id = an.animalid join
caretaker c
on a.caretakerid = c.id
) a
group by animal;

What's the SQL idiom for zipping — in the functional sense — two queries?

For example, if I have a set of classes and a set of classrooms, and I want to pair the two up with some arbitrary pairing:
> SELECT class_name FROM classes ORDER BY class_name
Calculus
English
History
> SELECT room_name FROM classrooms ORDER BY room_name
Room 101
Room 102
Room 201
I'd like to "zip" them like this:
> SELECT class_name FROM classes ORDER … ZIP SELECT room_name FROM classrooms ORDER …
Calculus | Room 101
English | Room 102
History | Room 201
Currently I'm dealing with MySQL… but possibly — optimistically? — there is a reasonably standards compliant way to do this?
One way to do it in MySql
SELECT c.class_name, r.room_name
FROM
(
SELECT class_name, #n := #n + 1 rnum
FROM classes CROSS JOIN (SELECT #n := 0) i
ORDER BY class_name
) c JOIN
(
SELECT room_name, #m := #m + 1 rnum
FROM classrooms CROSS JOIN (SELECT #m := 0) i
ORDER BY room_name
) r
ON c.rnum = r.rnum
Output:
| CLASS_NAME | ROOM_NAME |
-------------|-----------|
| Calculus | Room 101 |
| English | Room 102 |
| History | Room 201 |
Here is SQLFIddle demo
Same thing in Postgres will look like
SELECT c.class_name, r.room_name
FROM
(
SELECT class_name,
ROW_NUMBER() OVER (ORDER BY class_name) rnum
FROM classes
) c JOIN
(
SELECT room_name,
ROW_NUMBER() OVER (ORDER BY room_name) rnum
FROM classrooms
) r
ON c.rnum = r.rnum
Here is SQLFiddle demo
And in SQLite
SELECT c.class_name, r.room_name
FROM
(
SELECT class_name,
(SELECT COUNT(*)
FROM classes
WHERE c.class_name >= class_name) rnum
FROM classes c
) c JOIN
(
SELECT room_name,
(SELECT COUNT(*)
FROM classrooms
WHERE r.room_name >= room_name) rnum
FROM classrooms r
) r
ON c.rnum = r.rnum
Here is SQLFiddle demo
This is a form of join, but you need to create the join key. Alas, though, this requires a full outer join, because you do not know which list is longer.
So, you can do this by using variables to enumerate the rows and then using union all and group by to get the values:
select max(case when which = 'class' then name end) as class_name,
max(case when which = 'room' then name end) as room_name
from ((SELECT class_name as name, #rnc := #rnc + 1 as rn, 'class' as which
FROM classes cross join
(select #rnc := 0) const
ORDER BY class_name
) union all
(select room_name, #rnr := #rnr + 1 as rn, 'room'
from classrooms cross join
(select #rnr := 0) const
ORDER BY room_name
)
) t
group by rn;