MySQL query to convert normalized data into single row - mysql

I'm trying to create a query to format content from normalized tables into a single row.
What I would like is something like this with a single row for each contact:
Name Mobile Office Home
--------------- ----------- ---------- ----------
Fred Flintstone 123-456-7890 234-567-8901 789-012-3456
Barney Rubble 456-789-0123 678-901-2345
Wilma Flintstone 567-890-1234 789-012-3456
What I am getting from my latest query is this, with multiple rows per contact:
Name Phone Phone_Type
--------------- ------------ -----------
Fred Flintstone 123-456-7890 Mobile
Fred Flintstone 234-567-8901 Office
Fred Flintstone 789-012-3456 Home
Barney Rubble 456-789-0123 Mobile
Barney Rubble 678-901-2345 Home
Wilma Flintstone 567-890-1234 Mobile
Wilma Flintstone 789-012-3456 Home
Here are the tables involved (simplified):
contacts
----------
contact_id
name
link_contact_phonenumbers
-------------------------
contact_id
phone_number_id
phone_numbers
-------------
phone_number_id
phone_number
type_id
ref_phone_types
---------------
type_id
name
Here is what I have so far:
SELECT
cn.name as Name,
concat( left(ph.phone_number,3) , "-" , mid(ph.phone_number,4,3) , "-", right(ph.phone_number,4)) as Phone,
pt.name as Phone_Type
FROM
contacts cn
LEFT JOIN link_contact_phonenumbers lp ON lp.contact_id = cn.contact_id
LEFT JOIN phone_numbers ph ON ph.phone_number_id = lp.phone_number_id
LEFT JOIN ref_phone_types pt ON pt.type_id = ph.type_id
I looked at using GROUP_CONCAT() function, but that pulls all the content into a single column. I need them to go into their own columns.
I've been looking into subqueries combined with IF() but haven't figured it out yet.
Is there a way to do this in pure SQL?

It sounds like you can accomplish what you are looking for with a few joins:
Select a.name, c.phone_number, d.phone_number, e.phone_number from contacts a
left join link_contact_phonenumbers b on a.contact_id = b.contact_id
left join phone_numbers c on b.phone_number_id = c.phone_number_id and c.type_id = "whatever id is mobile"
left join phone_numbers d on b.phone_number_id = d.phone_number_id and d.type_id = "whatever id is office"
left join phone_numbers e on b.phone_number_id = e.phone_number_id and e.type_id = "whatever id is home"
I do not know if this is the most efficient way, I also don't have a database to test with right now so it might be off, but it should point you in the right direction. Also might have to group by a.name in case the first join adds multiple rows.

Here is one solution for others that will find this in the future:
SELECT
cn.name as Name,
MAX(CASE WHEN pt.type_id = '1' THEN ph.phone_number ELSE NULL END) as Mobile,
MAX(CASE WHEN pt.type_id = '2' THEN ph.phone_number ELSE NULL END) as Office,
MAX(CASE WHEN pt.type_id = '3' THEN ph.phone_number ELSE NULL END) as Home
FROM
contacts cn
LEFT JOIN link_contact_phonenumbers lp ON lp.contact_id = cn.contact_id
LEFT JOIN phone_numbers ph ON ph.phone_number_id = lp.phone_number_id
LEFT JOIN ref_phone_types pt ON pt.type_id = ph.type_id
GROUP BY cn.contact_id
Found this solution here: How to Denormalize a Normalized Table Using SQL
I'm not sure if this is the most efficient, but it works.

Related

SQL Query to Return only one row per customer

