select common element from various tables where all values are not null - mysql

I have 3 tables, A,B, and C. Each table has a common column, ID, plus other data.
I want to have all IDs that are in every table and where all data are not null.
What is the most efficent way to do it?
In my real case I have more than 5 tables.
Ex:
Table A
| ID | A_1 | A_2 | A_3 | A_4 | A_5 | A_6 |
Table B
| ID | B_1 | B_2 | B_3 | B_4 |
Table C
| ID | C_1 | C_2 | C_3 | C_4 | C_5 | C_6 | C_7 | C_8 |
This query will take all the ID in common among the three tables.
SELECT distinct ID FROM A
where
exists(select 1 from B where A.ID = B.ID )
AND exists(select 1 from C where A.ID = C.ID );
This query will select ID in which all columns are non null :
SELECT ID
FROM A
WHERE A_1 IS NOT NULL AND A_2 IS NOT NULL AND A_3 IS NOT NULL AND A_4 IS NOT NULL AND A_5 IS NOT NULL;
(I hope there is a better query for this.)
Than I should have the same query for all tables.
And then join all together.
Is there a better solution?

This should work:
SELECT DISTINCT A.ID
FROM A
INNER JOIN B ON A.ID = B.ID AND B.value IS NOT NULL
INNER JOIN C ON A.ID = C.ID AND C.value IS NOT NULL
INNER JOIN D ON A.ID = D.ID AND D.value IS NOT NULL
INNER JOIN E ON A.ID = E.ID AND E.value IS NOT NULL
WHERE A.value IS NOT NULL
;
The downside of it is that if there are one or more one-to-many relationships the intermediate results could proverbially explode. A better solution MIGHT be more similar to your first example; but instead of using correlated EXISTS subqueries....
SELECT DISTINCT ID
FROM A
WHERE A.Value IS NOT NULL
AND A.ID IN (SELECT DISTINCT ID FROM B WHERE Value IS NOT NULL)
AND A.ID IN (SELECT DISTINCT ID FROM C WHERE Value IS NOT NULL)
AND A.ID IN (SELECT DISTINCT ID FROM D WHERE Value IS NOT NULL)
AND A.ID IN (SELECT DISTINCT ID FROM E WHERE Value IS NOT NULL)
...etc
The downside of this is you won't get any benefit from indexes on ID. If the "explosion" issue I mentioned isn't a concern, and ID is indexed on all or most of the tables, my first suggestion would likely be faster and more efficient.
Edit: Oh, I overlooked you meant multiple values in each table NOT NULL, but the same concept should apply, you'll just have to replace all the Value IS NOT NULL conditions with ANDed IS NOT NULL conditions on the appropriate columns.

Related

Verify value of column in table is not equal to the sum of values of two columns in other tables

I have three tables, with the following schema;
Table A
id
b_id
amount
Table B
id
amount
Table C
id
a_id
fee_amount
Tables A and B are related directly, however, table C has a_id that may be NULL.
I am trying to put together an SQL query to return the rows whose value of A.amount != B.amount + C.amount.
So far I have this;
SELECT a.*
FROM a
LEFT OUTER JOIN b ON b.id = a.b_id
LEFT OUTER JOIN c ON c.a_id = a.id
WHERE a.amount > 5000 AND a.amount != (b.amount + c.fee_amount)
With a simple dataset like this;
Table A
| id | b_id | amount |
|----|------|--------|
| 1 | 1 | 50000 |
Table B
| id | amount |
|----|--------|
| 1 | 5000 |
Table C
| id | a_id | fee_amount |
|----|------|------------|
| 1 | 1 | 7000 |
And the query returns zero results. I'm pretty I've missed something, just not sure what it is.
I think you need to handle the NULLs by treating them as 0 - eg:
SELECT a.*
FROM a
LEFT OUTER JOIN b ON b.id = a.b_id
LEFT OUTER JOIN c ON c.a_id = a.id
WHERE a.amount > 5000 AND a.amount != (IFNULL(b.amount, 0) + IFNULL(c.fee_amount,0))
First, if only C can have the NULL value, then you are better off using the LEFT JOIN only for that table:
SELECT a.*
FROM a JOIN
b
ON b.id = a.b_id LEFT JOIN
c
ON c.a_id = a.id
WHERE a.amount > 5000 AND
a.amount <> (b.amount + COALESCE(c.fee_amount, 0)) ;
However, normally you have secondary tables like this because the tables can have multiple rows per a_id. If that is the case, you need some sort of aggregation.

How to select data across multiple MySQL tables with same ID

I had create two table (Table A and Table B) and try using INNER JOIN both table but the result will be in different row even the ID is same.
$sql = "SELECT * FROM Table A INNER JOIN Table B ON Table A.ID = Table B.ID";
Is there any mysql select to extract all the value with same ID in one row as shown in 'Result' table?
Code:
$sql =
"SELECT GROUP_CONCAT(ITEM1), GROUP_CONCAT(ITEM2)
FROM Table A INNER JOIN Table B
ON Table A.ID = Table B.ID
WHERE Table A.ID = Table B.ID ";
Image
You must group by id, type and use group_concat() only for the item columns.
From your sample data it seems like one of item1 or item2 in each row is null.
If this is the case then:
select
a.id, a.type,
group_concat(coalesce(b.item1, b.item2) order by b.sub_id) item
from tablea a inner join tableb b
on b.id = a.id
group by a.id, a.type
See the demo.
If they can be both null or both not null:
select
a.id, a.type,
group_concat(concat_ws(',', item1, item2) order by b.sub_id) item
from tablea a inner join tableb b
on b.id = a.id
group by a.id, a.type
See the demo.
Results:
| id | type | item |
| --- | --------- | --------------------------- |
| 1 | FRUIT | BANANA,PINEAPPLE,WATERMELON |
| 2 | VEGETABLE | SPINACH,CARROT |
| 3 | MEAT | CHICKEN |

