I have seen various examples for this, but I can't find a definitive example of how to return all rows from table_A that are not related to a row in table_B using relate table_A_B without a given value for either table_A.id or table_B.id. The closest matchs I can find to this are:
Get a list of A not related to one or more rows in B but without knowing which value in B is the issue.
Get a list of A not related to given B.
Get list of A with comma-separated field of unrelated B (not sure I saw that, but it seemed like one example could expand as such).
A list of As and Bs not related to each other but no indication of which is not related to the other.
I can also get a list of all potential A_B tuples with:
SELECT A.id, B.id FROM A
INNER JOIN B ON A.id <> B.id
And I could hypothetically use one of the EXCEPT workarounds (I think) against the relate table, but all attempts were unsuccessful and I imagine that once there are millions of potential combinations returned by that join it will be much less efficient anyway.
So given the table values:
A
id | name
1 | X
2 | y
3 | z
B
id | name
7 | e
8 | f
9 | g
A_B
id | a_id | b_id
1 | 1 | 7
2 | 1 | 8
3 | 1 | 9
1 | 2 | 7
2 | 2 | 8
1 | 3 | 7
Is there a query that would return:
A | B
2 | 9
3 | 8
3 | 9
Or even better:
A | B
y | g
z | f
z | g
Or is this asking for trouble?
Start with a cross join between A and B to get all possible pairs. Then do a left join to the relation table and choose where there is no match:
select driver.aID, driver.bID
from (select a.id as aID, b.id as bID
from table_A A cross join table_B B
) driver left outer join
table_A_B ab
on ab.aID = driver.aID and ab.bID = bID
where ab.aID is null
This works, assuming that the ids are never NULL.
I haven't tested the SQL so it might have syntax errors.
This version gets you the names:
select driver.aName, driver.bName
from (select a.id as aID, b.id as bID, a.name as aName, b.name as Bname
from table_A A cross join table_B B
) driver left outer join
table_A_B ab
on ab.aID = driver.aID and ab.bID = bID
where ab.aID is null
Related
I have tables A, B and C and I want to get matching values for from all tables (tables have different columns).
Table A (primary key = id)
+------+-------+
| id | name |
+------+-------+
| 1 | Ruby |
| 2 | Java |
| 3 | JRuby |
+------+-------+
Table B (pid is reference to A(id) - No primary key)
+------+------------+
| pid | name |
+------+------------+
| 1 | Table B |
+------+------------+
Table C (primary key = id, pid is reference to A(id))
+------+------+------------+
| id | pid | name |
+------+------+------------+
| 1 | 2 | Table C |
+------+------+------------+
So my below query returned nothing. Whats wrong here? Is it treated as AND when multiple inner joins present?
Select A.* from A
inner join B ON a.id = b.pid
inner join C ON a.id = c.pid;
As you first join
1 | Ruby | Table B
and then try to join Table C, there is no match for pid 2 in the aforementioned result, the result is therefore empty.
An inner join excludes everything that doesn't match. So after you joined against B, you were left with only one record (id=1). Your inner join against C doesn't have any matches from what's left, so you get nothing.
I suppose a union would do the trick:
select A.* from A join B on a.id = b.pid
union
select A.* from A join C on a.id = c.pid
Or there are other ways, like where a.id in (select pid from b) or a.id in (select pid from c)
When you inner-join like this, a single row from A needs to exist such that a.id = b.pid AND a.id = c.pid are true at the same time. If you examine the rows in your examples, you would find that there is a row in A for each individual condition, but no rows satisfy both conditions at once. That is why you get nothing back: the row that satisfies a.id = b.pid does not satisfy a.id = c.pid, and vice versa.
You could use an outer join to produce two results:
select *
from A
left outer join B ON a.id = b.pid
left outer join C ON a.id = c.pid;
a.id a.name b.pid b.name c.id c.pid c.name
1 | Ruby | 1 | Table B | NULL | NULL | NULL
2 | Java | NULL | NULL | 1 | 2 | Table C
Of course you return nothing!
Table A and B inner join returns 1st record of Table A (table A ID = 1)
then you join table C, there is no matching rows to join table C, and vice versa.
Simple problem. Given example tables:
Table A:
id | type
---+-----
1 | A
2 | B
3 | C
Table B:
id | a_id | type
---+------+-----
1 | 1 | X
2 | 2 | Y
3 | 1 | X
4 | 3 | Z
(there are additional columns, which I omitted, in order to clarify the problem)
The query:
SELECT a.*
FROM a a
INNER JOIN b b ON b.a_id = a.id
WHERE b.type = 'X'
Result:
id | type
---+-----
1 | A
1 | A
SQL Fiddle: http://sqlfiddle.com/#!2/e6138f/1
But I only want to have distinct rows of Table A. I know, I could do SELECT DISTINCT a.*, but our Table A has about 40 columns, and this SELECT can return 100-10000 rows. Isn't that extremely slow, if the database has to compare each column?
Or is MySQL intelligent enough, to just focus on the Primary Key for the DISTINCT operation?
Thanks in advance :)
Use exists instead of an explicit join:
select a.*
from tablea a
where exists (select 1 from tableb b where b.a_id = a.id and b.type = 'x');
For performance, create an index on tableb(a_id, type).
My question is almost identical to SQL DELETE with INNER JOIN ; but I want to delete on non equal!
MY PROBLEM IN A BRIEF:
There are 2 tables, bus_stops, bus_routes ;
bus_routes {id, bus_route_id,..other columns..}
bus_stops {id, bus_route_id,..other columns..}
Some routes had been deleted, but bus stops remaining, I need to delete them too. Means, I need to delete only bus_stops, which have NO associated bus route!
It means something like:
DELETE bs.* FROM bus_stops AS bs
INNER JOIN bus_routes AS br
ON bs.bus_route_id <> br.bus_route_id
But the above code will definitely not work.
You should use LEFT JOIN, below query will work:
DELETE bs.*
FROM bus_stops AS bs
LEFT JOIN bus_routes AS br
ON bs.bus_route_id = br.bus_route_id
WHERE br.bus_route_id IS NULL
A join in SQL is first of all the Cartesian product of both tables. Meaning every record of table A is combined with every record of table B. The join condition then reduces the records by eleminating records that do not match the condition.
If you use an INNER JOIN with not equal (<>) every record is going to be deleted if you have at least to distinct values. A small example:
Table A | B Table C | D
============= =============
| 1 | 1
| 2 | 2
The Cartesian product of A X B is:
| B | D
==========
| 1 | 1
| 1 | 2
| 2 | 1
| 2 | 2
If you now use B <> C to select the values, the result will be:
| B | D
==========
| 1 | 2
| 2 | 1
This would delete both records.
As a solution try an outer join or a subquery.
Example (subquery):
DELETE FROM C WHERE NOT EXISTS(SELECT * FROM A WHERE A.B = C.d)
Example (outer join):
DELETE FROM C LEFT JOIN A ON C.D = A.B WHERE A.B IS NULL
I have a scenario where there are two tables (tables A and B) linked in a one to many relationship. For a row in table A, the maximum number of linked rows in B is two, and these two rows (if they exist) are different from each other through a type column whose value is either x or y.
Aid | Name Bid | type | Aid
1 | name1 1 | x | 1
2 | name2 2 | x | 2
3 | name3 3 | y | 2
Now, what I want is to have a join query for the two tables in such a way that all rows in A will be displayed (no repetition) and two columns called type x and type y will hold a boolean / integer value to show the existence of types x and y for each row in A. i.e,
Aid | Name | Type X | Type Y |
1 | name1 | X | NULL |
2 | name2 | X | Y |
3 | name3 | NULL | NULL |
My DBMS is MySql.
Thanks.
You have to use two joins:
SELECT A.*, b1.type AS typeX, b2.type as typeY
FROM A
LEFT JOIN B b1
ON A.aid = b1.aid
AND b1.type = 'x'
LEFT JOIN B b2
ON a.aid = b2.aid
AND b2.type = 'y'
Well, this happens because your second table uses the EAV-model. If you had two tables, one for type_x and one for type_y, your relational schema would be a lot cleaner.
Offcourse, EAV does work, be it more clumsily:
SELECT a.aid, a.name, bx.type, by.type
FROM table_a a
LEFT JOIN table_b bx
ON a.aid = bx.aid
AND bx.type = 'x'
LEFT JOIN table_b by
ON a.aid = by.aid
AND by.type = 'y'
Say I have three tables, A, B and C. Conceptually A (optionally) has one B, and B (always) has one C.
Table A:
a_id
... other stuff
Table B:
a_fk_id (foreign key to A.a_id, unique, primary, not null)
c_fk_id (foreign key to C.c_id, not null)
... other stuff
Table C:
c_id
... other stuff
I want to select All records from A as well as their associated records from B and C if present. However, the B and C data must only occur in the result if both B and C are present.
I feel like I want to do:
SELECT *
FROM
A
LEFT JOIN B on A.a_id=B.a_fk_id
INNER JOIN C on B.c_fk_id=C.c_id
But Joins seem to be left associative (the first join happens before the second join), so this will not give records from A that don't have an entry in C.
AFAICT I must use sub queries, something along the lines of:
SELECT *
FROM
A
LEFT JOIN (
SELECT * FROM B INNER JOIN C ON B.c_fk_id=C.c_id
) as tmp ON A.id = tmp.a_fk_id
but once I have a couple of such relationships in a query (in reality I may have two or three nested), I'm worried both about code complexity and about the query optimizer.
Is there a way for me to specify the join order, other than this subquery method?
Thanks.
In SQL Server you can do
SELECT *
FROM a
LEFT JOIN b
INNER JOIN c
ON b.c_fk_id = c.c_id
ON a.id = b.a_fk_id
The position of the ON clause means that the LEFT JOIN on b logically happens last. As far as I know this is standard (claimed to be ANSI prescribed here) but I'm sure the downvotes will notify me if it doesn't work in MySQL!
Edit: And that's what I get for talking faster than I think. My previous solution doesn't work because 'c' hasn't been joined yet. Let's try this again.
We can use a WHERE clause to limit the results to only those that match the criteria you're looking for, where C has a valid (IS NOT NULL) or B does not have a value (IS NULL). Like this:
SELECT *
FROM a
LEFT JOIN b ON (b.a = a.a)
LEFT JOIN c ON (c.b = b.b)
WHERE (c.c IS NOT NULL OR b.b IS NULL);
Without WHERE Results:
mysql> SELECT * FROM a LEFT JOIN b ON (b.a = a.a) LEFT JOIN c ON (c.b = b.b);
+------+------+------+------+------+
| a | a | b | c | b |
+------+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 2 | NULL | NULL |
| 2 | 2 | 3 | 2 | 3 |
| 3 | NULL | NULL | NULL | NULL |
| 4 | NULL | NULL | NULL | NULL |
+------+------+------+------+------+
With WHERE Results:
mysql> SELECT * FROM a LEFT JOIN b ON (b.a = a.a) LEFT JOIN c ON (c.b = b.b) WHERE (c.c IS NOT NULL OR b.b IS NULL);
+------+------+------+------+------+
| a | a | b | c | b |
+------+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 3 | 2 | 3 |
| 3 | NULL | NULL | NULL | NULL |
| 4 | NULL | NULL | NULL | NULL |
+------+------+------+------+------+
Yes, you use the STRAIGHT_JOIN for this.
When using this keyword the join will occur in the exact order that you specify.
See: http://dev.mysql.com/doc/refman/5.5/en/join.html
Well, I thought up another solution as well, and I'm posting it for completeness (Though I'm actually using Martin's answer).
Use a RIGHT JOIN:
SELECT
*
FROM
b
INNER JOIN c ON b.c_fk_id = c.c_id
RIGHT JOIN a ON a.id = b.a_fk_id
I'm pretty sure every piece I've read about JOINS said that RIGHT JOINs were pointless, but there you are.