The database tables I am working with are as the following:
Type_Telephone
ID_Type_Telephone (PK)
Description
0
LandLine
1
Cellular
2
Telecopier
Telephone
ID_Telephone (PK)
ID_Client (FK)
ID_Type_Telephone (FK)
Numero
100
201
0
514-555-0165
101
201
1
514-555-0155
102
202
1
514-555-0176
103
200
0
514-555-0164
104
200
1
514-555-0119
Client
ID_Client (PK)
Nom
Prenom
200
Bertrand
Antoine
201
Legault
Claude
202
Leonard
Sylvie
I would like to write a SQL query that lists all customers' Cellular and Landline telephones. I would like to have only one row of results per customer such as:
Nom
Prenom
Landline
Cellular
Bertrand
Antoine
514-555-0164
514-555-0119
Legault
Claude
514-555-0165
514-555-0155
Leonard
Sylvie
514-555-0176
Any advice how to generate this ? Thank you !
This requires a pivot query, something like this:
SELECT
c.ID_Client,
c.Nom,
c.Prenom,
MAX(CASE WHEN tt.Description = 'LandLine' THEN t.Numero END) AS Landline,
MAX(CASE WHEN tt.Description = 'Cellular' THEN t.Numero END) AS Cellular
FROM Client c
LEFT JOIN Telephone t
ON t.ID_Client = c.ID_Client
LEFT JOIN Type_Telephone tt
ON tt.ID_Type_Telephone = t.ID_Type_Telephone
GROUP BY
c.ID_Client,
c.Nom,
c.Prenom;
If you also want to include possible telecopier numbers, then add another max of CASE expression to the above query.
SELECT c.nom,
c.prenom,
t0.numero landline,
t1.numero cellular
FROM Client c
LEFT JOIN Telephone t0 ON c.id = t0.ID_Client AND t0.ID_Type_Telephone = 0
LEFT JOIN Telephone t1 ON c.id = t1.ID_Client AND t1.ID_Type_Telephone = 1
If you need one more column for Telecopier then add one more table copy.
The query assumes that Telephone (ID_Client, ID_Type_Telephone) is defined as UNIQUE. If not then
SELECT c.nom,
c.prenom,
GROUP_CONCAT(t0.numero) landline,
GROUP_CONCAT(t1.numero) cellular
FROM Client c
LEFT JOIN Telephone t0 ON c.id = t0.ID_Client AND t0.ID_Type_Telephone = 0
LEFT JOIN Telephone t1 ON c.id = t1.ID_Client AND t1.ID_Type_Telephone = 1
GROUP BY 1, 2

write single SQLquery if possible?

