how exclude the query result from three different tables? - php

I have 3 tables offers countries and transactions and what I'm trying to do is extract the offers from a specific country and devices, I already do this with this formula:
SELECT
*
FROM countries a
INNER JOIN offers b ON a.id = b.offer_id
WHERE a.country = "US"
AND b.device = 'all'
UNION
SELECT
*
FROM countries a
INNER JOIN offers b ON a.id = b.offer_id
WHERE a.country = "US"
AND b.device LIKE 'iphone%';
This code works but now I want to exclude the offers with the same ref between the tables users and transactions
the way this should work is:
The user from X country enter on Y device.
The user select an offer according to his country and device.
The offer is registered via post back to transactions.
The user enter to the main page and not longer see the offer he already finish.
Right now i'm capable of do the 3 first steps but not the fourth step. Any help about how do this?
Here is an example of the tables: SQL Fiddle
Thanks in advance
EDIT
if I put all this on words would be something like:
Select all from *offers* where the **offer_id** match with **ID** from *countries* with the **country** "US" and also from *offers* have the **device** "iPhone" now exclude from *transactions* the offers with the **user** = "xxxxx"
I hope this explain a little bit what I'm trying to do

Can't see how the query is user-specific , but to exclude offers refered to from transactions you can use NOT EXISTS
SELECT *
FROM countries a
INNER JOIN offers b ON a.id=b.offer_id
WHERE a.country="US" AND b.device = 'all'
AND NOT EXISTS (SELECT 1 FROM transactions t WHERE t.offer = b.offer_Id)

You could solve it with a LEFT OUTER JOIN to the transactions table.
I added t.id IS NULL to the WHERE-clause. So your result will just display offers which have no transaction.
SELECT *
FROM countries a
INNER JOIN offers b
ON a.id = b.offer_id
LEFT OUTER JOIN transactions t
ON t.id = b.offer_id
WHERE a.country = "US"
AND t.id IS NULL
Regarding your question it is still unclear for me how the tables users & countries are connected. This is an important detail, as the LEFT OUTER JOIN should just grab transactions from this specific user. So the condition transactions.user = user.ref should be added to the join too.
Will update the answer whenever you explain me this link

Related

MySQL efficient SELECT query

I'm not a database professional, but currently working on one query (PHP->MySQL):
I have 3 tables:
'Items': id, name, link
'ItemsToUsers': id, item_id, user_id
'Users': id, email
Each 'Item' availability is submitted to regular changes which I check on fly by some algorithm.
My goal is to
1) SELECT all Items and check on fly if they are available
2) If Item is available, notify users who are monitoring it by email. For that I need to SELECT users from 'ItemsToUsers' and then get their emails from Users table.
I know how to do it in a straightforward way, but I feel that I will fall into running to many queries. (individual SELECT for every user...)
Is there a way to do it more efficiently: in one query or by changing the algorithm?
Thank you for your time!
There's not enough information to determine how an item is available. This severely impedes the ability to query item 2.
That said, let's suppose we add a "available" column to the Items table that is a tinyint of 0 for not available, 1 for available.
A query, then, which would get all email addresses for persons watching items that are available is:
SELECT u.email FROM Users AS u JOIN ItemsToUsers AS k ON k.user_id = u.id JOIN Items AS i on i.id = k.item_id WHERE i.available = 1;
Alternatively, you could use a subquery and IN.
Let's suppose you have a different table called Availability with the columns id, item_id and available, which again is a tinyint containing a 1 for available and 0 for not available.
SELECT u.email FROM Users AS u JOIN ItemsToUsers AS k ON k.user_id = u.id WHERE k.item_id IN (SELECT a.item_id FROM Availability AS a WHERE a.available = 1);
Again, without an idea of how you are getting a list of available products, it is impossible to optimize your queries for retrieving a list of email addresses.
Your steps allude to doing this in n+1 queries (where n = number of entries in the Items table):
SELECT * FROM Items; -- This is the +1 part
While iterating over that result set, you intend to determine if it's available and, if it is, to notify users who are watching it. Assuming you have a given item id and you want to select all users' email if that product id is active, then you could do this:
SELECT email FROM Users u
INNER JOIN ItemsToUsers iu ON iu.user_id = u.id
INNER JOIN Items i ON iu.item_id = i.id
WHERE i.id = {your item id}
You would be running this query for every item in your table. This is the n part.
In general you could instead generate a list of emails for all users who are watching all products that are active, after you have already determined which ones should be active:
SELECT DISTINCT email FROM Users u
INNER JOIN ItemsToUsers iu ON iu.user_id = u.id
INNER JOIN Items i ON iu.item_id = i.id
WHERE i.is_active = 1
This will get the job done in a total of 2 queries, regardless of how many users or items you have. As a bonus, this one can give you distinct emails, whereas the first solution would still need application-level code to remove duplicates returned by the multiple queries.
SELECT Items.id, Items.name, Items.link FROM Items
INNER JOIN ItemsToUsers ON ItemsToUsers.item_id = Items.items.id
INNER JOIN Users ON ItemsToUsers.user_id = Users.id ;

