Multi join one to many - mysql

Trades
id |Trade_name |
1 | trade1 |
2 | trade2 |
3 | trade3 |
4 | trade4 |
Users
Name | Primary_id(FK to trade.id) | secondary_id (FK to trade.id) |
John | 1 | 2 |
Alex | 3 | 4 |
This is my current SQL which joins trades.t1 to primary & secondary.id:
select
`users`.`name` ,
`t1`.`trade_name` AS `Primary_trade`,
`t2`.`trade_name` AS `Secondary_trade`,
FROM `users`
right JOIN `trades` `t1` On (`t1`.`trade_id` = `users`.`primary_trade_id`)
right JOIN `trades` `t2` on (`t2`.`trade_id` = `users`.`secondary_trade_id`)
My question is, how do I identify which trades are not used for users both as primary or secondary. I want to see record where a trade does not exist in both primary or secondary column so I can perform housekeeping.
Thanking you all in advance for your help.

If you need only the trades rows
SELECT t.*
FROM trades t
WHERE NOT EXISTS ( SELECT 'u'
FROM Users u
WHERE u.Primary_id = t.id
OR u.Secondary_id = t.id
)

I think this should work for you:
SELECT * FROM trades WHERE id NOT IN (SELECT Primary_id FROM Users) AND id NOT IN (SELECT Secondary_id FROM Users)
It selects the rows which are not in either primary_id nor secondary_id

Related

MySQL join two tables without NOT IN

Two tables users, relationships in my db.
CREATE TABLE users(
id int primary key auto_increment,
nickname varchar(20),
is_active TINYINT
)
CREATE TABLE relationships(
id int primary key auto_increment,
user_id int,
target_user_id int,
FOREIGN KEY(user_id) REFERENCES users(id),
FOREIGN KEY(target_user_id) REFERENCES users(id)
)
mysql> select * from users;
+----+----------+-----------+
| id | nickname | is_active |
+----+----------+-----------+
| 1 | hide | 1 |
| 2 | john | 1 |
| 3 | ben | 0 |
| 4 | test | 1 |
| 5 | gogo | 1 |
+----+----------+-----------+
mysql> select * from relationships;
+----+---------+----------------+
| id | user_id | target_user_id |
+----+---------+----------------+
| 1 | 1 | 2 |
| 2 | 1 | 4 |
+----+---------+----------------+
I have to extract users.id with certain condition.
I will explain in case of users.id = 1
users.is_active=1
user who does not have relationships via relationships table. you know that in current relationships table, user_id = 1 has 2 rows that target_user_id = 2 and 4. So query result does not contain user_id = 2 and 4.
Using NOT IN, it is pretty simple.
SELECT id FROM users WHERE is_active=1 AND id NOT IN(SELECT target_user_id FROM relationships WHERE user_id=1)
RESULT : 1, 5
Note that there is huge rows in users and relationships.
If I using NOT IN with subquery, it will occur performance issue.
So I think I have to join with foreign key but I don't know how to make query exactly.
Any suggestion, appreciate.
Thanks.
TRY THIS: I am sure LEFT JOIN with IS NULL approach will definitely work for you
SELECT u.id
FROM users u
LEFT JOIN relationships r ON r.target_user_id = u.id
AND r.user_id = 1
WHERE u.is_active=1 AND r.target_user_id IS NULL
Nothing wrong with your query. MySQL should be able to use your index.
However, you can also use left join:
SELECT
users.id
FROM
users
LEFT JOIN relationships ON (
users.id = relationships.target_user_id
/*
-- un-comment if filtering by user is needed
AND relationships.user_id = 1
*/
)
WHERE
users.is_active=1
AND relationships.target_user_id IS NULL
-- addition filtering can be here
UPDATE:
If you filtering by user, you can try to add composite index (user_id, target_user_id) to relationships table (columns should be in this order)

mysql: How to exclude rows from table which exist in table_alias with good perfomanse?

