How to select users without specific one to many rows in MySQL - mysql

Consider the following data set:
users table:
id (int) email (string)
1 first#example.com
2 second#example.com
order_items table:
id (int) user_id (int) generation (string)
1 1 '11'
2 1 '12'
2 1 '12.50'
3 1 '16.00'
4 2 '11'
5 2 '12'
UPDATED question
How can I select users which doesn't have order_items with generation 16.00 and have at least one order_item?
So:
email
second#example.com

1) Returning Users who don't have order item with generation 16 included users with no orders at all.
Assuming you have some kind of id column in order_items table:
select u.* from users u
left outer join order_items oi on (u.id = oi.user_id and oi.generation = 16)
where oi.id is null;
Otherwise use whatever primary key you have in order_items in the where condition to be NULL.
Updated to include answer for the question in comment
2) Returning users who don't have order item with generation 16 but have least one order.
select distinct u.* from users u
left outer join order_items oi16 on (u.id = oi.user_id and oi.generation = 16)
join order_items oiother on (u.id = oiother.user_id and oiother.generation != 16)
where oi16.id is null;
We do the filtering by using a second (normal) join which only returns users where it finds matching rows from the order_items table.
Here we need the distinct because the second join will multiply your rows depending on how many other orders the user have.
Alternatively you can also do a count or sum like this:
select u.*, count(distinct oiother.id) from users u
left outer join order_items oi16 on (u.id = oi.user_id and oi.generation = 16)
join order_items oiother on (u.id = oiother.user_id and oiother.generation != 16)
where oi16.id is null
group by u.id;
This will give you also how many other order items each returned user have. Or omit the count completely and using group by just to return distinct items.

You can use NOT EXISTS() like this:
SELECT * FROM Users u
WHERE NOT EXISTS(SELECT 1 FROM order_items o
WHERE o.userid = u.id
AND o.generation = 16)
That checks if there is a record for this user with order.generation = 16, and if there isn't it selects him.
Or not in()
SELECT * FROM Users u
WHERE u.id NOT IN(SELECT userid FROM order_items o
WHERE o.generation = 16)
That selects the list of users who have order.generation = 16, and select every id except them.

Following query should give you the desired output:
*update*
changed query as per the new result format in the question
As we want the data only from generation table, join with user table is not needed anymore. Here's the updated query:
select id, generation
from mytable where id not in (
select id from mytable
where generation = 16
group by id
);
Here is the SQL fiddle for it.

Related

SQL LEFT JOIN for all records of first table and matched records of another table

I have 3 tables.
Table seller with columns like id, name etc.
Table customer like id, name etc.
Table connections which have seller_id, customer_id, status of friendship like "friends", "pending_request" etc.
Now I want to get all the sellers who are not friends of a specific customer.
So I tried like fetching records from seller table with left join of connections table, with condition status is not "friends"
I tried the following query but didn't help me. I also tried other queries but didn't help.
SELECT * FROM `seller` LEFT JOIN `connections` ON seller.user_id = connections.user_id WHERE customer_id = 10 AND request_status NOT LIKE "friends"
Here is the reference screen I want the result. Like for a particular customer, all the sellers who are not friends or request is pending.
Join the connections of type 'friends' for customer_id = 10 and in a WHERE clause check for the connections.user_id being NULL, i.e. nothing has been joined.
SELECT *
FROM seller
LEFT JOIN connections
ON seller.user_id = connections.user_id
AND connections.customer_id = 10
AND connections.request_status = 'friends'
WHERE connections.user_id IS NULL;
Or use a correlated subquery, that gets the connection with a NOT EXISTS.
SELECT *
FROM seller s
WHERE NOT EXISTS (SELECT *
FROM connections c
WHERE c.user_id = s.user_id
AND c.customer_id = 10
AND c.request_status = 'friends');
Try this:
SELECT * FROM `seller` LEFT JOIN `connections` ON seller.user_id = connections.user_id WHERE customer_id = 10 AND (request_status NOT LIKE "friends"
or request_status is null)
Or this:
SELECT * FROM `seller` LEFT JOIN `connections` ON seller.user_id = connections.user_id WHERE customer_id = 10 AND IFNULL(request_status,"other") NOT LIKE "friends"

How to Join three tables properly

