Combine data of three tables in SQL - mysql

I'm working on a SQL-statement which will involve 3 different tables. Below you can see the structure of them with one example data each.
FOLLOWERS-TABLE: This table contains information about the current user ('user') following another user ('following')
MESSAGES: This table contains information about messages of a certain user
LIKES: This table contains information about who liked other messages. 'idmessage' is the same as the 'id' in the Messages-table
First of all, I wanted to display all the messages of the persons who the user follows. I used this query:
SELECT *
FROM Messages
LEFT JOIN Followers ON Followers.following=Messages.user
WHERE Followers.user = 'Peter Jackson'
This resulted in this:
It is working. However, in the next step I should add a custom column to it, this must be 'alreadyLiked'. This should check if the user already liked the message. The value must be 'yes' or 'no'. Therefore, information about the Likes-table is needed. I guess I should make another join on 'Likes.idmessage' and 'Messages.id'. But I don't know how to give it a value 'yes' or 'no'. In the end, it should be like this:
On the server-side I use Node.js and the client-side AngularJS.

If I understood correctly then this should work:
SELECT Messages.*, Followers.*,
CASE WHEN Likes.id IS NOT NULL THEN 'yes' ELSE 'no' END AS alreadyLiked
FROM Messages
LEFT JOIN Followers ON Followers.following=Messages.user
LEFT JOIN Likes ON Likes.idmessage = Messages.id
WHERE Followers.user = 'Peter Jackson'
The way this works is simply by left joining the Likes table. Now if there is a no link found between the message table and the like table then the fields from the Like table will be null. Then we use the case operator to check each row if any field from the like table is or isn't null.

Related

Laravel Left Join with a tricky filter

I am assigning unique users to vouchers on my website.1 user may have more than one voucher assigned to them but cannot be assigned the same voucher twice.I have 2 mysql tables that I am fetching data from.
tbl_users
tbl_voucher_users
When a user click on a button on my website, they pass along a voucher_id with which I use to display eligible users that can be assigned this voucher ( I.e Users that have not been assigned to this voucher ).
Below is how I am getting the users where voucher_id = 8
$user_data = DB::table('users')
->leftJoin('voucher_users', 'users.id', '=', 'voucher_users.user_id')
->where('voucher_users.voucher_id','!=',8) //User not assigned this voucher
->select('users.*','users.id as userID','voucher_users.*')
->get();
My problem
I am able to left join without the where clause and get results from both Users table and Voucher_users table having eliminated all users assigned voucher_id=8.
However, the results also include users who are assigned other vouchers but also the voucher I am assigning.
i.e
Expected resulsts would be users: 8,11,12,13,14 having eliminated users: 1,4
But my current results are:4,8,11,12,13,14
How do I get rid of the user 4 to prevent double assignment?
Thanks to the suggestion above by #Kevin Lynch to use NOT EXIST .. I simplified the code to:
SELECT users.*
FROM
users
WHERE
NOT EXISTS(SELECT user_id FROM voucher_users WHERE voucher_users.user_id = users.id AND voucher_id=9)
It works so far, I can then covert it to Laravel style
NOT EXISTS would be a good solution if this were to remain a small project (or if you could put more constraints on the users to keep the dataset small perhaps by limiting based on user created date or similar).
However if you can't do such a thing, this query will eventually give you problems because under the hood, mysql will be running that sub-query for each record returned in the main query. You can check https://dev.mysql.com/doc/refman/5.6/en/subquery-materialization.html for more information.
A different solution which would handle the scaling quite a bit better would be to generate a temporary table of users that have the voucher you are looking to remove...
create temporary table tmp_voucher_user (user_id int not null, primary key (user_id)) as
select distinct user_id from voucher_users where voucher_id = 8;
Now that we have a table of users we which to remove, all we need to do is worry about a simple left join...
select users.*, user_voucher.*
from users
inner join user_voucher on users.id = user_voucher.user_id
left join tmp_voucher_user on users.id = tmp_voucher_user.user_id
where tmp_voucher_user.user_id is null -- this part is important, it's only going to grab users where there isn't a match on tmp_user_voucher
Unfortunately this isn't as clean as just doing a NOT EXISTS and I don't believe Laravel supports a way to build temporary tables outside of just writing a raw query but it should scale quite a bit better.

How to query DB table with additional fields from another table

I'm working on a HN-style database, and I'm trying to query a list of 'posts', that contain a 'userHasVoted' property for each one (this checks to see if the user currently logged in has voted for that specific post). (Note: the userHasVoted field does not exist - but needs to be dynamically created if the user has voted for a specific post.)
The 'posts' live in a separate table as the 'votes', joined on post.id and votes.postId.
How can I query the DB to show every post with this property, not limited to just the posts the user has voted for?
It's not entirely clear to me what you are asking, but I think the most likely scenario is you used an INNER JOIN (perhaps expressed as just JOIN), which will only return results where a record from both tables actually exists. What you want is an outer join, which will let you keep records from the first table if no record matches in the second:
SELECT p.*, case when v.postid is not null then 1 else 0 end as UserHasVoted
FROM posts p
LEFT JOIN votes v ON v.postid = p.id and v.userid = #UserID

What happen if two sql table have the same column name while trying to write a sql query from the two? [duplicate]

