Getting rows from two tables with a join and where clause - mysql

I am needing to get users from a 'users' table that are not equal to my username, but also not in a blocked table as the 'blockee'.
Using only the users table, I am doing:
SELECT * FROM users WHERE username != <myself> AND interests LIKE <string>;
However, since I've added the additional 'blocked' table, I'm needing to further filter them.
mysql> describe users;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | | NULL | |
| city | varchar(60) | NO | | NULL | |
| state | varchar(60) | NO | | NULL | |
| lat | decimal(8,6) | YES | | NULL | |
| lng | decimal(9,6) | YES | | NULL | |
| interests | text | YES | | NULL | |
| hash | varchar(32) | YES | | NULL | |
| active | int(1) | YES | | NULL | |
| avatar | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
12 rows in set (0.00 sec)
mysql> describe blocked;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| blocker | varchar(255) | NO | | NULL | |
| blockee | varchar(255) | NO | | NULL | |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
I attempted:
SELECT * FROM users JOIN blocked ON users.username = blocked.blocker WHERE username != 'myself' AND blocked.blockee IS NULL;
A pseudo-query would look like:
"give me every user that's not equal to my username, but is also not a blockee where I am the blocker"
So, if I had these values in the blocked table:
+----+-----------+----------+
| id | blocker | blockee |
+----+-----------+----------+
| 1 | myself | testuser |
+----+-----------+----------+
It would return everyone that is not testuser and had the same values in the interests column as the LIKE clause.
I hope this makes sense. I'm stuck.

This query will give you the list of all users who are not you and are not blocked by you. You can then add other conditions (such as AND interests LIKE <string>) to the WHERE clause as necessary.
SELECT u.*
FROM users u
LEFT JOIN blocked b
ON b.blockee != u.username AND b.blocker = 'myself'
WHERE u.username != 'myself' AND b.blockee IS NOT NULL
Demo on dbfiddle

Here's the query that will do what you're looking for:
select *
from users u
where u.username != 'myself'
and not exists (
select 1
from blocked b
where b.blocker = 'myself'
and b.blockee = u.username
)
and u.interests like '%what%';
I put together an example you can execute to see how it behaves: https://rextester.com/GPEC60793
Here's a copy of the entire demo:
drop table if exists demo_users;
drop table if exists demo_blocked;
create table demo_users (id int auto_increment, username varchar(255), interests text, primary key (id));
create table demo_blocked (id int auto_increment, blocker varchar(255), blockee varchar(255), primary key (id));
insert into demo_users (username, interests)
values ('myself', 'whatever'),
('testuser', 'blah'),
('somebody', 'foo'),
('nobody', 'whatfor');
insert into demo_blocked (blocker, blockee)
values ('myself', 'testuser');
select * from demo_users;
select * from demo_blocked;
select *
from demo_users u
where u.username != 'myself'
and not exists (
select 1
from demo_blocked b
where b.blocker = 'myself'
and b.blockee = u.username
)
and u.interests like '%what%';
drop table demo_users;
drop table demo_blocked;

If you don't care about performance, you can try sub query.
SELECT * FROM users WHERE username != 'myself' and user.username not in (select locker from blocked);
I think it will work correctly for you

