Matching over related tables - mysql

I have two tables, one with names, nametable:
id | name
---------
1 | alice
2 | bob
3 | charlie
A second with related data, datatable:
id | data
------------
1 | chicken
2 | fish
2 | chicken
3 | spaghetti
Now I want to find matching matching data between two names of nametable:
name1 | name2 | data
--------------------
alice | bob | chicken
Now I have this:
SELECT nt0.name, nt1.name, nt0.data
FROM nametable AS nt0
INNER JOIN datatable AS dt0
ON nt0.id = dt0.id
LEFT JOIN nametable AS nt1
INNER JOIN datatable AS dt1
ON nt1.id = dt1.id
WHERE dt0.data = dt1.data AND nt0.name < nt1.name;
But it doesn't work. I guess the JOINs are the reason, but I don't know how else to do it.

You can do a self join on derived tables:
SELECT
t1.name, t2.name, t1.data
FROM (
SELECT n.id, n.name, d.data
FROM nametable n
INNER JOIN datatable d
ON d.id = n.id
) t1
INNER JOIN (
SELECT n.id, n.name, d.data
FROM nametable n
INNER JOIN datatable d
ON d.id = n.id
)t2
ON t2.data = t1.data
AND t2.id > t1.id

Related

MySQL: Join with 4 tables and common field: row_number()

I have 4 tables -
Tab: d
Name | ID
----------
A | 1
B | 2
C | 3
Tab: p
Name | ID
----------
AX | 1
B | 2
X | 3
Y | 4
Z | 5
Tab: s
Name | ID
----------
A | 1
BL | 2
V | 3
M | 4
Tab: a
Name | ID
----------
K | 1
J | 2
H | 3
N | 4
Now I am using MySQL and today I found out that MySQL does not support FULL join. So, I am using left join with all 4tables and then using "union" and right join to merge all the 4 tables' records.
The query I am using is like -
(select d.Name, p.Name, s.Name, a.Name from doc d
left join
prof p
on d.id = p.id
left join
sing s
on d.id = s.id
left join
act a
on d.id = a.id)
union
(select d.Name, p.Name, s.Name, a.Name from doc d
right join
prof p
on d.id = p.id
right join
sing s
on d.id = s.id
right join
act a
on d.id = a.id)
But this is not giving the intended output. It is giving something like -
D | P | S | A
---------------------------------
A | AX | A | K
B | B | BL | J
C | X | V | H
NULL | NULL | NULL | N
Actual output should be -
D | P | S | A
---------------------------------
A | AX | A | K
B | B | BL | J
C | X | V | H
NULL | Y | M | N
NULL | Z | NULL | NULL
Please help me to figure out what I am missing! And also to help me to get the result...
The behavior you really want here is a full outer join, but MySQL does not directly support that (and the workaround is fairly ugly). One approach I can suggest here would be to maintain a fifth table containing all ID values which you expect in the result set. Consider:
SELECT c.ID, d.Name, p.Name, s.Name, a.Name
FROM
(SELECT 1 AS ID UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5) c
LEFT JOIN d ON d.ID = c.ID
LEFT JOIN p ON p.ID = c.ID
LEFT JOIN s ON s.ID = c.ID
LEFT JOIN a ON a.ID = c.ID
ORDER BY c.ID;
Assuming that you're using MySQL v8, you might be able to do it this way:
WITH RECURSIVE cte AS (
SELECT 1 AS rn, MAX(cnt) AS mxcnt
FROM
( SELECT COUNT(ID) cnt FROM doc UNION
SELECT COUNT(ID) FROM prof UNION
SELECT COUNT(ID) FROM sing UNION
SELECT COUNT(ID) FROM act ) v UNION ALL
SELECT rn+1, mxcnt FROM cte WHERE rn+1 <= mxcnt)
SELECT c.rn, d.Name, p.Name, s.Name, a.Name
FROM cte c
LEFT JOIN doc d ON d.ID = c.rn
LEFT JOIN prof p ON p.ID = c.rn
LEFT JOIN sing s ON s.ID = c.rn
LEFT JOIN act a ON a.ID = c.rn
ORDER BY c.rn;
Using WITH RECURSIVE to generate numbering sequence based on the largest count result from all related table then use it as the reference in LEFT JOIN. I agree with Tim about having a master table for all of the ids.

SQL select 2 tables but not ommit blank results

I have to do a select from 3 trables.
table1- idc,title,description..
table2- idc,filename,filepath,tabel1FK
table3- idc,table1FK,tabel2FK
I need a select from table1 and table2 and count unique ocurrences of table1 in table3
the select must be something like this
TB1 | TB2 | COUNT ON TB3
a | aa | 1
b | | 4
c | cc | 3
d | | 0
e | | 3
I think you just want left join and group by:
select tb1.idc, tb2.idc, count(tb3.idc)
from tb1 left join
tb2
on tb2.table1FK = tb1.idc left join
tb3
on tb3.table1FK = tb1.idc and tb3.tabel2FK = tb2.idc
group by tb1.idc, tb2.idc;

How to join a table to itself and get max row then connect another table to it