The main table has 4 columns:
User Activity Table
userActivityId userId therapistId activityId
1 1 1 1
Each of these columns is a table and these values are all foreign keys.
Basically im trying to run a query that will join to the users table and pull their first and last name based off the user Id.Same thing with therapist - join to the therapist table, pull first + last name.And finally Join to the Activity table and pull the activity name and path from the activity Id
The other tables look like this:
User Table
userId fName lName
Therapist Table
therapistId therapistFirstName therapistLastName
Activity Table
activityId activityTitle activityPath
So far my query looks like
SELECT
User_Activities.userId,
User_Activities.therapistId,
User_Activities.activityId,
Activities.activityTitle,
Activities.activityPath,
Users.fName,
users.lName,
Therapists.therapistFirstName,
Therapists.therapistLastName
FROM
User_Activities
INNER JOIN Users
ON User_Activities.userId = Users.userId
INNER JOIN Therapists ON
User_Activities.therapistId = Therapists.therapistId
INNER JOIN Activities ON
Activities.activityId = User_Activities.userActivityId
WHERE
User_Activities.userId = 1;
When I run this query It only returns 1 row as a result. However there are two activities in the User_Activites table assigned to userId 1.
If I change : INNER JOIN Activities ON
Activities.activityId = User_Activities.userActivityId
from an INNER JOIN to the LEFT JOIN it will display the second row, however the activityTitle and activityPath will be displayed as NULL in the second row.
userActivityId userId therapistId activityId activityId activityTitle activityPath fName lName therapistFirstName therapistLastName
1 1 1 1 1 Brain GZZ0zpUQ S C M D
11 1 1 1 NULL NULL NULL S C M D
You have pretty much answered your question. The second activity does not have a valid ActivityId.
If you want all activities for a user, then you should phrase the query as:
SELECT . . .
FROM Users u LEFT JOIN
User_Activities ua
ON ua.userId = u.userId LEFT JOIN
Therapists t
ON ua.therapistId = t.therapistId LEFT JOIN
Activities a
ON a.activityId = ua.userActivityId
WHERE u.userId = 1;
You want to start with the table where you want to keep all the rows. Then use LEFT JOIN to bring in other tables.
Two other changes of note:
Table aliases are used to simplify reading and writing the query. The SELECT needs to change to use the aliases.
The WHERE clause refers to the Users table rather than UserActivities.

"Combine" FULL OUTER JOIN and NOT IN?

I have two tables:
+-----------------------+
| Tables_in_my_database |
+-----------------------+
| orders |
| orderTaken |
+-----------------------+
In orders, there are attributes
orderId, orderName, isClosed and orderCreationTime.
In orderTaken, there are attributes
userId, orderId and orderStatus.
Let's say when
orderStatus = 1 --> the customer has taken the order
orderStatus = 2 --> the order has been shipped
orderStatus = 3 --> the order is completed
orderStatus = 4 --> the order is canceled
orderStatus = 5 --> the order has an exception
Basically the mechanism of my project is running like: A user with a unique userId will be able to take an order from the web page, where each order has its own unique orderId as well. After taken, the orderTaken table will record the userId, orderId and initially set orderStatus = 1. The shop then update the orderStatus based on various situations. Once the shop has updated isClosed = 1 then this order wouldn't be displayed at all no matter the user has taken it or not(not make sense but it's just a isClosed == 0 in the query).
Now, I want to construct a web page that will show both the new orders that the user hasn't taken yet (which should be the orders that their orderIds are not recorded in the orderTaken table under this user's userId), and the orders that the user has already taken with the orderStatus shown BUT the orderStatus IS NOT 4 or 5, group by orderCreationTime DESC (yea, maybe not make sense if I don't have a orderTakenTime but let's keep it that way), like:
OrderId 4
Order Name: PetPikachu
orderStatus = 1
CreationTime: 5am
OrderId 3
Order Name: A truck of hamsters
orderStatus = 3
CreationTime: 4am
OrderId 2
New order
Order Name: Macbuk bull
CreationTime: 3am
OrderId 1
Order Name: Jay Chou's Album
orderStatus = 2
CreationTime: 2am
I have this query written based on the knowledge I've learned:
SELECT * FROM orders A WHERE A.isClosed == '0' FULL OUTER JOIN orderTaken B WHERE B.userId = '4' AND (B.orderStatus<>'4' OR B.orderStatus<>'5') ORDER BY A.orderCreationTime DESC;
Apparently this query doesn't work, but I'm afraid to have a
ON A.orderId = B.orderId
since then the table returned will eliminate the new orders that the orderId hasn't been recorded in orderTaken B. I've also tried a NOT IN clause like
SELECT * FROM orders A WHERE A.isClosed = '0' AND A.orderId NOT IN (SELECT orderId FROM orderTaken B WHERE B.userId = '$userId' AND (B.orderStatus='4' OR B.orderStatus='5')) ORDER BY creationTime DESC;
This query works but it doesn't have the field orderStatus from orderTaken B in the returned table. I was thinking to add another JOIN orderTaken B clause after this query to get the fields from B but I think that's not a good way to write a query.
I just wanna kinda combine "NOT IN" and "FULL JOIN". Can anybody help me out? Thanks!
Just like #terje-d said, what you need is LEFT JOIN. Updated it with the the original table names and fixed the $userId filter.
For all open orders and incomplete orders.
SELECT o.`orderId`,
o.`orderName`,
ot.`orderStatus`,
o.`orderCreationTime`
FROM orders o
LEFT JOIN orderTaken ot
ON o.orderId = ot.orderId
WHERE o.isClosed = 0
AND (
ot.orderId IS NULL
OR ot.orderStatus NOT IN (4,5)
)
ORDER BY o.`orderCreationTime` DESC
For all open orders and incomplete orders for a particular user
SELECT o.`orderId`,
o.`orderName`,
ot.`orderStatus`,
o.`orderCreationTime`
FROM orders o
LEFT JOIN orderTaken ot
ON o.orderId = ot.orderId
WHERE o.isClosed = 0
AND ( ot.orderStatus IS NULL
OR (
ot.user_id = ?
AND ot.orderStatus NOT IN (4,5)
)
)
ORDER BY o.`orderCreationTime` DESC
You seem to want to find the records in orders that is not assigned to an user (i.e. does not have a related record in orderTaken) plus the ones that are assigned to an user, but where the orderStatus is not 4 or 5.
Then a full outer join is not needed as there will be no records in orderTaken without a related record in orders. A Left inner join can be used to find all the records from orders, an onclause will include data from the related records from orderTaken and the where clause can then filter out orders taken by other users, or where orderStatus is 4 or 5:
SELECT o.*, ot.userID, ot.orderStatus
FROM orders o
LEFT JOIN orderTaken ot
ON ot.orderID = o.orderID
WHERE o.isClosed = 0
AND (ot.userID IS NULL OR ot.userID = $userID AND ot.orderStatus NOT IN (4,5))
ORDER BY o.creationTime DESC