SQL: Update table by mapping two columns to each other

I have the following two tables:
Table A
+-------------------+
|___User___|__Value_|
| 3 | a |
| 4 | b |
| 5 | c |
|____6_____|__d_____|
Table B
+-------------------+
|___User___|__Value_|
| 1 | |
| 4 | |
| 5 | |
|____9_____|________|
My job is to take user from Table A (and their correspondings value) and then map it to Table B and insert those values in there. So from the above example Table B should look like this after running the script:
Table B
+-------------------+
|___User___|__Value_|
| 1 | |
| 4 | b |
| 5 | c |
|____9_____|________|
My question is how can I construct an SQL query that will do this for me in an efficient way, if Table A contains 300,000 + entries and Table B contains 70,000 entries?
NOTES: In Table A the User field is not unique and neither is the Value field. However in Table B, both the User and Value fields are unique and should not appear more than once. Neither are primary keys for either tables.
Could be this
update table_b as b
inner join table_a as a on a.User = b.User
set b.value = a.value
In real-world situations, it would be more likely that you want a predictable value, such as the greatest value for any given user. In that case you would want
update table_b as b
inner join (
select user, max(value) from table_a
group by user ) as a_max on a.user = b.user
set b.value = a_max.value
Your question is unclear about what to do about any values that are already in b. If you use a left join, then these will explicitly be set to NULL:
update table_b b left join
table_a a
on a.User = b.User
set b.value = a.value;
If you want to keep the existing values for non-matches, then use inner join.
Note that this might be inefficient, but should be ok if an index exists on a(user).
If you had very few users in a and lots and lots of duplicates, then you might want to aggregate a before doing the join.

Fastest way of doing a 1 to 1 left join on tables with a 1 to many relationship (MySQL)

I have two tables that have a 1 to many relationship, which I'm doing a 1:1 left join on. The query returns the correct results but it shows up in my slow query log (it takes up to 5s). Is there a better way to write this query?
select * from
tablea a left join tableb b
on a.tablea_id = b.tablea_id
and b.tableb_id = (select max(tableb_id) from tableb b2 where b2.tablea_id = a.tablea_id)
i.e. I would like TableA left joined to the row in TableB with the largest tableb_id.
TableA
tablea_id
1
2
TableB
tableb_id, tablea_id, data
1, 1, x
2, 1, y
Expected Result
tablea_id, tableb_id, data
1, 2, y
2, null, null
TableA has an index on tablea_id and TableB has a composite index on tablea_id,tableb_id.
Explain Output
+----+--------------------+---------------+--------+-----------------+---------------+---------+----------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+---------------+--------+-----------------+---------------+---------+----------------------+-------+-------------+
| 1 | PRIMARY | c | index | NULL | department_id | 4 | NULL | 18966 | Using index |
| 1 | PRIMARY | recent_cv_lut | eq_ref | PRIMARY,case_id | PRIMARY | 4 | func | 1 | |
| 2 | DEPENDENT SUBQUERY | cases_visits | ref | case_id | case_id | 4 | abcd_records_v2.c.id | 2 | Using index |
+----+--------------------+---------------+--------+-----------------+---------------+---------+----------------------+-------+-------------+
Likely, that correlated subquery is getting executed for each row from tableb.
(Without the output from EXPLAIN, we're really just guessing as to whether appropriate indexes are available, and if MySQL is making use of them.)
It might be more efficient to use an inline view query, to get the maximum tableb_id value for each tablea_id in one shot, and then use a join operation. Something like this:
SELECT a.*
, b.*
FROM tablea a
LEFT
JOIN ( SELECT n.tablea_id
, MAX(n.tableb_id) AS max_tableb_id
FROM tableb n
GROUP
BY n.tablea_id
) m
ON m.tablea_id = a.tablea_id
LEFT
JOIN tableb b
ON b.tablea_id = m.tablea_id
AND b.tableb_id = m.max_tableb_id
That's an alternative, but there's no guarantee that's going to be faster. It really depends, on a whole load of things that we don't have any information about. (Number of rows, cardinality, datatypes, available indexes, etc.)
EDIT
As an alternative, we could do the join between tablea and tableb in an inline view. This might improve performance. (Again, it really depends on a lot of things we don't have any information about.)
SELECT m.tablea_id
, m.foo
, b.*
FROM ( SELECT a.tablea_id
, a.foo
, MAX(n.tableb_id) AS max_tableb_id
FROM tablea a
LEFT
JOIN tableb n ON n.tablea_id = a.tablea_id
GROUP
BY a.tablea_id
) m
LEFT
JOIN tableb b
ON b.tablea_id = m.tablea_id
AND b.tableb_id = m.max_tableb_id

How do I inner join multiple tables?

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.