I've sql with NOT EXIST and it works very slowly in big db:
SELECT *
FROM
(
SELECT * FROM profiles ORDER BY id DESC
/* I need this order HERE! More info: https://stackoverflow.com/q/43516402/2051938 */
) AS users
WHERE
NOT EXISTS (
SELECT *
FROM request_for_friendship
WHERE
(
request_for_friendship.from_id = 1
AND
request_for_friendship.to_id = users.id
)
OR
(
request_for_friendship.from_id = users.id
AND
request_for_friendship.to_id = 1
)
)
LIMIT 0 , 1;
And I think I need to get request_for_friendship with some WHERE and after that check NOT EXIST, like this:
SELECT users.*
FROM
(
SELECT * FROM profiles ORDER BY id DESC
) AS users,
(
SELECT *
FROM request_for_friendship
WHERE
request_for_friendship.from_id = 1
OR
request_for_friendship.to_id = 1
) AS exclude_table
WHERE
NOT EXISTS
(
SELECT *
FROM exclude_table /* #1146 - Table 'join_test.exclude_table' doesn't exist */
WHERE
request_for_friendship.from_id = users.id
OR
request_for_friendship.to_id = users.id
)
LIMIT 0 , 1;
But it doesn't work: #1146 - Table 'join_test.exclude_table' doesn't exist
My tables:
1) profiles
+----+---------+
| id | name |
+----+---------+
| 1 | WILLIAM |
| 2 | JOHN |
| 3 | ROBERT |
| 4 | MICHAEL |
| 5 | JAMES |
| 6 | DAVID |
| 7 | RICHARD |
| 8 | CHARLES |
| 9 | JOSEPH |
| 10 | THOMAS |
+----+---------+
2) request_for_friendship
+----+---------+-------+
| id | from_id | to_id |
+----+---------+-------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 8 |
| 5 | 4 | 1 |
| 6 | 9 | 1 |
+----+---------+-------+
How to do some like this or better for perfomance?
p.s. I need to get only 1 row from table
Demo: http://rextester.com/DTA64368
I've already tried LEFT JOIN, but I've problem with order with him. mysql: how to save ORDER BY after LEFT JOIN without reorder?
First, do not use subqueries unnecessarily. Second, split the NOT EXISTS into two conditions:
SELECT p.*
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.from_id = 1 AND
rff.to_id = p.id
) AND
NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.to_id = 1 AND
rff.from_id = p.id
)
ORDER BY id DESC;
This can now make use of two indexes: request_for_friendship(to_id, from_id) and request_for_friendship(from_id, to_id). Each index is needed for one of the NOT EXISTS conditions.
I still think there's ways to optimize this as 'in' is generally slower.
SELECT *
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship
WHERE (request_for_friendship.from_id,
request_for_friendship.to_id)
in ((1,p.id),
(p.id,1))
)
Get rid of the id in request_for_friendship. It wastes space and performance. The table has a "natural" PRIMARY KEY, which I will get to in a moment.
Since it seems that the relationship seems to commutative, let's make use of that by sorting the from and to -- put the smaller id in from and the larger is to. See LEAST() and GREATEST() functions.
Then you need only one EXISTS(), not two. And have
PRIMARY KEY(from_id, to_id)
Now to rethink the purpose of the query... You are looking for the highest id that is not "related" to id #1, correct? That sounds like a LEFT JOIN.
SELECT
FROM profiles AS p
LEFT JOIN request_for_friendship AS r ON r.to = p.id AND r.from = 1
WHERE r.to IS NULL
ORDER BY id DESC
LIMIT 1;
This may run about the same speed as the EXISTS -- Both walk through profiles from the highest id, reaching into the other table to see if a row is there.
If there is no such id, then the entire profiles table will be scanned, plus a the same number of probes into the other table.

Update MySQL table with another table's data

I read this and this, but I need to make a GROUP BY query to set another table.
logintable is like this:
id | name | login_date
------------------------
1 | michael | 2013-01-04
2 | michael | 2013-01-08
3 | mary | 2013-01-11
4 | john | 2013-01-15
5 | michael | 2013-01-19
6 | mary | 2013-01-22
7 | john | 2013-01-26
I make a query like this:
SELECT * FROM logintable GROUP BY name ORDER BY id ASC
This gives me first login date of the users:
1 | michael | 2013-01-04
3 | mary | 2013-01-11
4 | john | 2013-01-15
I have another table called userstable like this:
id | name | last_seen_date | first_login_date
------------------------------------------------
1 | michael | 2013-02-02 |
2 | john | 2013-02-04 |
3 | mary | 2013-02-16 |
I need to update userstable's first_login_date column, with the first result. How can I do this ?
(I have 75k records in logintable and 10k records in userstable)
UPDATE userstable a
INNER JOIN
(
SELECT name, min(login_date) min_date
FROM logintable
GROUP BY name
) b ON a.name = b.Name
SET a.first_login_table = b.min_date
for faster performance, you need to add an INDEX on column Name for both tables. This will prevent from performing full table scan which is very slow in large databases.
Adding INDEX on table userstable:
ALTER TABLE usersTable ADD INDEX (Name);
for referential integrity, make table logintable dependent on table userstable by defining FOREIGN KEY constraint:
ALTER TABLE loginTable ADD CONSTRAINT tb_fk
FOREIGN KEY (Name) REFRENCES usersTable (Name)
UPDATE userstable AS u
INNER JOIN
(
SELECT MIN(login_date) AS MinLoginDate, Name
FROM logintable
GROUP BY name
) AS l ON u.name = l.name
SET u.first_login_date = l.MinLoginDate
I don't know MySql...but in SQL Server you would write something like this:
UPDATE userstable set first_login_date = (SELECT MIN(login_date) FROM logintable where name = userstable.name)
UPDATE userstable a
INNER JOIN
(
SELECT name, min(login_date) min_date
FROM logintable
GROUP BY name
) b ON a.name = b.Name
SET a.first_login_table = b.min_date
This will definitely show your output.