I have a table for users. But when a user makes any changes to their profile, I store them in a temp table until I approve them. The data then is copied over to the live table and deleted from the temp table.
What I want to achieve is that when viewing the data in the admin panel, or in the page where the user can double check before submitting, I want to write a single query that will allow me to fetch the data from both tables where the id in both equals $userid. Then I want to display them a table form, where old value appears in the left column and the new value appears in the right column.
I've found some sql solutions, but I'm not sure how to use them in php to echo the results as the columns in both have the same name.
Adding AS to a column name will allow you to alias it to a different name.
SELECT table1.name AS name1, table2.name AS name2, ...
FROM table1
INNER JOIN table2
ON ...
If you use the AS SQL keyword, you can rename a column just for that query's result.
SELECT
`member.uid`,
`member.column` AS `oldvalue`,
`edit.column` AS `newvalue`
FROM member, edit
WHERE
`member.uid` = $userId AND
`edit.uid` = $userId;
Something along those lines should work for you. Although SQL is not my strong point, so I'm pretty sure that this query would not work as is, even on a table with the correct fields and values.
Here is your required query.
Let suppose you have for example name field in two tables. Table one login and table 2 information. Now
SELECT login.name as LoginName , information.name InofName
FROM login left join information on information.user_id = login.id
Now you can use LoginName and InofName anywhere you need.
Use MySQL JOIN. And you can get all data from 2 tables in one mysql query.
SELECT * FROM `table1`
JOIN `table2` ON `table1`.`userid` = `table2`.`userid`
WHERE `table1`.`userid` = 1

LEFT JOIN - narrow things down

I'm currently having a problem with a legacy app I just inherited on my new job. I have a SQL query that's way too long to respond and I need to find a way to fasten it.
This query acts on 3 tables:
SESSION contains all users visits
CONTACT contains all the messages people have been sending through a form and contains a "session_id" field that links back to the SESSION id field
ACCOUNT contains users accounts (people who registered on the website) and whose "id" field is linked back in SESSION (through a "SESSION.account_id" field). ACCOUNT and CONTACT are no linked in any way, besides the SESSION table (legacy app...).
I can't change this structure unfortunately.
My query tries to recover ALL the interesting sessions to serve to the administrator. I need to find all sessions that links back to an account OR a contact form.
Currently, the query is structured like that :
SELECT s.id
/* a few fields from ACCOUNT and CONTACT tables */
FROM session s
LEFT JOIN account act ON act.id = s.account_id
LEFT JOIN contact c on c.session_id = s.id
WHERE s.programme_id = :program_id
AND (
c.id IS NOT NULL
OR
act.id IS NOT NULL
)
Problem is, the SESSION table is growing pretty fast (as you can expect) and with 400k records it slows things down for some programs ( :programme_id in the query).
I tried to use an UNION query with two INNER JOIN query, one between SESSION and ACCOUNT and the other one between SESSION and CONTACT, but it doesn't give me the same number of records and I don't really understand why.
Can somebody help me to find a better way to make this query ?
Thanks a lot in advance.
I think you just need indexes. For this query:
SELECT s.id
/* a few fields from ACCOUNT and CONTACT tables */
FROM session s LEFT JOIN
account act
ON act.id = s.account_id LEFT JOIN
contact c
ON c.session_id = s.id
WHERE s.programme_id = :program_id AND
(c.id IS NOT NULL OR act.id IS NOT NULL);
You want indexes on session(programme_id, account_id, id), account(id) and contact(session_id).
It is important that programme_id be the first column in the index on session.
#Gordon already suggested you add an index, which is generally the easy and effective solution, so I'm going to answer a different part of your question.
I tried to use an UNION query with two INNER JOIN query, one between
SESSION and ACCOUNT and the other one between SESSION and CONTACT, but
it doesn't give me the same number of records and I don't really
understand why.
That part is rather simple: the JOIN returns a result set that contains the rows of both tables joined together. So in the first case you would end up with a result that looks like
session.id, session.column2, session.column3, ..., account.id, account.column2, account.column3, ....
and a second where
session.id, session.column2, session.column3, ..., contact.id, contact.column2, contact.column3, ....
Then an UNION will faill unless the contact and account tables have the same number of columns with correspoding types, which is unlikely. Otherwise, the database will be unable to perform a UNION. From the docs (emphasis mine):
The column names from the first SELECT statement are used as the column names for the results returned. Selected columns listed in corresponding positions of each SELECT statement should have the same data type. (For example, the first column selected by the first statement should have the same type as the first column selected by the other statements.)
Just perform both INNER JOINs seperately and compare the results if you're unsure.
If you want to stick to an UNION solution, make sure to perform a SELECT only on corresponding columns : doing SELECT s.id would be trivial but it should work, for instance.

SQL Server 2005/2008 : find row values that exist and list that don't in my in select column condition IN()

I have a single table that contain columns:
UserID, EmployeeID, BadgeType, HiredDate, TermDate
Now I need to find userID that are with (gbro, qunro, 1utny, ybeiot, 4ybey)
The 3 users (gbro, qunro, 1utny) exist so it is listed with respective its column info.
What if ybeiot, 4ybey does not exist AT ALL but still I want them listed in a separate table still but with a message that PRINTS: User that does not exist: ybeiot;4ybey
Help, been finding way how to do this.
I tried JOIN (all the joins) but it does not result to what I wanted.
Did you look at SQL EXISTS keyword?
put all the users to be searched in a temp table or table variable #userstoSearch
select * from #userstoSearch us left join users u
on us.UserID=u.UserID where u.userID is not null
select us.UserID from #userstoSearch us left join users u
on us.UserID=u.UserID where u.userID is null
for xml path('')
You need two selects. The first will list the existing values and the second lists the not existing values. You should merge these results using the union keyword.