How can I get values null after inner join? - mysql

I have this structure:
Table A -- |id_A | field_1a | id_B | value_A|
Table B -- |id_B | field_1b | field_2b | value_B|
Table C -- |field_1c | id_D | id_A|
Table D -- |id_D | description|
I want to get a query which returns:
-- Value A and if this value is null, returns its value in B.
-- Description of Table D through Table C. If row is not in C returns null.
I achieved the first part but I am stuck in the second part. As far as I tried this is my query:
select a.id_A
if (VALOR_b is null, VALOR_a, VALOR_b) as valor,
d.description
from A a inner join B b
on a.id_B = b.id_B
inner join C c
on c.id_A = A.id_A
inner join D d
on d.id_D = c.D
As I said I am getting the first part and just the values what I can cross through C, but I need when I can cross them a null value in the previously part of the query.
Hoping I explain clear enough. Please, ask me any doubt.
Thanks in advance.

Use a left join to both C and D.
Also your life will be easier if you use the SQL standard coalesce() instead of if ():
select
a.id_A,
coalesce(VALOR_a, VALOR_b) as valor,
d.description
from A a
join B b on a.id_B = b.id_B
left join C c on c.id_A = A.id_A
left join D d on d.id_D = c.D
Left join still returns a row from the parent table if there is no matching row, in which case it returns all nulls from the joined table"
coalesce() takes any number of arguments and returns the first non-null value.

Related

select if join one table else join other table

There are 3 tables. Lets call them a, b and c. Selected data from table a must be intersected with b or c depending of value of one cell in table a. Lets call this cell d. Is there way in MySQL to make query like:
SELECT
a.cell,
a.other_cell,
a.d,
alias.cell,
alias.other_cell if(
a.d = 3,
left join b as alias on b.id = a.id,
left join c as alias on c.id = a.id
)
FROM
a where a.id = 123
You can try for example Conditional join as here but instead coalesce, use CASE WHEN
In short:
Instead, you simply LEFT OUTER JOIN to both tables, and in your SELECT clause, return data from the one that matches
select
E.EmployeeName,
CASE
WHEN d = 3 THEN s.store
WHEN d <>3 THEN o.office
END as Location
from
Employees E
left outer join
Stores S on …
left outer join
Offices O on …
NOTE I think the two columns must have the same type, at least in PG, not sure about MYSQL
Assuming you want select from the different tables (b and c) based on a 'cell' value in
table a then something like this work may work for you.
SELECT
a.cell,
a.other_cell,
a.d,
if(a.d = 3, b.cell, c.cell) cell
FROM a
INNER JOIN b on b.id = a.id
INNER JOIN c on c.id = a.id
WHERE a.id = 123
This should join the all 3 tables provided the id in b and c also match the id in table a. The if function will select the cell column from table b or c based on the value of column d in table a.
Also helpful: https://www.w3resource.com/mysql/control-flow-functions/if-function.php

Join three tables A, B, C and return common in A in mysql

I want to join below three tables A, B, C and return only common(shaded) part of table A
A
-------
ID, Name
B
-------
ID, ORG
C
--------
ID, DEP
Please anyone provide simple join query
I understand that you want rows from a whose id can be foud in either b or c.
This sounds like two exists subquery:
select a.*
from a
where
exists (select 1 from b where b.id = a.id)
or exists (select 1 from c where c.id = a.id)
If you also want columns from tables b or c, you can use two left joins instead, with a where condition that ensures that at least one of the joins did succeed:
select a.*, b.org, c.dept
from a
left join b on b.id = a.id
left join c on c.id = a.id
where b.id is not null or c.id is not null
You want a left join starting with A and then some filtering:
select . . .
from a left join
b
on . . . left join
c
on . . .
where b.? is not null or c.? is not null;
The ? are either columns used in the joins or primary keys on the respective tables.

Mysql SELECT only unique values in one column when left joined with another table

