Mysql join:search with two tables - mysql

I have two tables
1)Users table (id,name);
1)Deals table (id,user_id(FK users),title,keyword);
When the user gives a keyword
I would like to get all deals from the deals table that matches title and keyword along with all deals of the users to match the keyword name.
Is it possible to achieve this within single query I am confusing with it or do I need to run two query's
ie
1)Get all deals of that matches keyword and title from deals table.
2)Get all deals of the users matches the keyword in the name from user table (probably a join query).
If I go with this approach how do I implement pagination on it??.

This joins both tables and returns the rows that matches all 3 conditions.
SELECT d.id, d.title, d.keyword, u.name,
FROM Deals d
JOIN Users u ON u.id = d.user_id
WHERE (d.title = 'some title' AND d.keyword = 'some keyword')
OR u.name = 'some name'
If you don't want/need exact matches LIKE can be used instead, for instance
WHERE d.title LIKE '%some title%'

Related

Joining and querying two tables with conflicting column names

Imagine I have two tables: users and students
Each table has an id and email column. I want to be able to search for emails from both tables. I can join the two tables where the IDs match like this:
SELECT users.email,students.email as student_email FROM users
INNER JOIN students ON users.id = students.id
I can't search the email column because they have the same column name (email). So if I try use WHERE it complains that the column (email) is ambiguous. If I try use student_email it complains that the column doesn't exist.
If the two tables didn't use an identical column name (email) then it would be fine when I use WHERE.
The only solution I can think of is to get all rows and then loop around them and search the email strings manually. This is extremely slow compared to using MySQL like this:
SELECT users.email,students.email as student_email FROM users
INNER JOIN students ON users.id = students.id
WHERE email LIKE '%test#email.com%' OR student_email LIKE '%test#email.com%'
But of course this doesn't work because WHERE looks for the original column names before the SELECT.
How can I get around this without getting all the rows and the looping it? Maybe it is a union, rather than a JOIN? I'm not sure...
Just use the qualified column name. I also recommend table aliases:
SELECT u.email, s.email as student_email
FROM users u INNER JOIN
students s
ON u.id = s.id
WHERE u.email LIKE '%test#email.com%' OR
s.email LIKE '%test#email.com%';
Whenever you have more than one table in a query, you should always qualify all the column names. Using abbreviations for the tables as table aliases makes the query easier to write and to read.

How can I filter out results based on another table. (A reverse join I guess?)

Basically, I have a table which contains two fields: [id, other] which have user tokens stored in them. The goal of my query is to select a random user that has not been selected before. Once the user is selected it is stored in the table shown above. So if Jack selects Jim randomly, Jack cannot select Jim again, and on the flip side, Jim cannot select Jack.
Something like this is what comes to mind:
SELECT * FROM users
WHERE (SELECT * FROM selected WHERE (id=? AND other=?) OR (id=? AND other=?));
Well, first of all I've read that uses sub-queries like this is extremely inneficient, and I'm not even sure if I used the correct syntax, the problem is however, that I have numerous tables in my scenario which I need to filter by, so it would look more like this.
SELECT * FROM users u
WHERE (SELECT * FROM selected WHERE (id=? AND other=?) OR (id=? AND other=?))
AND (SELECT * FROM other_table WHERE (id=? AND other=?) OR (id=? AND other=?))
AND (SELECT * FROM diff_table WHERE (id=? AND value=?))
AND u.type = 'BASIC'
LIMIT = 1
I feel like there's a much, much more efficient way of handling this.
Please note: I don't want a row returned at all if the users id is present in any of the nested queries. Returning "null" is not sufficient. The reason I have the OR clause is because the user's id can be stored in either the id or the other field, so we need to check both.
I am using Postgre 9.5.3, but I added the MySQL tag as the code is mostly backwards comptable, Fancy Postgre only solutions are accepted(if any)
You can left join to another table, which produces nulls where no record is found:
Select u.* from users u
left selected s on s.id = u.id or s.other = u.other
where s.id is null
The or in a join is different, but should work. Example is kinda silly...but as long as you understand the logic. Left join first table to second table, where second table column is not null means there was atleast one record found that matched the join conditions. Where second table column is null means no record was found.
And you are right...avoid the where field = (select statement) logic when you can, poor performer there.
Use an outer join filtered on missed joins:
SELECT * FROM users u
LEFT JOIN selected s on u.id in (s.id, s.other) and ? in (s.id, s.other)
WHERE u.id != ?
AND s.id IN NULL
LIMIT 1

How to select two tables and combined them together if null isn't present

Currently I can search two tables (events table and the users table) and combined them together using the following:
SELECT e.*, u.user_first_name, u.user_last_name
FROM users u, events e
WHERE e.event_user_id = u.user_id
ORDER BY e.event_id DESC
This will allow me to search everything from the events table and the first and last name from the users table.
But the problem occurs when the event_user_id can be NULL, meaning if there is a user set, I want to include their first and last name, but if there isn't then I just want that field to be blank.
SELECT e.*, u.user_first_name, u.user_last_name
FROM users u, events e
WHERE e.event_user_id = u.user_id
OR e.event_user_id IS NULL
ORDER BY e.event_id DESC
I know the above example isn't correct. My question is, how can I select two tables where the main table has an optional event_user_id, if it is not NULL, then combine with the users table to get the first and last name?
Is it just more reliable to have two separate queries?
As Ivan mentioned in the comments to my question, the correct way to successfully carry out the query I want is to use a left join, so I did a bit of research and I found the right query for me:
SELECT events.*, users.user_first_name, users.user_last_name
FROM events
LEFT OUTER JOIN users
ON events.event_user_id = users.user_id;
If event_user_id is present, then the user_first_name and user_last_name will be added to the row, however, if event_user_id is NULL then both the first and last name will also be NULL. Exactly what I wanted.

