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)
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
Details:
V and C are tables
I want C to join V on 2 conditions(two C are named C0 and C1). Since I must filter V by columns of C and C's child tables separately in the same query, I think joining is needed.
V has many, at least one C, and either Cx sometimes doesn't exist.
Because of 3., using inner join to C0 and C1 sometimes cause empty result, but I don't want get V's white area.
Schema:
V (id)
C (id, v_id, name, type)
Query(1):
SELECT V.*
FROM V
INNER JOIN C AS C0 ON V.id = C0.v_id WHERE C0.type = 0
INNER JOIN C AS C1 ON V.id = C1.v_id WHERE C1.type = 1
WHERE C0.name = 'word' or C1.name = 'word'
WHERE C0.name like '%wo%'
Query (2):
SELECT V.*
FROM V
INNER JOIN C AS C0 ON V.id = C0.v_id WHERE C0.type = 0
INNER JOIN C AS C1 ON V.id = C1.v_id WHERE C1.type = 1
WHERE C0.name = 'word' or C1.name = 'word'
WHERE C1.name like '%wo%'
WHERE C0.name = 'word' or C1.name = 'word': This clause exists every time.
WHERE Cx.name like '%wo%': This clause depends on the time.
Well... You seem to be looking for an EXISTS condition with correlated subquery:
SELECT * FROM v
WHERE EXISTS (SELECT 1 FROM c WHERE c.c0 = v.c0 OR c.c1 = v.c1)
This will return all records in table v for which at least one record exists in table c that satisfies either condition c.c0 = v.c0 or condition c.c1 = v.c1.
In this situation, the advantage of the correlated subquery against the JOIN approach is that it avoids multiplying records when more than one match exists in table c.
If you want to exclude records that meet either condition instead of including them, then just change WHERE EXISTS to WHERE NOT EXISTS.
bad ask / details of the question, knowing a better context of what you want, even if abbreviated fields and actual tables could help better in the future. However, it looks like what you want is an "IN" condition based on EITHER of the "C" aliased conditions. Without seeing them, you can look at it like this (or simplified both criteria in second).
select
v.*
from
VTable v
LEFT JOIN CTable C0
on v.SomeField = C0.SomeField
LEFT JOIN CTable C1
on v.SomeOtherField = C1.SomeOtherField
where
c0.SomeField IS NOT NULL
OR c1.SomeField IS NOT NULL
OR... simplified with a single left-join, combining both criteria
select
v.*
from
VTable v
LEFT JOIN CTable C0
on ( ( v.SomeField = C0.SomeField )
OR ( v.SomeOtherField = C0.SomeOtherField ) )
where
c0.SomeField IS NOT NULL
I used extra parenthesis in the above query in case the original C0 and C1 conditions were complex in themselves.
So what you described as the white area in the "V" table is in simple English is give me everything from the V table that does NOT match EITHER of the C0 or C1 conditions. Hence the left-join and then ENSURING they DO exist by checking for any column (expect a key column though) to explicitly BE NOT NULL (hence it does exist in the C table criteria).
If you just want columns from v, then exists comes to mind.
I would phrase this as:
select v.*
from v
where exists (select 1
from c
where v.id = c.v_id and
c.type = 0
) or
exists (select 1
from c
where v.id = c.v_id and
c.type = 1
) ;
This can take advantage of an index on c(v_id, type).
It should also be fine to phrase this as:
select v.*
from v
where exists (select 1
from c
where v.id = c.v_id and
c.type in (0, 1)
);
Or even:
select v.*
from v
where v.id in (select c.v_id
from c
where c.type in (0, 1)
);
The key idea is that by doing the comparison in the where clause, you are guaranteeing that rows cannot be duplicated.
You don't need to join table c two times, you can write your query like following.
select V.*
from V as V
inner join C as C on C.v_id=V.id
where C.name='word' and C.type in(0,1)
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.
Sorry if the title is a bit confusing. Basically, I have three tables:
MainTable
ItemsTable
ObjectsTable
Where the ID (PK) on MainTable are foreign keys on ItemsTable and ObjectsTable (So MainTable will never return null in my query).
Now the goal is to join them all on ID and then execute a WHERE clause using all three tables. Here is the query:
select * from MainTable as a
left outer join ItemsTable as b
on a.ID = b.ID
left outer join ObjectsTable as c
on a.ID = c.ID
where
a.ID = 9999 AND
(a.StatusOne = 1 and b.StatusOne = 1 and c.StatusOne = 1) AND
(a.StatusTwo != 1 or b.StatusTwo != 1 or c.StatusTwo != 1)
The idea of the query is that if it returns any results, a variable in my code is set to true and false if vice versa.
This works perfectly only if every table has an ID. However, I ran in to a case where ItemsTable doesn't have any records with that ID, and thus the query returned no results, when it should have.
My question is:
How can I ignore a NULL joined table in my WHERE clause? So if ItemsTable is NULL, I still want to execute the condition, just without b.StatusOne and b.StatusTwo
Move the qualifying statement to the Where clause makes your left outer join INNER Joins. Below will work.
select * from MainTable as a
left outer join ItemsTable as b
on a.ID = b.ID and b.StatusOne = 1 and b.StatusTwo != 1
left outer join ObjectsTable as c
on a.ID = c.ID c.StatusOne = 1 and c.StatusTwo != 1
where
a.ID = 9999 AND
(a.StatusOne = 1 and
(a.StatusTwo != 1)
I would move those related conditions to the JOIN ON clause instead in WHERE
left outer join ItemsTable as b
on a.ID = b.ID and ( b.StatusOne = 1 or b.StatusTwo != 1)
left outer join ObjectsTable as c
on a.ID = c.ID and (and c.StatusOne = 1 or c.StatusTwo != 1)
where a.ID = 9999 AND (a.StatusOne = 1 or a.StatusTwo != 1);
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.