Flixn
Flixn

Reputation: 21

mysql random selection in inner join

Question Mysql Random Row Query on Inner Join is much the same as mine but it was never answered.

I have a master table m and slave s. S contains 1 to many rows for each m. I would like a query that selects every master row joined to exactly one randomly chosen slave.

If the table schemas were:

M
---
id

S
---
id
mid

then, in pseudo code the query would be: select * from m inner join s on m.id = s.mid where s.id is one randomly chosen from the values that exist

Can this be translated into real SQL?

Upvotes: 2

Views: 1133

Answers (2)

Madhur Bhaiya
Madhur Bhaiya

Reputation: 28834

This can be solved using Row_Number() concept. We need to randomly assign row number values within a partition of mid in the table s. And, do a Join from the m table to s using mid and row_number = 1. This will pick a single Random row everytime.

In MySQL version below 8, we can use User-defined Variables to emulate Row_Number(). To understand how this works, you may check this answer for the explanation: https://stackoverflow.com/a/53465139/2469308

Note that this technique will be efficient on Large tables than using a Subquery (in the SELECT clause), as it will be doing overall table Sorting only once

View on DB Fiddle

create table m (id int, m_nm varchar(10));
create table s (id int, 
                mid int references m(mid), 
                s_nm varchar(10));

insert into m values(1, "a");
insert into m values(2, "b");
insert into m values(3, "c");

insert into s values(1, 1, "aa");
insert into s values(2, 1, "aa");
insert into s values(3, 2, "bb");
insert into s values(4, 2, "bbb");
insert into s values(5, 2, "bbbb");
insert into s values(6, 3, "cc");
insert into s values(7, 3, "ccc");

Query

SELECT 
  m.*, s_dt.id, s_dt.mid, s_dt.s_nm 
FROM 
  m 
JOIN 
(
SELECT
  @rn := IF(@m = dt.mid, @rn+1, 1) AS row_num,   
  @m := dt.mid AS mid, 
  dt.id, 
  dt.s_nm 
FROM 
( 
SELECT
   id, mid, s_nm, RAND() as rand_num
 FROM s
 ORDER BY mid, rand_num ) AS dt
CROSS JOIN (SELECT @rn:=0, @m:=0) AS user_vars 
) AS s_dt 
  ON s_dt.mid = m.id AND 
     s_dt.row_num = 1;

Result (Run #1)

| id  | m_nm | id  | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1   | a    | 2   | 1   | aa   |
| 2   | b    | 5   | 2   | bbbb |
| 3   | c    | 7   | 3   | ccc  |

Result (Run #2)

| id  | m_nm | id  | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1   | a    | 1   | 1   | aa   |
| 2   | b    | 4   | 2   | bbb  |
| 3   | c    | 6   | 3   | cc   |

Result (Run #3)

| id  | m_nm | id  | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1   | a    | 1   | 1   | aa   |
| 2   | b    | 3   | 2   | bb   |
| 3   | c    | 7   | 3   | ccc  |

MySQL 8.0.2+ / MariaDB 10.3+ solution would be simply the following:

SELECT 
  m.*, s_dt.id, s_dt.mid, s_dt.s_nm 
FROM 
  m 
JOIN 
(
  SELECT
    s.*, 
    ROW_NUMBER() OVER w AS row_num
  FROM s
  WINDOW w AS (PARTITION BY mid 
               ORDER BY RAND())
) AS s_dt 
  ON s_dt.mid = m.id AND 
     s_dt.row_num = 1

View on DB Fiddle

Upvotes: 0

Taher A. Ghaleb
Taher A. Ghaleb

Reputation: 5240

I think the following query does the required job but using a subquery (not inner join):

SELECT *, (SELECT id FROM S WHERE S.mid = M.id ORDER BY RAND() LIMIT 1) AS S_id
FROM M

Here is a link to test it. Hope it helps.

Upvotes: 2

Related Questions