How to properly use inner join in SQL when I have to join a table twice?

Note: The actual schema isn't male/female, but some other criteria. I'm just using male/female for this example to make it easier to understand.
I have a table "users", which contains a column user_name and user_gender. The gender can be "M" or "F".
The problem is that I have another table, "messages", that has a column for "sender" and "receiver". These columns contains user_name in each row.
How can I use INNER JOIN so that I can get messages where only males send to females?
I know easily how to specify it once, binding users.user_name to "sender" or "receiver" but not both.
To expand on my question, how do see which top 10 pairs where a male sent the most messages to a female? Note, this means unique A/B pairs, so I want to return cases where a guy sends a single female a ton of messages, not when a guy spams a lot of messages to different females.
Think of your messages table as a "cross table" connecting two rows in the users table. When you join to a table like that, give users two different aliases, and refer to them in your join conditions, like this:
select *
from messages msg
join users m on msg.sender = m.user_id AND m.user_gender='M'
join users f on msg.receiver = f.user_id AND f.user_gender='F'
With this skeleton in hand, you should be able to figure out the rest of your query:
Use GROUP BY to group by m.user_id, f.user_id, and count(*) to count
Order by COUNT(*) to get the highest sender+receiver pairs at the top
Use LIMIT to grab the top ten pairs.

MySQL: Returning More than 1 Row When Referencing Through Several Tables

I am trying to list all of the stores that sell products that contain specific guitar parts for a guitar rig.
I have a guitarRig database. Guitar rigs have parts (I.e. amplifier, cabinet, microphone, guitarType, guitarStringType, patchCord, effectsPedal) which come from products, which are purchased from stores.
Products are purchased for a price as dictated by my PurchaseInformation table.
Here are my tables:
Table Part:
name
guitarRig references GuitarRig(name)
product references Product(name)
Table Product:
name
part references Part(name)
barcodeNumber
Table PurchaseInformation:
price
product references Product(name)
purchasedFrom references Store(name)
Table Store:
name
storeLocation
So far what I have is this:
SELECT p.name AS Part, prod.name AS Product, sto.name AS Store
FROM Part p, ProductInformation prod, Store sto, PurchaseInfo purch
WHERE sto.storeNumber = purch.storeNumber
AND purch.product = prod.name
AND prod.Part = p.name
AND p.name =
(
SELECT name
FROM Part
WHERE name LIKE '%shielded%'
)
GROUP BY p.name;
The error I get is that it returns more than 1 row, however, this is what I want! I want to list the stores that sell products that contain the part I am searching for.
The quick fix is to replace the equality comparison operator ( = ) with the IN operator.
AND p.name IN
(
SELECT name ...
I say that's the quick fix, because that will fix the error, but this isn't the most efficient way to write the query. And it's not clear your query is going return the result set you specified or actually expect.
I strongly recommend you avoid the old-school comma join operator, and use the JOIN keyword instead.
Re-structuring your query into an equivalent query yields this:
SELECT p.name AS Part
, prod.name AS Product
, sto.name AS Store
FROM Part p
JOIN ProductInformation prod
ON prod.Part = p.name
JOIN PurchaseInfo purch
ON purch.product = prod.name
JOIN Store sto
ON sto.storeNumber = purch.storeNumber
WHERE p.name IN
(
SELECT name
FROM Part
WHERE name LIKE '%shielded%'
)
GROUP BY p.name;
Some notes. The GROUP BY clause is going to collapse all of the joined rows into a single row for each distinct part name. That is, you are only going to get one row back for each part name.
It doesn't sound like that's what you want. I recommend you remove that GROUP BY, and add an ORDER BY, at least until you figure out what resultset you are getting, and if that's the rows you want to return.
Secondly, using the IN (subquery) isn't the most efficient approach. If p.name matches a value returned by that subquery, since p is a reference to the same Part table, this:
WHERE p.name IN
(
SELECT name
FROM Part
WHERE name LIKE '%shielded%'
)
is really just a more complicated way of saying this:
WHERE p.name LIKE '%shielded%'
I think you really want something more like this:
SELECT p.name AS Part
, prod.name AS Product
, sto.name AS Store
FROM Part p
JOIN ProductInformation prod
ON prod.Part = p.name
JOIN PurchaseInfo purch
ON purch.product = prod.name
JOIN Store sto
ON sto.storeNumber = purch.storeNumber
WHERE p.name LIKE '%shielded%'
ORDER BY p.name, prod.name, sto.name
That's going to return all rows from Part that include the string 'shielded' somewhere in the name.
We're going to match those rows to all rows in ProductInformation that match that part. (Note that with the inner join, if a Part doesn't have at least one matching row in ProductInformation, that row from Part will not be returned. It will only return rows that find at least one "matching" row in ProductionInformation.
Similarly, we join to matching rows in PurchaseInfo, and then to Store. Again, if there's not matching row from at least one Store, we won't get those rows back. This query is only going to return rows for Parts that are related to at least on Store. We won't get back any Part that's not in a Store.
The rows can be returned in any order, so to make the result set deterministic, we can add an ORDER BY clause. It's not required, it doesn't influence the rows returned, it only affects the sequence the rows that get returned.