I have a table that contains redundant entries. What i need is to
get the column with max id data, then connect the table to users table and get name based on the max row user_id
tracking table
id | labref | user_id
-----------------------
1 | a | 1
------------------------
2 | a | 3
------------------------
3 | b | 4
------------------------
5 | b | 7
------------------------
SQL Query:
SELECT id,labref,user_id FROM tracking_table t WHERE t.id =
(SELECT MAX(t2.id) FROM tracking_table t2, user u WHERE t.labref = t2.labref AND u.id = t2.user_id)
Result:
id | labref | user_id
--------------------
2 | a | 3
--------------------
5 | b | 7
--------------------
Would like to join users table below
Users Table
id | name
-------------
1 | ua
------------
2 | ub
------------
3 | uc
------------
4 | ud
------------
5 | ue
-------------
7 | uf
-------------
Desired results should be as follows:
id | labref | name
--------------------
2 | a | uc
--------------------
5 | b | uf
--------------------
Suggestions as to where I am now stuck?
You can use a subquery to find the maximum id value for each lab reference. Then, join to this subquery to leave you with the effective rows you want. Finally, join this to the user table to bring in the usernames.
SELECT
t1.id,
t1.labref,
u.name
FROM tracking_table t1
INNER JOIN
(
SELECT labref, MAX(id) AS max_id
FROM tracking_table
GROUP BY labref
) t2
ON t1.labref = t2.labref AND
t1.id = t2.max_id
INNER JOIN user_table u
ON t1.user_id = u.id
Output:
Demo here:
Rextester
Please check this statement. Does it fit your needs?
SELECT t.id, t.labref, u.name
FROM tracking_table t
INNER JOIN users u
on t.user_id = u.id
WHERE t.id =
(SELECT MAX(t2.id)
FROM tracking_table t2, user u
WHERE t.labref = t2.labref AND u.id = t2.user_id)
The users table is unnecessary in the subquery. You need it in the main query:
SELECT t.id, t.labref, u.name
FROM tracking_table t
JOIN user u ON u.id = t.user_id
WHERE t.id = (SELECT MAX(t2.id) FROM tracking_table t2 WHERE t.labref = t2.labref);
You shouldn't use comma-separated joins anymore by the way. They were made redundant in 1992. Use explicit ANSI joins instead.

Return results only if it doesn't contain a particular value?

I have 2 tables :-
Table T
ID | val
1 | abcd
2 | 1234
3 | asd
4 | lkj
And another table M
ID | T_ID | Type
1 | 1 | I
2 | 1 | S
3 | 2 | I
4 | 2 | I
5 | 3 | I
6 | 4 | S
I want to write a query that joins table T and M on m.T_ID = T.ID but it should not return T.ID if any M mapped to it has Type S i.e. the above set of data should return values T.ID = 2,3 and not 1,4 because M mapped to it has Type S
One way to do it would be to write a inner query. Something like :-
SELECT T.id
FROM table1 T
JOIN table2 M
ON M.t_id = T.id
WHERE T.id NOT IN (SELECT m2.t_id
FROM table2 m2
WHERE m2.type = 'S')
But inner query can be very expensive as my table M has millions of rows. Is there a better way to do this ?
Use a conditional COUNT
SELECT T.id
FROM table1 T
JOIN table2 M
ON M.t_id = T.id
GROUP BY T.id
HAVING COUNT( CASE WHEN M.Type = 'S' THEN 1 END ) = 0
Mean you dont have 'S' in that group.
Not the prettiest but it seems to work
select T.ID
from Table1 T
left join Table2 M on M.T_ID = T.ID
group by T.Id
having sum(case when M.Type = 'S' then 1 else 0 end) = 0
You should check if it is actually less expensive in the execution plan.
You should look into LEFT JOIN as opposed to INNER JOIN.

MySQL - Display two columns where first col values in table C and second col values not in table C

let's say there are two tables: must_have_products and products_buyed
i want to display, for each customer, what products that they already
bought as col A (GROUP_CONCAT) and what products they still needs to buy as col B (GROUP_CONCAT)..
Table A(must_have_products)
|id_a| name |
|1a | TV |
|2a | House |
|3a | Car |
Table B(People)
|id_b| name |
|1b | Mr. A |
|2b | Mr. B |
Table C
|id_c|id_b|id_a|
|1 |1b |1a |
expected result:
|id_b | buyed | left |
|1b | 1a | 2a, 3a |
|2b | | 1a, 2a, 3a |
PS: i'm sorry if the title is wrong, i just have no idea what to say
Here is a way to do it. Getting the items which are bought with a group_concat is easy but not bought is not straight forward.
select
t2.id_b,
group_concat(t1.id_a) as buyed,
case
when group_concat(t1.id_a) is null
then (select group_concat(id_a order by id_a) from tableA)
else substring_index((select group_concat(id_a order by id_a) from tableA),
concat(group_concat(t1.id_a order by t1.id_a),','),-1)
end as `left`
from tableB t2
left join tableC t3 on t3.id_b = t2.id_b
left join tableA t1 on t1.id_a = t3.id_a
group by t2.id_b
DEMO
left is a keyword, you can't use it mysql-left-function
here is the solution:
select B.id_b, COALESCE(R1.buyer, '') buyer, COALESCE(R2.lft, '') lft
from
B,
(select B.id_b , GROUP_CONCAT(DISTINCT A2.id_a SEPARATOR ', ') as buyer
from B
left join A A2
on Exists(select * from C where A2.id_a = C.id_a and C.id_b = B.id_b)
group by B.id_b
) R1,
(
select B.id_b , GROUP_CONCAT(DISTINCT A2.id_a SEPARATOR ', ') as `left`
from B
left join A A2 on
NOT Exists(select * from C where A2.id_a = C.id_a and C.id_b = B.id_b)
group by B.id_b
) R2
where
B.id_b = R1.id_b and B.id_b = R2.id_b
DEMO