JOIN each row only once — arrange (distribute) rows 1 to 1 - mysql

I have two tables to join, and I want each row to be joined only once. Here is sample data:
CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');
When we join on id we obtain:
mysql> SELECT * FROM A JOIN B ON A.id = B.id;
+------+------+------+------+
| id | val | id | val |
+------+------+------+------+
| 2 | b | 2 | x |
| 3 | c | 3 | y |
| 3 | d | 3 | y |
| 3 | c | 3 | k |
| 3 | d | 3 | k |
+------+------+------+------+
What I want is either:
+------+------+------+------+ +------+------+------+------+
| id | val | id | val | | id | val | id | val |
+------+------+------+------+ or +------+------+------+------+
| 2 | b | 2 | x | | 2 | b | 2 | x |
| 3 | c | 3 | y | | 3 | d | 3 | y |
| 3 | d | 3 | k | | 3 | c | 3 | k |
+------+------+------+------+ +------+------+------+------+
The order and arrangement don’t matter.
Is it possible? How?
According to this answer I need to specify how to select the matching row. In that case I guess would need to check in a subquery if the row of the joined table has already been used; or a kind of counter related to the id... but I do not know how to write this.
Edit:
To clarify I want each row with id 3 to be mapped with another one in the joined table, such as each row is mapped only once (I am also interested to know what happens when the number of rows with same id is different in the two tables):
(3, c) -> (3, y) [join only with the first row such as B.id = 3]
(3, d) -> (3, k) [the first row has been used, so map with (and only with) the second row such as B.id = 3]
But as I said mapping may be in any other order (e.g. mapping rows in reverse order).

SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');
Query 1:
select
aa.id as aid
, aa.val as aval
, bb.id as bid
, bb.val as bval
from (
select
#row_num :=IF(#prev_value=a.id,#row_num+1,1)AS RowInGroup
, a.id
, a.val
, #prev_value := a.id
from (
SELECT id, val
FROM A
group by id, val
/* order by ?? */
) a
CROSS JOIN (
SELECT #row_num :=1, #prev_value :=''
) vars
) aa
INNER JOIN (
select
#row_num :=IF(#prev_value=b.id,#row_num+1,1)AS RowInGroup
, b.id
, b.val
, #prev_value := b.id
from (
SELECT id, val
FROM B
group by id, val
/* order by ?? */
) b
CROSS JOIN (
SELECT #row_num :=1, #prev_value :=''
) vars
) bb on aa.id = bb.id and aa.RowInGroup = bb.RowInGroup
order by
aa.id
, aa.val
Results:
| id | val | id | val |
|----|-----|----|-----|
| 2 | b | 2 | x |
| 3 | c | 3 | k |
| 3 | d | 3 | y |
nb: you could influence the final result by introducing order by in the subqueries that group by id, val where the sequence RowInGroup is calculated.

Finally I did it!
SELECT T.ID_A,
T.VAL_A,
T.XXXX,
T.ID_B,
T.VAL_B,
T.YYYY
FROM (
SELECT A.id AS ID_A,
A.VAL AS VAL_A,
ROW_NUMBER() OVER (PARTITION BY A.ID, A.VAL ORDER BY A.ID, A.VAL) AS XXXX,
B.ID AS ID_B,
B.VAL AS VAL_B,
ROW_NUMBER() OVER (PARTITION BY B.ID, B.VAL ORDER BY B.ID DESC, B.VAL) AS YYYY
FROM A INNER JOIN B ON A.id = B.id) AS T
WHERE T.YYYY = 1