MySQL Merge Table B into Table A based on values in row of Table B

I am trying to merge one table on another table based on a given value. But, it isn't as simple as a JOIN. ( I found plenty of JOIN examples online, but none like this. Also, I am not incredibly great at DB queries. )
For a simple example of what I am trying to accomplish, lets say I run a computer shop and sell multiple computers to multiple people. For whatever reason, these machines periodically check-in with me, and I want to know which customers of mine have a machine that is currently offline. However, if they have at least 1 machine online, I DO NOT want to see ANY of the machines they have that are either online OR offline.
My query so far is something like this:
SELECT *
FROM Computers
LEFT JOIN Computers_On_Customers ON Computer_ID = Computers.ID
LEFT JOIN Customers ON Computers_On_Customers.Customer_ID = Customers.ID
WHERE Computers.Currently_Online LIKE "0"
However, if a Customer has 2 computers, one of each status, it will show 1 computer, when I want it to show neither. (Because one is currently offline, and therefore I do not care about the online one.) How could I go about this?
Here is a rough visual approximation of my database:
Customers:
ID Other Stuff
Computers_On_Customers
ID Customer_ID Computer_ID
Computers:
ID Currently_Online
Any help would be greatly appreciated.
You need to perform an aggregation and then take the result of that aggregation and select with the filters you want.
Using your example. Take the where and move it outside an aggregation selection.
SELECT
ID, offline_count
from
(SELECT
Customers.ID,
SUM(IF(Computers.Currently_Online Like '0', 0, 1)) 'offline_count'
FROM
Computers
LEFT JOIN Computers_On_Customers ON Computer_ID = Computers.ID
LEFT JOIN Customers ON Computers_On_Customers.Customer_ID = Customers.ID
GROUP BY Customers.ID) c
where
c.offline_count > 0
Assuming data_table has information to specify if a user is online or offline and there can be multiple entries. Aggregate the users and do sum of the offline count, if its > 0 after aggregating then you know that user has an offline entry. You can achieve similar with HAVING clause.
You could use NOT EXISTS to filter out computers whose owners have a computer online:
SELECT * FROM Computers
LEFT JOIN Computers_On_Customers ON Computer_ID = Computers.ID
LEFT JOIN Customers ON Computers_On_Customers.Customer_ID = Customers.ID
WHERE
Computers.Currently_Online LIKE "0" AND
NOT EXISTS (
SELECT 1 FROM Computers_On_Customers
LEFT JOIN Computers ON Computer_ID = Computers.ID
WHERE
Currently_Online LIKE "1" AND
Computers_On_Customers.Customer_ID = Customers.ID
)
Assuming you just want the Customer data - surely you can just roll the whole thing up with a GROUP BY clause ... and since you're only after records where the maximum value of Computers.Currently_Online = 0 you should be able to use a MAX function to exclude anything where that value > 0.
SELECT Customers.*, MAX(Computers.Currently_Online) AS computer_online
FROM Customers
INNER JOIN Computers_On_Customers
ON Customers.ID = Computers_On_Customers.Customer_ID
INNER JOIN Computers
ON Computers_On_Customers.Computer_ID = Computers.ID
GROUP BY Customers.ID
HAVING computer_online = 0
Personally I don't like nesting SELECTs unless I absolutely have to ... and in those instances I'd prefer to put it into a stored procedure.

SQL count with two table

was wondering if someone could help a newbie out with some sql?
Right so basically I want to get all the CustomerID's associated with a certain countrycode I can get that by doing a simple query on the customer table however I then need to find out how many forms the customer has submitted in the order table.
so in summary I need to get a list of customerID's from the Customer table and count how many time they show up in the Order table.
What's the easiest way to go about this?
P.S. please ignore any data type / relationship issues with the image above, it's just an example.
Thanks.
Join the tables, filter on country code and group the results:
SELECT CustomerID, COUNT(*)
FROM query_test.customer JOIN query_test.Order USING (CustomerID)
WHERE query_test.customer.countrycode = ?
GROUP BY CustomerID
You could use
SELECT
c.customerID,
count(o.form_id) AS form_count
FROM
customer c INNER JOIN order o
ON c.customerID = o.customerID
WHERE
c.countrycode = ??
GROUP BY
c.customerID
If you have customers that don't have any orders, then you will want a left outer join:
SELECT c.CustomerID, COUNT(o.CustomerID) as NumOrders, COUNT(distinct FormID) as NumForms
FROM query_test.customer c LEFT JOIN
query_test.Order o
on c.CustomerID = o.CustomerId
WHERE c.countrycode = ?
GROUP BY c.CustomerID;
Note this counts both the number of orders and the number of forms.