This should work - but again I don't have your test data to test it against.
Cheers
SELECT *
FROM users
WHERE ( 1 = 1 )
AND ( username ! = 'myself OR username not in ( select username from blocked ))
AND interests like '%string%';
in the predicate yo may need to change the OR to an AND - as I said no data to test my logic.
HTH!

First get a list of your blockee, then try to match those with your user table, if you cant find a match you will have null blockee, and those are the users you want.
SELECT u.*
FROM users u
LEFT JOIN ( SELECT b.blockee
FROM blocked b
WHERE b.blocker = 'myself') as b
ON o.username = b.blockee
WHERE b.blockee IS NULL
AND u.interests LIKE <string>
AND u.username != 'myself' -- if blocker cant be the same as blockee
-- you dont need this one

Related

sql query for fetching leaderboard from two tables

I have two MySql table user and marks
User table:
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| firstName | varchar(255) | YES | | NULL | |
| lastName | varchar(255) | YES | | NULL | |
| email | varchar(55) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
Marks Table
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| user_id | int | NO | | NULL | |
| subject_id | varchar(255) | YES | | NULL | |
| score | int | YES | | NULL | |
| subject_name | varchar(225) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
I want to fetch details(userid, firstName and lastName) of top 10 users with the highest marks in descending order.
Marks of the user is defined as sum of all scores a user has in different subjects.
I am really confused which join is to be used here, I am new to MySql and this query is kind of challenging for me, Hope you understood the problem.
Please let me know if you have any suggestion, Thank You
You can join the two tables together by id=user_id, then you group the result by id, sort by the total marks per id, then take the top 10 results.
If you wanted a result even if the user had no marks at all, change JOIN to LEFT JOIN, this will still give you a result from the first table even if there are no results from the second.
SELECT u.id, u.firstName, u.lastName, SUM(m.score) AS TotalScore
FROM [User] AS u
JOIN Marks AS m ON m.user_id = u.id
GROUP BY u.id, u.firstName, u.lastName
ORDER BY SUM(m.score) DESC
LIMIT 10;
You should inner join the table Marks on the id from table User with user_id from the table Marks:
SELECT user_id, firstName, lastName
FROM User
INNER JOIN Marks
ON User.id = Marks.user_id
Here is a helpful resource for SQL joins

Cannot update table by counting fields in related table

I'd like to update followers in profile table by counting the followed_id on follow table.
mysql> explain follow;
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| followed_id | int(11) | NO | | NULL | |
| follower_id | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+-------+
And
mysql> explain profile;
+----------------+---------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(10) | NO | MUL | 0 | |
| followers | int(7) | NO | | 0 | |
| following | int(7) | NO | | 0 | |
+----------------+---------------+------+-----+-------------------+----------------+
Here is the query that I came up with:
UPDATE profile A
INNER JOIN (SELECT id,COUNT(*) idcount FROM follow GROUP BY id) as B
ON B.id = A.user_id
SET A.followers = B.idcount
But the query does not work as it should. It adds only 1 when profile has followers.
How can I fix this?
You are currently counting the number of rows for each id value in follow, which is always going to be 1. What you need to do is count the number of follower_id values for each followed_id. Also, as #juergend pointed out, you should use a LEFT JOIN so that you can get 0 values for users with no followers. Change your query to this:
UPDATE profile A
LEFT JOIN (SELECT followed_id, COUNT(DISTINCT follower_id) AS idcount
FROM follow
GROUP BY followed_id) as B ON B.followed_id = A.user_id
SET A.followers = COALESCE(B.idcount, 0)
You can use a similar query to update following:
UPDATE profile A
LEFT JOIN (SELECT follower_id, COUNT(DISTINCT followed_id) AS idcount
FROM follow
GROUP BY follower_id) as B ON B.follower_id = A.user_id
SET A.following = COALESCE(B.idcount, 0)

MySQL - UPDATE one column based on results of a SELECT when the SELECT returns multiple columns

I've read MySQL - UPDATE query based on SELECT Query and am trying to do something similar - i.e. run an UPDATE query on a table and populate it with the results from a SELECT.
In my case the table I want to update is called substances and has a column called cas_html which is supposed to store CAS Numbers (chemical codes) as a HTML string.
Due to the structure of the database I am running the following query which will give me a result set of the substance ID and name (substances.id, substances.name) and the CAS as a HTML string (cas_values which comes from cas.value):
SELECT s.`id`, GROUP_CONCAT(c.`value` ORDER BY c.`id` SEPARATOR '<br>') cas_values, GROUP_CONCAT(s.`name` ORDER BY s.`id`) substance_name FROM substances s LEFT JOIN cas_substances cs ON s.id = cs.substance_id LEFT JOIN cas c ON cs.cas_id = c.id GROUP BY s.id;
Sample output:
id | cas_values | substance_name
----------------------------------------
1 | 133-24<br> | Chemical A
455-213<br>
21-234
-----|----------------|-----------------
2 999-23 | Chemical B
-----|----------------|-----------------
3 | | Chemical C
-----|----------------|-----------------
As you can see the cas_values column contains the HTML string (which may also be an empty string as in the case of "Chemical C"). I want to write the data in the cas_values column into substances.cas_html. However I can't piece together how to do this because other posts I'm reading get the data for the UPDATE in one column - I have other columns returned by my SELECT query.
Essentially the problem is that in my "sample output" table above I have 3 columns being returned. Other SO posts seem to have just 1 column being returned which is the actual values that are used in the UPDATE query (in this case on the substances table).
Is this possible?
I am using MySQL 5.5.56-MariaDB
These are the structures of the tables, if this helps:
mysql> DESCRIBE substances;
+-------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------------+------+-----+---------+----------------+
| id | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| app_id | varchar(8) | NO | UNI | NULL | |
| name | varchar(1500) | NO | | NULL | |
| date | date | NO | | NULL | |
| cas_html | text | YES | | NULL | |
+-------------+-----------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)
mysql> DESCRIBE cas;
+-------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------------------+------+-----+---------+----------------+
| id | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| value | varchar(13) | NO | UNI | NULL | |
+-------+-----------------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)
mysql> DESCRIBE cas_substances;
+--------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| cas_id | mediumint(8) unsigned | NO | MUL | NULL | |
| substance_id | mediumint(8) unsigned | NO | MUL | NULL | |
+--------------+-----------------------+------+-----+---------+----------------+
3 rows in set (0.02 sec)
Try something like this :
UPDATE substances AS s,
(
SELECT s.`id`,
GROUP_CONCAT(c.`value` ORDER BY c.`id` SEPARATOR '<br>') cas_values,
GROUP_CONCAT(s.`name` ORDER BY s.`id`) substance_name
FROM substances s
LEFT JOIN cas_substances cs ON s.id = cs.substance_id
LEFT JOIN cas c ON cs.cas_id = c.id
GROUP BY s.id
) AS t
SET s.cas_html=t.cas_values
WHERE s.id = t.id
If you don't want to modify all the value, the best way to limit the update to test it, is to add a condition in the where, something like that :
...
WHERE s.id = t.id AND s.id = 1

