简体   繁体   中英

MySQL JOIN Query - one row from right table for each row left table with prioritizing contained data

I have two tables, house and renter. in each flat there are multiple renter. What I need is a joined list, containing at most two renter, prioritized rows with a full data set, then phone number and last e-mail address.

I'd also like to avoid temporary tables and subqueries, as there is a large amount of data. Thanks!

Example:

table house

-------------
| id | flat|
-------------
| 1  | 011 |
| 2  | 012 |
| 3  | 111 |
-------------

table renter

------------------------------
| fid | name | phone | mail   |
------------------------------
| 1   | pete | NULL  | NULL   |
| 1   | cloe | NULL  | cloe@..|
| 1   | bill | 555.. | bill@..|
| 2   | john | 555.. | john@..|
| 3   | paul | 555.. | NULL   |
| 3   | mary | NULL  | mary@..|
------------------------------

expected output:

-----------------------------------------------------------------
| id | flat | name1 | phone1 | mail1  | name2 | phone2 | mail2  |
-------------
| 1  | 011  | bill  | 555..  | bill@..| cloe  | NULL   | cloe@..|
| 2  | 012  | john  | 555..  | john@..| NULL  | NULL   | NULL   |
| 3  | 111  | paul  | 555..  | NULL   | mary  | NULL   | mary@..|
-----------------------------------------------------------------

In MySQL8:

SELECT 
  f.id,
  f.flat,
  MAX(CASE WHEN rr.rn = 1 THEN rr.`name` END) AS name1,
  MAX(CASE WHEN rr.rn = 1 THEN rr.phone END) AS phone1,
  MAX(CASE WHEN rr.rn = 1 THEN rr.mail END) AS email1,
  MAX(CASE WHEN rr.rn = 2 THEN rr.`name` END) AS name2,
  MAX(CASE WHEN rr.rn = 2 THEN rr.phone END) AS phone2,
  MAX(CASE WHEN rr.rn = 2 THEN rr.mail END) AS email2
FROM
house f
LEFT JOIN
(
 SELECT 
  r.*, 
  ROW_NUMBER() OVER(PARTITION BY r.fid ORDER BY 
    (CASE WHEN r.phone IS NOT NULL THEN -2 ELSE 0 END + CASE WHEN r.mail IS NOT NULL THEN -1 ELSE 0 END), r.fid
  ) rn
 FROM
  renter r
) rr
ON rr.fid = f.id and rr.rn <= 2
GROUP BY f.id, f.flat

In MySQL < 8 you'll have to fake ROW_NUMBER/PARTITION BY using this undocumented (might suddenly stop working) technique:

SELECT 
  f.id,
  f.flat,
  MAX(CASE WHEN rr.rn = 1 THEN rr.`name` END) AS name1,
  MAX(CASE WHEN rr.rn = 1 THEN rr.phone END) AS phone1,
  MAX(CASE WHEN rr.rn = 1 THEN rr.mail END) AS email1,
  MAX(CASE WHEN rr.rn = 2 THEN rr.`name` END) AS name2,
  MAX(CASE WHEN rr.rn = 2 THEN rr.phone END) AS phone2,
  MAX(CASE WHEN rr.rn = 2 THEN rr.mail END) AS email2
FROM
house f
LEFT JOIN
(
 SELECT 
  r.*, 
  @rn:=CASE WHEN r.fid=@previd THEN @rn+1 ELSE 1 END as rn,
  @previd:=r.fid
 FROM
  (select @rn:=0,@previd:=-1) x,
  renter r
  ORDER BY r.fid, (CASE WHEN r.phone IS NOT NULL THEN -2 ELSE 0 END + CASE WHEN r.mail IS NOT NULL THEN -1 ELSE 0 END)
) rr
ON rr.fid = f.id and rr.rn <= 2
GROUP BY f.id, f.flat

https://www.db-fiddle.com/f/dYS68AFFGTxZxfia1UtJEK/0

How it works:

Your renter table has a row number applied, that counts the rows in priority order. If a row has a phone, it scores -2, if a row has an email it scores -1. added together these come to -3 if a row has both. When ordered ascending, it means that -3 has higher priority (is the first row out of the sort) than a -2 or -1. Row number puts a number on the row like 1,2,3.. it restarts everytime the flat id number changes.

We take our augmented data set and join it onto the flats, abd we say we are only interested in rows <=2 on the rownumber because you only want name1 and name2 etc.

But this data is still in a single column:

FlatID, Name,  RN
1,      Bill,  1
1,      Cloe,  2

To turn the column into a row, we use a pivoting operation. The standard way to do this is to use CASE WHEN rn = 1 or 2 ... :

SELECT *, case when rn = 1 then name end as name, case when rn = 2 then name end

Produces

FlatID, Name1,  Name2,  RN
1,      Bill,   null,   1
1,      null,   Cloe,   2

Now we use MAX() to group up onto a single row for the FlatID, and because MAX discards nulls, the Bill and Cloe are perserved and become one row. RN has done its job and is discarded:

FlatID, Name1,  Name2
1,      Bill,   Cloe

The bottom query (mysql5.x) uses the same technique, it just uses variables to imitate row_number()


For future questions, please ensure you post the MySQL version and also attempt to make an example set of data on db-fiddle.com (like i did above) or similar - it will get more people interested in helping you if they don't have to mess around creating tables and loading them with data to test their theories

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM