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.
Related
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
I have a table addresses and many tables that has a address_id column that references an address (all have foreign key contraints). I want to find the number of addresses that are not referenced from any of the other tables.
I've tried the following query, but it is VERY slow:
SELECT COUNT(*)
FROM addresses A
LEFT JOIN customers C ON C.address_id = A.id
LEFT JOIN agreements AG ON AG.address_id = A.id
LEFT JOIN products P ON P.address_id = A.id
LEFT JOIN letters L ON L.address_id = A.id
WHERE C.id IS NULL
AND AG.id IS NULL
AND P.id IS NULL
AND L.id IS NULL
Is there any way I can do this, without the query taking forever?
I would start by rewriting this with not exists:
select count(*)
from addresses a
where
not exists (select 1 from customers c where c.address_id = a.id)
and not exists (select 1 from agreements g where g.address_id = a.id)
and not exists (select 1 from products p where p.address_id = a.id)
and not exists (select 1 from letters l where l.address_id = a.id)
Then, make sure to have the following indexes in place to speed up the query:
customers(address_id)
agreements(address_id)
products(address_id)
letters(address_id)
If you have properly defined the address_id columns as foreign keys to address(id), then these indexes are already there.
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.
Here is a simple database schema, according to the schema, I would like to find the highest salary among all employees, presenting the information about that employee (a_id, a_name, b_area).
There will be only one result return from table D and I try to return the employment_id to link with other tables but then it returns more than 1 result. Please check my query underneath, thank you very much :)
SELECT
a.a_id,
a.a_name,
b.b_area
FROM
A a, B b, C c
LEFT JOIN (SELECT d.employee_id, MAX(d.salary) FROM D d)
ON d.employee_id= c.employee_id;
Start with D, then join the other tables as needed:
... D left join c on (d.employee_id = c.employee_id) left join a on ...
And the highest salary bit would be something like:
... where salary = (select max(salary) from d)
Keep in mind that this will return multiple results if there is more than one employee having the maximum salary.
You miss a join condition for your tables. This should do what you need.
SELECT a.a_id, a.a_name, b.b_area
FROM C
JOIN A ON a.a_id = c.a_id
JOIN B ON b.b_id = c.c_id
JOIN D ON d.employee_id = c.employee_id
WHERE d.salary = (SELECT max(salary) FROM D)
SELECT D.*, a.a_name, b.b_area FROM D
LEFT JOIN c ON d.employee_id = c.employee_id
LEFT JOIN a ON a.a_id = c.a_id
LEFT JOIN b ON b.b_id = c.a_id
ORDER BY D.salary DESC LIMIT 1
This will output one row as:
employee_id | salary | a_name | b_area
There are several ways:
Subselect-Solution:
SELECT A.a_name
,B.b_area
,maxsalary.MaxSal
FROM
(
SELECT D.employee_id
,MAX(D.Salary) as MaxSal
FROM D
GROUP BY D.employee
) maxsalary
INNER JOIN C
ON C.employee_id = maxsalary.employee_id
INNER JOIN B
ON C.b_id = B.b_id
INNER JOIN A
ON C.a_id = A.a_id
This solution might be more performant. As SUBSELECT maxsalary will return just one row.
I think you use HAVING Clause,
SELECT a.a_id, a.a_name, b.b_area FROM A a, B b, C c LEFT JOIN D d ON c.employee_id = d.employee_id
GROUP BY d.salary
HAVING MAX(d.salary);
I'm not that into MySQL joins, so maybe you could give me a hand. I've got the following tables:
Table a
Fields ID,name
Table b
Fields aID,cID,ID,found
Table c
Fields ID,name
The result I want to get is the following: I want all the records where b.found = 1. Of these records I don't want a.id or a.name, but I want the number of records that would have been returned if I would have wanted so. So if there are five records that have b.found = 1 and c.id = (for example) 3, then I want a returned value of 5, c.id and c.name.
Someone is able to do this?
Actually this is what I want to get from the database:
A list of all records in table C and a count of records in table B that has found = 1 and b.c_id = c.id
Table: a
Fields: ID, name
Table: b
Fields: aID, cID, found
Table: c
Fields: ID, name
SELECT c.ID, c.name, COUNT(1)
FROM b
JOIN c ON c.ID = b.cID AND b.found=1
GROUP BY c.ID
SELECT c.id, c.name, COUNT(*)
FROM c
INNER JOIN b
ON c.id = b.c_id
AND b.found = 1
GROUP BY c.id, c.name
SELECT COUNT(*), c.id, c.name
FROM a, b, c
WHERE a.id = b.a.id AND c.id = b.a.id AND b.found = 1 AND c.id = idThatIAmSearchingFor
Apologies if I didn't get the syntax exact, but I believe that's the basic structure you want. The COUNT function returns the number of rows found by the query.
Something like:
SELECT count(`c`.*),
`c`.`id`,
`c`.`name`
FROM `b`
JOIN `c`
ON `c`.`id` = `b`.`c_id`
WHERE `b.found` = 1
I think this would provide the required output -
select count(*), b.cID, c.name from b
inner join c on c.id=b.cID and b.found=1
group by b.cID
SELECT COUNT(*) AS Count, c.id, c.name
FROM b join a on a.id = b.a_id
WHERE b.found = 1
GROUP BY c.Id;
COUNT returns count of records in each group from GROUP BY.