How to write mysql query to insert values only if foreign_key exists

I am having a user_role table which has user_id referred as a foreign_key from user table.
desc user;
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(256) | YES | | NULL | |
| e_key | varchar(256) | NO | UNI | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
desc user_role;
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(256) | YES | | NULL | |
| user_id | int(11) | NO | | UNIQ | |
+-----------------+--------------+------+-----+---------+----------------+
I want to create a test data to insert values into user_role table only if the user_id exists. User can be identified by the unique key 'e_key'. I am searching for some query like this
SET #user_id = (select u.id from user u where u.e_key = 'dave');
IF(#user_id IS NOT NULL) THEN
BEGIN
INSERT INTO user_role(`name`, user_id) VALUES ('admin', #user_id) ON DUPLICATE KEY UPDATE `name` = 'admin';
END;
END IF;
But the sql throws an error. Is there any way to achieve this behaviour?
You could change the INSERT to use a SELECT as the source of the field to insert, with the SELECT checking the user table.
Something like this:-
INSERT INTO user_role(`name`, user_id)
SELECT 'admin', id
FROM user
WHERE id = #user_id
ON DUPLICATE KEY UPDATE `name` = 'admin';
If in your work scenario you always first add user, and only later user role - consider changing the foreign key order.
It seems that in that case it is better to have user_role table to reference the user table, and not vice versa.

Select from another table if result not matched

This has been asked but I dont quite match existing questions with my case.
I have two tables, the first table is credentials with id username and email
and an email-alias table with user-id (which corresponds to credentials.id) and email. Emails in credentials are more often "user.name#domain.com" while in alias they'd be "usern#domain.com".
All I have now is
SELECT `username` FROM `credentials` WHERE `email` LIKE ?
But the email will not always match if I query with "usern#domain.com". What I want to do is get the username with one query which would fall back to email-alias and use "usern#domain.com" to get an user-id from there to be used again in credentials to match a username
The pitfall is that the supplied email could be either an aliased one "usern#.." or "user.name#.."
mysql> describe `email-alias`;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user-id | int(11) | NO | UNI | NULL | |
| email | varchar(100) | YES | | NULL | |
+---------+--------------+------+-----+---------+----------------+
mysql> describe `credentials`;
+-----------------+--------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(100) | NO | UNI | NULL | |
| email | varchar(100) | NO | | NULL | |
+-----------------+--------------+------+-----+---------------------+----------------+
Your question is kind of confusing but I think what you're trying to do is select username from the first table if the email exists and if it doesn't select from the 2nd table if it exists. You would use subqueries for that. Hope this helps.
SELECT `c.username`
FROM credentials c
WHERE c.email = 'usern#domain.com' OR c.id =
(SELECT `e.user-id` from email-alias e WHERE e.email = "usern#domain.com")
I'm not sure if I understand your question clearly because it's too confusing. But let me give this a try. You need to join the table using INNER JOIN.
SELECT `username`
FROM credentials a
INNER JOIN email_alias b
on a.ID = b.userID
WHERE b.email = 'usern#domain.com'
UPDATE 1
SELECT `username`
FROM credentials a
INNER JOIN email_alias b
on a.ID = b.userID
WHERE b.email LIKE '%usern#%'