SQL SUM Help - Only returning 1 field

My SQL is only returning one field when it should be returning one for each user.
Any idea where I'm going wrong? If you need additional information I can provide, but I'm just not sure where to go with this at the moment.
Here is my SQL:
SELECT uId, uForename, SUM(biProductPrice * biQuantity) AS uTotalSpent
FROM users
LEFT JOIN orders ON uId = ordUserId
LEFT JOIN basket ON ordUserId = bUserId
LEFT JOIN basketitems ON bId = biBasketId
WHERE ordStatus BETWEEN 4 AND 50
GROUP BY uId, uForename
any columns starting with u belong to the users table.
any columns starting with ord belong to the orders table.
any columns starting with b belong to the basket table.
any columns starting with bi belong to the basketitems table.
EDIT:
Everything now works fine except for my SUM, there are only 2 fields with an ordStatus between 4 and 50, so they are the only ones that apply, the biQuantity for one is 8 and the biProductPrice is 100, the other field has a biQuantity of 1 and a biProductPrice of 100, why is it returning a value of 400?
Group by the user and the sum will be returned for each one
SELECT users.id, users.name, SUM(biProductPrice) AS uTotalSpent
FROM users
LEFT JOIN orders ON uId = ordUserId
LEFT JOIN basket ON ordUserId = bUserId
LEFT JOIN basketitems ON bId = biBasketId
WHERE ordStatus BETWEEN 4 AND 50
group by users.uId, users.name
SELECT users.id, users.name, SUM(biProductPrice) AS uTotalSpent
FROM users
LEFT JOIN orders ON uId = ordUserId
LEFT JOIN basket ON ordUserId = bUserId
LEFT JOIN basketitems ON bId = biBasketId
WHERE ordStatus BETWEEN 4 AND 50
group by users.uId, users.name

Select Rows From Second Table Based On id From First

I have 2 tables
1_products
id, code, make, model, fk_group_id
1_stock
id, stock, repair
I want to be able to return all of the rows in both tables based on the match in the first. Say WHERE fk_group_id = 11
Here is one:
SELECT *
FROM products AS p
INNER JOIN stock AS s ON p.id = s.id
WHERE fk_group_id = 11