I could make it thanks to this blog post:
SELECT A2.id, A2.val, B2.val FROM (
SELECT l.id, l.val, COUNT(*) AS n1 FROM A AS l JOIN A AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS A2 JOIN (
SELECT l.id, l.val, COUNT(*) AS n2 FROM B AS l JOIN B AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS B2 ON
A2.id = B2.id AND n1 = n2;
The result is:
+------+------+------+
| id | val | val |
+------+------+------+
| 2 | b | x |
| 3 | c | k |
| 3 | d | y |
+------+------+------+

Related

Select all rows with values that appear twice by the same customer?

I have a table:
CREATE TABLE Orders (
ID INT,
Customer INT,
PRIMARY KEY(ID)
);
CREATE TABLE Items (
ID INT,
Barcode INT,
PRIMARY KEY(ID, Barcode)
);
INSERT INTO Orders VALUES
(1, 1), (2, 1), (3, 2), (4, 3), (5, 3);
INSERT INTO Items VALUES
(1, 1), (1, 2), (1, 3), (1, 7),
(2, 1), (2, 3), (3, 2), (3, 8),
(4, 2), (4, 3), (4, 8), (5, 4);
I'm trying to find all customers who have ordered the same item twice and specify the item, but not from the same order. I just need a list of Orders.Customer and Items.Barcode showing this.
Here's a query that helps illustrate:
SELECT i.ID, i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID
Which produces the below:
+----+---------+----------+
| ID | Barcode | Customer |
+----+---------+----------+
| 1 | 1 | 1 | # A
| 1 | 2 | 1 |
| 1 | 3 | 1 | # B
| 1 | 7 | 1 |
| 2 | 1 | 1 | # A
| 2 | 3 | 1 | # B
| 3 | 2 | 2 |
| 3 | 8 | 2 |
| 4 | 2 | 3 |
| 4 | 3 | 3 |
| 4 | 8 | 3 |
| 5 | 4 | 3 |
+----+---------+----------+
Note where I tagged A, Barcode 1 appears in both ID 1 and ID 2. Both those orders have the same customer, same barcode, but different order IDs. B is another example.
How can I pull out these rows, so I have something like the below:
+---------+----------+
| Barcode | Customer |
+---------+----------+
| 1 | 1 |
| 3 | 1 |
+---------+----------+
More declaratively, I want to know what customers have ordered the same item twice, and list the items and customers. In other words, "Customer 1 has ordered Items 1 and 3 twice".
I'm trying to find all customers who have ordered the same item twice and specify the item, but not from the same order.
This is pretty simple with a HAVING clause:
SELECT o.Customer, i.Barcode
FROM Orders o JOIN
Items i
ON i.ID = o.ID
GROUP BY o.Customer, i.Barcode
HAVING MIN(o.id) <> MAX(o.id);
Note the use of proper, explicit, standard, readable JOIN syntax. Never use commas in the FROM clause.
If you need the expected result then you should group by with having clause like below.
SELECT i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID
GROUP BY i.Barcode, o.Customer HAVING COUNT(*) >1
I am assuming that you need all records which are repeating more than once.
You may try this -
With cte1 as (SELECT i.ID as orderId, i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID),
cte2 as (SELECT i.ID as orderId, i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID)
Select distinct cte1.Barcode, cte1.Customer
from cte1, cte2
where
cte1.Barcode = cte2.Barcode
and cte1.Customer = cte2.Customer
and cte1.orderId <> cte2.orderId;
More elegant way - Please refer Amit Verma's answer

GROUP_CONCAT on LEFT JOIN generates groups against NULL

I'm trying to use a LEFT JOIN in conjunction with a GROUP_CONCAT but not getting the expected results.
Two simple tables:
weather_alerts:
id | user_id | resort_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 5
weather_users
id | email
1 | me#me.com
The query:
SELECT GROUP_CONCAT(wa.resort_id) AS resort_ids, wu.email FROM weather_alerts wa LEFT JOIN weather_users wu ON wa.id = wu.user_id GROUP BY wu.email
Instead of generating:
email resort_ids
me#me.com 1,2,3,5
I get:
email resort_ids
NULL 2,3,5
me#me.com 1
I suspect this is an issue with the JOIN rather than the CONCAT.
It appears that your LEFT JOIN needs improvement.
create table weather_alerts (id int, user_id int, resort_id int);
insert into weather_alerts values (1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 1, 5);
create table weather_users (id int, email varchar(100));
insert into weather_users values (1, 'me#me.com');
Query
SELECT GROUP_CONCAT(wa.resort_id ORDER BY wa.resort_id) AS resort_ids, wu.email
FROM weather_alerts wa
LEFT JOIN weather_users wu ON wa.user_id = wu.id
GROUP BY wu.email
Notice that you are joining on wa.id = wu.user_id. The join should be on wa.user_id = wu.id
Result
| resort_ids | email |
|------------|-----------|
| 1,2,3,5 | me#me.com |

Select From a table limiting by column of another table

I have a table named question_dispositions composed like this
Type | chapter | number
-------------------------
A | 1 | 3
B | 1 | 2
B | 4 | 1
The second table named question has columns type, text and chapter.
Type | chapter | text
-----------------------
A | 1 | T1
A | 1 | T2
B | 1 | T3
B | 1 | T4
B | 1 | T5
B | 2 | T6
B | 2 | T7
B | 3 | T8
B | 4 | T9
What I need is to fetch X random questions from table Question where the X is the column number from question_dispositions
So if I need questions of type B the result that I expect is this:
chapter | text
--------------
1 | T3
1 | T4
4 | T9
How can I Select random question limiting by the column number in another table? I tried with this query but returns every row in the table Question as expected because I can not limit with qd.count
SELECT * FROM
question_dispositions AS qd, question AS q
WHERE qd.chapter = q.chapter AND qd.type = q.type AND qd.type = 'B'
ORDER BY RAND() is a typical approach to randomness in MySQL but it is usually criticized because it does not scale well. Here however you appear to be selecting a quite small number of rows as questions but to achieve some randomness of those. So, here is an approach using ORDER BY RAND() which should be acceptable in performance for small result sets.
There is an added requirement to LIMIT by a stored Number, however in the small example supplied that number isn't consistent for type B so I chose to limit by the maximum of that number for the wanted type B. MySQL doesn't allow use of variables or subqueries with the LIMIT clause so instead a generated row number is used in a where clause to achieve that outcome.
Refer to this SQL Fiddle
Data:
CREATE TABLE question_dispositions
(`Type` varchar(1), `chapter` int, `number` int)
;
INSERT INTO question_dispositions
(`Type`, `chapter`, `number`)
VALUES
('A', 1, 3),
('B', 1, 2),
('B', 4, 1)
;
CREATE TABLE question
(`Type` varchar(1), `chapter` int, `text` varchar(2))
;
INSERT INTO question
(`Type`, `chapter`, `text`)
VALUES
('A', 1, 'T1'),
('A', 1, 'T2'),
('B', 1, 'T3'),
('B', 1, 'T4'),
('B', 1, 'T5'),
('B', 2, 'T6'),
('B', 2, 'T7'),
('B', 3, 'T8'),
('B', 4, 'T9')
;
Query:
SELECT *
FROM (
SELECT #rownum := #rownum + 1 AS rn , q.*
FROM question_dispositions AS qd
INNER JOIN question AS q ON qd.chapter = q.chapter AND qd.type = q.type
CROSS JOIN (SELECT #rownum := 0) r
WHERE qd.type = 'B'
ORDER BY RAND()
) d
WHERE rn <= (SELECT MAX(NUMBER) FROM question_dispositions WHERE type = 'B')
Example Results:
| rn | Type | chapter | text |
|----|------|---------|------|
| 1 | B | 1 | T3 |
| 2 | B | 1 | T4 |

display two tables into one using select sql

I have Table A
============
| id | val |
=====+======
| 1 | abc |
| 1 | def |
| 2 | ghi |
| 2 | jkl |
============
I have Table B
============
| id | val2 |
=====+======
| 1 | rty |
| 1 | vbn |
| 2 | uio |
| 2 | zxc |
============
I want to display the two tables like this..
===================
| id | val | val2 |
=====+=====+=======
| 1 | abc | rty |
| 1 | def | vbn |
| 2 | ghi | uio |
| 2 | jkl | zxc |
===================
my problem is i am having redundancy..
Yes, you have a problem because you don't have a proper join key. You can do this by using variables to create one. Something like this will work for the data you provide:
select min(id), max(aval), max(bval)
from ((select id, val as aval, NULL as bval, #rna := #rna + 1 as seqnum
from tablea a cross join (select #rna := 0)
) union all
(select id, NULL val, #rnb := #rnb + 1 as seqnum
from tableb b cross join (select #rnb := 0)
)
) ab
group by seqnum;
I like Gordon's approach, since it doesn't assume the same number of rows in each table, but here's a JOIN version:
SELECT a.id,a.val,b.val2
FROM (SELECT #row_number:=#row_number+1 AS row_number
,id,val
FROM Table1 a cross join (select #row_number := 0) b
)a
JOIN (SELECT #row_number2:=#row_number2+1 AS row_number2
,id,val2
FROM Table2 a cross join (select #row_number2 := 0) b
)b
ON a.Row_Number = b.Row_Number2
AND a.id = b.id
Here's a working version of his UNION version:
SELECT Row_Number,ID,MAX(Val) AS Val,MAX(Val2) AS Val2
FROM (SELECT #row_number:=#row_number+1 AS row_number
,id,val,NULL as Val2
FROM Table1 a cross join (select #row_number := 0) b
UNION ALL
SELECT #row_number2:=#row_number2+1 AS row_number
,id,NULL,val2
FROM Table2 a cross join (select #row_number2 := 0) b
)sub
GROUP BY Row_Number,ID
Demo of both: SQL Fiddle
You can simply do this using INNER JOIN. See my query below:
SELECT A.id,val,val2 FROM
(SELECT
#row_number:=#row_number+1 AS RowNumber,
id,
val
FROM TableA, (SELECT #row_number:=0) AS t ORDER BY val) AS A
INNER JOIN
(SELECT
#row_number:=#row_number+1 AS RowNumber,
id,
val2
FROM TableB, (SELECT #row_number:=0) AS t ORDER BY val2) AS B
ON A.RowNumber=B.RowNumber

can not get correct results with group by in mysql

I have 2 SQL tables
CREATE TABLE A(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR(1) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE B(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
A_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO A VALUES (1, 'A'), (2, 'B'), (3, 'C'), (4, 'A');
INSERT INTO B VALUES (1, 1), (2, 2), (3, 4), (4, 4);
The tables look this way:
select * from A;
+----+------+
| id | name |
+----+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
+----+------+
select * from B;
+----+------+
| id | A_id |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 4 |
| 4 | 4 |
+----+------+
Now I want to find out how many each of the elements from table A are there in table B. Using other words I want to see:
A = 3
B = 1
C = 0
I tried to do this with: SELECT name, count(*) FROM A, B WHERE A.id = A_id GROUP BY A.id;, but it returns something completely weird. Can someone help me?
Query
SELECT a.name,COUNT(b.A_id) as `count`
FROM A a
LEFT JOIN B b
ON a.id=b.A_id
GROUP BY a.name;
Fiddle Demo
You just need a left outer join to handle the condition where there are no A's in B:
SELECT A.Name, COUNT(b.id)
FROM A
LEFT OUTER JOIN B on A.id = B.a_id
GROUP BY A.Name;
SqlFiddle here
You should use a LEFT JOIN, and not GROUP BY A.id, but instead by name:
SELECT A.name, COUNT(B.A_id)
FROM A
LEFT JOIN B ON A.id = B.A_id
GROUP BY A.name;