This is the query:
SELECT a.id, a.userName,if(o.userId=1,'C',if(i.userId=1,'I','N')) AS relation
FROM tbl_users AS a
LEFT JOIN tbl_contacts AS o ON a.id = o.contactId
LEFT JOIN tbl_invites AS i ON a.id = i.invitedId
ORDER BY relation
This returns the output as follows:
+----+--------------+-------------+
| ID | USERNAME | RELATION |
+----+--------------+-------------+
| 1 | ray | C |
+----+--------------+-------------+
| 2 | john | I |
+----+--------------+-------------+
| 1 | ray | N |
+----+--------------+-------------+
I need to remove the third row from the select query by checking if possible that id is duplicate. The priority is as follows:
C -> I -> N. So since there is already a "ray" with a C, I dont want it again with an I or N.
I tried adding distinct(a.id) but it doesn't work. How do I do this?
Why doesn't DISTINCT work for this?
From the specs you gave, all you have to do is group by ID and username, then pick the lowest value of relation you can find (since C < I < N)
SELECT a.id, a.userName, MIN(if(o.userId=1,'C',if(i.userId=1,'I','N'))) AS relation
FROM tbl_users AS a
LEFT JOIN tbl_contacts AS o ON a.id = o.contactId
LEFT JOIN tbl_invites AS i ON a.id = i.invitedId
GROUP BY a.id, a.username
There are multiple ways to get the group-wise maximum/minimum as you can see in this manual page.
The best one suited for you is the first one, if the order of the rows can not be defined by alphabetic order.
In this case, given if the desired order were z-a-m (see Rams' comment) you'd need the FIELD() function.
So your answer is
SELECT
a.id,
a.userName,
if(o.userId=1,'C',if(i.userId=1,'I','N')) AS relation
FROM tbl_users a
LEFT JOIN tbl_contacts AS o ON a.id = o.contactId
LEFT JOIN tbl_invites AS i ON a.id = i.invitedId
WHERE
if(o.userId=1,'C',if(i.userId=1,'I','N')) = (
SELECT
if(o.userId=1,'C',if(i.userId=1,'I','N')) AS relation
FROM tbl_users aa
LEFT JOIN tbl_contacts AS o ON aa.id = o.contactId
LEFT JOIN tbl_invites AS i ON aa.id = i.invitedId
WHERE aa.id = a.id AND aa.userName = a.userName
ORDER BY FIELD(relation, 'N', 'I', 'C') DESC
LIMIT 1
)
Note, you can also do it like ORDER BY FIELD(relation, 'C', 'I', 'N') to have it more readable / intuitive. I turned it the other way round, because if you'd have the possibility of having a 'X' in the relation, the FIELD() function would have returned 0 because X is not specified as a parameter. Therefore it would be sorted before 'C'. By sorting descending and turning the order of the parameters around this can not happen.

Inner join where not

I have my main table with the following structure:
id | created_at | f1_id | f2_id
1 2016-01-23 11:41:21 5 7
2 2016-01-23 12:31:22 5 7
Then I have a table 1:n to the previous table:
main_id | value
1 aaa
1 bbb
2 ccc
And then I have a table 1:1 to the previous one with confirmations:
f1_id | f2_id | value | confirmed_at
5 7 aaa 2016-01-25 21:41:51
9 9 ccc 2016-01-25 23:51:45
Now I want to get all NOT confirmed values from the date range from the main table. How do I find all not confirmed values from range from the main table?
f1 and f2 are 1:1 tables used in the main table and confirmed values.
SELECT *
FROM maintable m
INNER JOIN f1table f1 ON m.f1_id = f1.id
INNER JOIN f2table f2 ON m.f2_id = f2.id
INNER JOIN values v ON m.id = v.main_id
INNER JOIN confirmed c ON (v.value = c.value AND c.f1_id = m.f1_id AND c.f2_id = m.f2_id)
WHERE m.created_at BETWEEN '2016-01-20' AND '2016-01-24';
This SQL will fetch all confirmed values...but what about not confirmed?
You can use an anti-join pattern. Use an outer join to return all rows from one table. The outer join will return matching rows from the second table, but will also return rows from the first table that do not have a matching row. The trick is a predicate (condition) in the WHERE clause that excludes the rows that had a match, leaving only those rows that didn't have a match.
Based on your query, to return all rows from m, v, f1 and f2 where there isn't a matching row found in c, something like this:
SELECT m.id
, m.created_at
, m.f1_id
, m.f2_id
, v.main_id
, v.value
FROM maintable m
JOIN f1table f1 ON m.f1_id = f1.id
JOIN f2table f2 ON m.f2_id = f2.id
JOIN values v ON m.id = v.main_id
LEFT
JOIN confirmed c
ON c.value = v.value
AND c.f1_id = m.f1_id
AND c.f2_id = m.f2_id
WHERE c.f1_id IS NULL
AND m.created_at BETWEEN '2016-01-20' AND '2016-01-24'
Any row from c that "matches" is going to have non-NULL values for the columns evaluated in the join condition (i.e. a NULL value in c.f1_id isn't going to satisfy the equality comparison.) So we can test for a non-NULL value in that column to identify which rows matched.
The specification is a little ambiguous, as to whether you would expect there to be rows from f1 and/or f2 when a matching row doesn't exist in confirmed. It's possible to use outer join operations to the f1 and f2 tables (add the LEFT keyword before JOIN.)
The anti-join pattern is easier to understand in a less complicated example. To return rows from a for which a matching row doesn't exist in b
SELECT a.foo
FROM a
LEFT
JOIN b
ON b.foo = a.foo
WHERE b.foo IS NULL
Try replacing the INNER JOIN to the confirmed table with a NOT EXISTS where-clause. Something like the following:
SELECT *
FROM maintable m
INNER JOIN f1table f1 ON m.f1_id = f1.id
INNER JOIN f2table f2 ON m.f2_id = f2.id
INNER JOIN values v ON m.id = v.main_id
WHERE m.created_at BETWEEN '2016-01-20' AND '2016-01-24'
AND NOT EXISTS (SELECT * FROM confirmed c WHERE v.value = c.value AND c.f1_id = m.f1_id AND c.f2_id = m.f2_id)

Query for rows that share a set of associations

I have three tables: A, B, C. B has a many-to-many relationship with A and C.
A >-< B >-< C
Assume every table has a primary key column called id, and the join tables have two columns [a_id, b_id] and [b_id, c_id].
The same row in B can be linked to both rows in A and C. It's fairly straightforward to find rows in C that share a specific row in B (a series of inner joins).
Given a row id of A, I would like to query for all rows in C that share ALL rows of B associated with that row in A:
select id,
(select count(*) from c
inner join b_c on c.id = b_c.c_id
) as c_group,
(select count(*) from c
inner join b_c on c.id = b_c.c_id
inner join b on b.id = b_c.b_id
inner join a_b on b.id = a_b.b_id
where a_id = ?
) as a_c_group
from c
where c_group <= a_c_group;
Can this be done via SQL? I'm working in MySQL, so a MySQL-specific solution would be fine.
This would yield all the id's for B that are associated with the selected A:
SELECT b_id FROM ab WHERE a_id = ?
So you need to find any C's that are related to only these B id's and not others. This can be done by excluding all C's that match other B id's:
SELECT c.id
FROM c
LEFT JOIN bc ON c.id = bc.c_id
AND bc.b_id NOT IN (SELECT b_id FROM ab WHERE a_id = ?)
WHERE bc.c_id IS NULL
If I understand your question correctly, you want all rows in C that have a matching row in B that, in turn, match the specified row id in A.
This should do it:
select
c.*
from
c
join bc on c.id = bc.c_id
where
exists (select 1
from ab
where ab.a_id = 123
and ab.b_id = bc.b_id)
This assumes that your join (bridge) tables are named ab and bc, and that you're looking for an a.id of 123
SQLFiddle here
Solution was to compare the count of an inner join of B and C with a inner join of all three tables.
SELECT ID
FROM C
WHERE (
SELECT COUNT(*)
FROM B_C
WHERE C_ID = C.ID
) <= (
SELECT COUNT(*)
FROM B_C INNER JOIN A_B ON B_C.B_ID = A_B.B_ID
WHERE C_ID = C.ID AND A_ID = ?
);
This covers:
If C has no associated B's, it should appear in the result set.
If C has associated B's not associated with A, it does not appear in the result set.
If A has associated B's not associated with C, but all B's C is associated with, C appears in the result set.