SQL Joins across multiple tables

I am building an online survey system for which I wish to produce statistics. I want query based on the gender of the user. I have the following tables:
survey_question_options
survey_answer
users
I have constructed the following query so that it brings back a null response where there are no answers to the question:
SELECT COUNT(sa.option_id) AS answer , so.option_label
FROM survey_answer sa
RIGHT JOIN survey_question_options so
ON sa.option_id = so.option_id AND
sa.record_date>='2011-09-01' AND
sa.record_date<='2012-08-01'
LEFT JOIN users u
ON (sa.uid = u.uid AND u.gender='F')
WHERE so.question_id=24
GROUP BY so.option_label
ORDER BY so.option_id ASC
My query returns the following results set:
0 Red
1 Yellow
0 Blue
0 Green
However, the gender condition in the LEFT JOIN appears to be ignored in the query. When I change the gender to 'M' the same result is returned. However, the expected result would be 0 for everything.
I am not sure where I am going wrong. Please help.
Thanks in advance.
Well, you are doing a COUNT on a column from the main table, so the gender condition on the LEFT JOIN won't affect the result. You should do the COUNT on a column from the users table. I'm not sure if this is what you want, but you should try:
SELECT COUNT(u.uid) AS answer , so.option_label
FROM survey_answer sa
RIGHT JOIN survey_question_options so
ON sa.option_id = so.option_id AND
sa.record_date>='2011-09-01' AND
sa.record_date<='2012-08-01'
LEFT JOIN users u
ON (sa.uid = u.uid AND u.gender='M')
WHERE so.question_id=24
GROUP BY so.option_label
ORDER BY so.option_id ASC
The left join to the users table is evaluated after the join to the answer table - so although the user record is not returned if the user is the wrong gender, the answer record will be returned (regardless of the user's gender). Try:
SELECT COUNT(sa.option_id) AS answer , so.option_label
FROM (select a.option_id
from survey_answer a
JOIN users u ON a.uid = u.uid AND u.gender='F'
where a.record_date>='2011-09-01' AND
a.record_date<='2012-08-01') sa
RIGHT JOIN survey_question_options so
ON sa.option_id = so.option_id
WHERE so.question_id=24
GROUP BY so.option_label
ORDER BY so.option_id ASC
You're putting your condition in the wrong block. Since you're performing a LEFT JOIN, (which is a left-bound outer join) everything in the left table (the main table) is selected, together with the data from the joined table, where applicable. What you want is to add the data from all users and then restrict the full output of the query. What you've actually done is add the user data from only the female users and then displayed all data.
Sounds technical, but all you have to do is move the AND u.gender='F' into the main WHERE clause instead the ON clause. That will cause SQL to only select the rows for female users after the JOIN has taken place.

Mysql - Join matches and non-matches

This is related to my other question:
Managing Foreign Keys
I am trying to join the table of matches and non-matches.
So I have a list of interests, a list of users, and a list of user interests.
I want the query to return all interests, whether the user has the interest or not (should be null in that case), only where the user = x. Every time I get the query working its only matching interests that the user specifically has, instead of all interests whether they have it or not.
You should rather use LEFT JOINS
Something like
SELECT *
FROM interests i LEFT JOIN
userinterests ui ON i.interestID = ui.interestID LEFT JOIN
users u ON ui.userID = u.uiserID
WHERE userID = ?
where is the user id you are looking for.
SELECT *
FROM interests i
LEFT JOIN userinterests ui ON i.interestID = ui.interestID
LEFT JOIN users u ON ui.userID = u.uiserID
and u.userID = ?
If you put a where condition on a table that should have no records inteh main tbale, you convert the join from a left join to an inner join. The only time you should ever have a condition inthe where clasue for something one the right side of a left join is when you are searching for records that don't match (where u.userid is null, for instance)
Of course you should define the fields to be selected and never use select * in production code especially not when you have a join as it sends repeated information across the network (the data inteh join feilds is repeated) and is a waste of resources and poor prgramming practice for multiple reasons.

Categories