Select the fields are duplicated in mysql

Assuming that I have the below customer_offer table.
My question is:
How to select all the rows where the key(s) are duplicated in that table?
+---------+-------------+------------+----------+--------+---------------------+
| link_id | customer_id | partner_id | offer_id | key | date_updated |
+---------+-------------+------------+----------+--------+---------------------+
| 1 | 99 | 11 | 14 | mmmmmq | 2011-09-21 12:40:46 |
| 2 | 100 | 11 | 14 | qmmmmq | 2011-09-21 12:40:46 |
| 3 | 101 | 11 | 14 | 8mmmmq | 2011-09-21 12:40:46 |
| 4 | 99 | 11 | 14 | Dmmmmq | 2011-09-21 12:59:28 |
| 5 | 100 | 11 | 14 | Nmmmmq | 2011-09-21 12:59:28 |
+---------+-------------+------------+----------+--------+---------------------+
UPDATE:
Thanks so much for all your answer. There are many answers are good. Now I got the solution to do.
select *
from customer_offer
where key in
(select key from customer_offer group by key having count(*) > 1)
Update:
As mentioned from #Scorpi0, if with a big table, it is better to use join. And from mysql6.0 the new optimizer will convert this kind of subqueries into joins.
Self join
SELECT * FROM customer_offer c1 inner join customer_offer c2
on c1.key = c2.key
or group by the field then take when count > 1
SELECT COUNT(key),link_id FROM customer_offer c1
group by key, link_id
having COUNT(Key) > 1
SELECT DISTINCT c1.*
FROM customer_offer c1
INNER JOIN customer_offer c2
ON c1.key = c2.key
AND c1.link_id != c2.link_id
Assuming link_id is a primary key.
Use a sub-query to do the count check, and the main query to select the rows. The count check query is simply:
SELECT `link_id` FROM `customer_offer` GROUP BY `key` HAVING COUNT(`key`) > 1
Then the outer query will use this by joining into it:
SELECT customer_offer.* FROM customer_offer
INNER JOIN (SELECT `link_id` FROM `customer_offer` GROUP BY `key` HAVING COUNT(`key`) > 1) AS count_check
ON customer_offer.link_id = count_check.link_id
There are many threads on the mysql website which explains how to do this. This link will explain how to do this using mysql: http://forums.mysql.com/read.php?10,180556,180567#msg-180567
As a brief example the code below is from the link with a slight modification which better suits your example.
SELECT *
FROM tbl
GROUP BY key
HAVING COUNT(key)>1;
You can also use a joing which is my prefered method, as this removes the slower count method:
SELECT *
FROM this_table t
inner join this_table t1 on t.key = t1.key
SELECT link_id, key, count(key) as Occurrences
FROM table
GROUP BY key
HAVING COUNT(key)>1;

SQL: How to return an non-existing row?

Say I have the following table:
=================================================
| color_id | parent_id | language_id | name |
=================================================
| 1 | 50 | 1 | Black |
-------------------------------------------------
Then say I need the row WHERE parent_id = 50 AND language_id = 2. Obviously, I would get nothing back based on my example table. However, I still need a result -- probably something like this:
=================================================
| color_id | parent_id | language_id | name |
=================================================
| NULL | 50 | 2 | NULL |
-------------------------------------------------
Is there a way to do this in SQL?
You could do a union query of both the potentially valid record and your default, then select the first one:
SELECT * FROM
(SELECT color_id, parent_id, language_id, name, 1 as order_rank
FROM some_table
WHERE parent_id = %parent_id% AND language_id = %language_id%
UNION
SELECT NULL, %parent_id%, %language_id%, NULL, 2 as order_rank
)
ORDER BY order_rank
LIMIT 1
(Edited with static value for ordering as suggested by OMG Ponies)
try working with LEFT JOIN statement. i'm probably not doing this 100% but a bit of trial and error on your part should make this work.
SELECT table1.field1, table1.field2, table2.field3, table2.field4
FROM my_table table1
LEFT JOIN my_table table2 ON table1.field1=table2.field1 OR table1.field2=table2.field2
a left join on a forced fixed value first table SHOULD work.
select
YourTable.color_id,
ForcedSQL1Record.parent_id,
ForcedSQL1Record.language_id,
YourTable.name
from
( select 50 as Parent_ID,
2 as Language_ID
from YourTable
limit 1 ) ForcedSQL1Record
left join
YourTable
on ForcedSQL1Record.Parent_ID = YourTable.Parent_ID
AND ForcedSQL1Record Language_ID = YourTable.Language_ID