There are three tables (column names are in brackets):
1-user_table (user_id, social_site)
value in social_site -> fb, whatsapp, wechat
2-booking_confirm (booking_id)
3-payment_table (order_id, payment_status)
payment_status = "success" or "fail"
no of user | social site | no of payment successful
34 | fb | 10
"no of user": find count(user_id) with respect to "fb" and "payment" (when order_id = booking_id and payment_status="success")
There's no connection between USER_TABLE (your first table) and another two tables, so the following query certainly won't work, but might give you an idea of how to do it (once you manage to fix what's wrong).
select count(*)
from user_table u join ??? on ??? --> what goes here?
--
booking_confirm b join payment_table p on b.booking_id = p.order_id
where u.social_site = 'fb'
and p.payment_status = 'success'

MYSql search perfomance problems

account
act_id | act_name | grp_id | grp_id_2
2 | test | 4 | 10
promotion
pml_id | act_id | grp_id
2 | 2 | null
3 | null | 4
4 | null | 10
I have two tables, shown above (trimmed down). Account has about 15000 records, promotion about 20000.
Customer basically wants it so that they could search for an account name, say 'test'. And it would show the promotions 2, 3 and 4.
Promotion 3 would show because the account 'test' has grp_id = 4.
Promotion 4 would show because account 'test' has grp_id_2 = 10.
I originally did this with a couple of joins
SELECT pml_id FROM promotion
LEFT JOIN account AS account1 ON promotion.act_id = account1.act_id
LEFT JOIN account AS account2 ON promotion.grp_id = account2.grp_id
LEFT JOIN account AS account3 ON promotion.grp_id = account3.grp_id_2
WHERE account1.act_name LIKE 'test%' or account2.act_name LIKE 'test%'
or account3.act_name LIKE 'test%'
GROUP BY pml_id
The problem with this is this ended up taking a long time when I started having to join 5 times to the account table. It also gave me about 10000000 records (without the group by). Most of the promotions use a grp_id, rarely do they use an act_id.
Is there any way to search the act_name column quickly in this scenario? Without having to do so many joins?
I have single indexes on act_id, pml_id, grp_id, grp_id_2
Note: This is part of a query where the user may not search by account. I.E the where clause may not always be there
Use an INNER JOIN instead to avoid scanning the entire table :
SELECT p.pml_id
FROM account a
INNER JOIN promotion p
ON (p.act_id = a.act_id OR p.grp_id = a.grp_id OR p.grp_id = a.grp_id_2)
WHERE a.act_name LIKE "test%";
Is this any faster?
SELECT pml_id FROM promotion p
LEFT JOIN account a
ON (p.act_id = a.act_id OR p.grp_id = a.grp_id OR p.grp_id = a.grp_id_2)
WHERE a.act_name LIKE "test%";
Try this one as well, if you have an index on act_id, this should be quite a bit faster:
SELECT * FROM promotion p
LEFT JOIN account a
ON (p.act_id = a.act_id OR p.grp_id = a.grp_id OR p.grp_id = a.grp_id_2)
WHERE a.act_id IN (
SELECT act_id FROM account WHERE act_name LIKE "test%"
)

Mysql joins headache

I have this database. It does not contain all the tables, but for my problem what is included in the picture is enough.
The database is designed for transportation companies.
So, I have a booking. Any booking can have multiple addresses. These types are (routing_type: pick-up, stop, drop-off). My addresses are either a street address or an airport.
Inside routing table is use either the address_id or the airport_id to tell which type the routing is. If airport_id has a number than address_id is NULL.
In my booking_routing table I have as many entries as there are routing types for a specific booking. If I have 3 addresses for a specific booking, I will have 3 entries in booking_routing (maybe Pick-up, then Stop and then Drop-off). Usually there are only two addresses necessary, pick-up an drop-off.
PROBLEM: I try to create a query in which I will have displayed my Pick-up and Drop-off point on the same line with other fields from the booking.
Example:
Booking_ID Account_ID ... Pick-Up Drop-Off
121 32 1 Main St IAD, AA-322
Right now I only managed to get them using LEFT JOIN on two lines.
Like this:
Booking_ID Account_ID ... Address_Type Address
121 32 Pick_up 1 Main St
121 32 Drop-Off IAD, AA-322
SELECT
b.booking_id as ID, a.account_id as Acc_ID,
MAX(CASE WHEN br.routing_type_id = 1 THEN -- 1=pick-up
Concat(ad.address_line1, " ", api.code, " ", aii.code, "-", ap.flight_number) END) as Pick_up,
MAX(CASE WHEN br.routing_type_id = 4 THEN -- 4=drop-off
Concat(ad.address_line1, " ", api.code, " ", aii.code, "-", ap.flight_number) END) as Drop_off
FROM booking b
left JOIN account a ON b.account_id = a.account_id
left join booking_routing br ON b.booking_id = br.booking_id
left join routing r ON br.routing_id = r.routing_id
left join routing_type rt ON br.routing_type_id = rt.routing_type_id
left join address ad ON r.address_id = ad.address_id
left join airport ap ON r.airport_id = ap.airport_id
left join airport_info api ON ap.airport_info_id = api.airport_info_id
left join airline_info aii ON ap.airline_info_id = aii.airline_info_id
group by b.booking_id, a.account_id;
You can achieve this by using MAX and CASE:
SELECT Booking_Id,
Account_Id,
MAX(CASE WHEN Address_Type = 'Pick_up' THEN Address END) Pick_Up,
MAX(CASE WHEN Address_Type = 'Drop-Off' THEN Address END) Drop_Off
FROM YourTables...
GROUP BY Booking_Id, Account_Id
SQL Fiddle Demo

SQL multiple phone numbers on multiple rows - how to move all fields into 1 row

I need to combine 3 phone numbers for a user into one row. The end result would look like this
contactId | phone | mobile | fax
my tables look like this with the phone numbers broken into their own rows by phoneTypeId
contact_phone_link
contactId | contactPhoneNumberId
3344 | 1
contact_phone_numbers
id | phoneTypeId | phoneNumber
123 | 1 | 555-555-5555
phone_types
id | phoneTypeName
1 | Phone
2 | Mobile
3 | Fax
Dana thanks for your help, I'm getting closer to understanding this. I see how it's grouping and naming and then grouping again.
I've gone through and adjusted the syntax, but I'm still having problems where it gets to 'phone_link.phoneTypeId'. I think it's because 'contact_phone_link' doesn't hold 'phoneTypeId'. Also it gets hung up when the SELECT section and JOIN section had the same variable name like 'phone' so I added a 1.
Tell me if I'm way off.
SELECT
`ct`.`id` AS contact_id,
`phone1`.`lineNumber` AS phone,
`fax1`.`lineNumber` AS fax,
`mobile1`.`lineNumber` AS mobile
FROM `cmd`.`contacts` AS ct
LEFT JOIN `cmd`.`contact_phone_link` AS `phone_link` ON (`phone_link`.`contactId` = `ct`.`id` AND `phone_link`.`phoneTypeId` = 1)
LEFT JOIN `cmd`.`contact_phone_numbers` AS `phone1` ON (`phone`.`id` = `phone_link`.`contactPhoneNumberId`)
LEFT JOIN `cmd`.`contact_phone_link` AS `fax_link` ON (`fax_link`.`contactId` = `ct`.`id` AND `fax_link`.`phoneTypeId` = 2)
LEFT JOIN `cmd`.`contact_phone_numbers` AS `fax1` ON (`fax`.`id` = `fax_link`.`contactPhoneNumberId`)
LEFT JOIN `cmd`.`contact_phone_link` AS `mobile_link` ON (`mobile_link`.`contactId` = `ct`.`id` AND `mobile_link`.`phoneTypeId` = 3)
LEFT JOIN `cmd`.`contact_phone_numbers` AS `mobile1` ON (`mobile`.`id` = `mobile_link`.`contactPhoneNumberId`)
WHERE (`ct`.`id` = 4160);
Edited solution below.
SELECT
`ct`.`id` AS contact_id,
`phone_sub_query`.`lineNumber` AS phone,
`fax_sub_query`.`lineNumber` AS fax,
`mobile_sub_query`.`lineNumber` AS mobile
FROM `cmd`.`contacts` AS ct
left join
(select `phone_link`.`contactId`, `phone_number`.`lineNumber`
from `cmd`.`contact_phone_link` AS `phone_link`
join `cmd`.`contact_phone_numbers` AS `phone_number` ON (`phone_number`.`id` = `phone_link`.`contactPhoneNumberId`)
where `phone_number`.`phoneTypeId` = 1) as `phone_sub_query` on `phone_sub_query`.`contactId` = `ct`.`id`
left join
(select `phone_link`.`contactId`, `phone_number`.`lineNumber`
from `cmd`.`contact_phone_link` AS `phone_link`
join `cmd`.`contact_phone_numbers` AS `phone_number` ON (`phone_number`.`id` = `phone_link`.`contactPhoneNumberId`)
where `phone_number`.`phoneTypeId` = 2) as `fax_sub_query` on `fax_sub_query`.`contactId` = `ct`.`id`
left join
(select `phone_link`.`contactId`, `phone_number`.`lineNumber`
from `cmd`.`contact_phone_link` AS `phone_link`
join `cmd`.`contact_phone_numbers` AS `phone_number` ON (`phone_number`.`id` = `phone_link`.`contactPhoneNumberId`)
where `phone_number`.`phoneTypeId` = 3) as `mobile_sub_query` on `mobile_sub_query`.`contactId` = `ct`.`id`
WHERE (`ct`.`id` = 4160);
Given your table structure, I think using sub-queries might be required. My solution actually looks a little more like #Eugene's now, but you want to left join against an inner join. My sub-queries check the phone type and emit (a) a contact id and (b) a line number.
Let me know if this works. I think it should be closer.
We need to create a table of contactIds and join the numbers to it:
SELECT
idlist.contactId AS contactId,
phones.phone AS phone,
mobiles.mobile AS mobile,
faxes.fax AS fax
FROM
-- This builds the ID list
(SELECT DISTINCT contactId
FROM contact_phone_link
) AS idlist
LEFT JOIN
-- This gets the landlines
(SELECT
contact_phone_link.contactId AS contactId,
contact_phone_numbers.phoneNumber AS phone
FROM contact_phone_link
INNER JOIN contact_phone_numbers ON contact_phone_numbers.id=contact_phone_link.contactPhoneNumberId
WHERE contact_phone_numbers.phoneTypeId=1
) AS phones ON phones.contactId=idlist.contactIs
LEFT JOIN
-- This gets the mobiles
(SELECT
contact_phone_link.contactId AS contactId,
contact_phone_numbers.phoneNumber AS mobile
FROM contact_phone_link
INNER JOIN contact_phone_numbers ON contact_phone_numbers.id=contact_phone_link.contactPhoneNumberId
WHERE contact_phone_numbers.phoneTypeId=2
) AS mobiles ON mobiles.contactId=idlist.contactId
LEFT JOIN
-- This gets the faxes
(SELECT
contact_phone_link.contactId AS contactId,
contact_phone_numbers.phoneNumber AS fax
FROM contact_phone_link
INNER JOIN contact_phone_numbers ON contact_phone_numbers.id=contact_phone_link.contactPhoneNumberId
WHERE contact_phone_numbers.phoneTypeId=3
) AS faxes ON faxes.contactId=idlist.contactId