SELECT *
FROM users
WHERE id
IN ( 2024 )
AND id NOT IN (
SELECT user_id
FROM `used`
WHERE DATE_SUB( DATE_ADD( CURDATE( ) , INTERVAL 7 DAY ) , INTERVAL 14 DAY ) <= created)
AND id NOT IN (
SELECT user_id
FROM coupon_used
WHERE code = 'XXXXX')
AND id IN (
SELECT user_id
FROM accounts)
I have id 2024 in users table, but this id 2024 is there in used tables. So when I run this query, it shows me 2024 id also, which should be filtered out. I run the query where I selected specific users, and then I want these user to be filter out that they should not be in used table. But above query is not giving me the desire result. Desire Result is that I want to Select Users by following conditions: Take Specific Users, and check that they are not in used table and not in coupon_used table but they should be in accounts table.
I would use left joins for the exclusion conditions and a regular join for the inclusions:
SELECT users.*
FROM users
INNER JOIN accounts ON accounts.user_id = users.id
LEFT JOIN used ON used.user_id = users.id AND DATE_SUB(CURDATE(), INTERVAL 7 DAY) <= used.created)
LEFT JOIN coupon_used ON coupon_used.user_id = users.id AND coupon_used.code = 'XXXX'
WHERE id IN (2024) AND used.user_id IS NULL AND coupon_used.user_id IS NULL
I've edited the date manipulation as well; +7 -14 would be -7 :)
I would recommend using a JOIN on accounts and LEFT OUTER JOINs on the other two tables. A JOIN on accounts means it must be in the accounts table. LEFT OUTER JOINS on the coupon_used and used means it will return a record no matter if they're in that table or not. Filtering down to c.user_id IS NULL means that there is NOT a record in that table.
SELECT users.*
FROM users
JOIN accounts ON users.id = accounts.user_id
LEFT OUTER JOIN coupon_used c ON users.id = c.user_id AND c.code = 'XXXXX'
LEFT OUTER JOIN `used` u ON users.id = u.user_id AND DATE_SUB( DATE_ADD( CURDATE( ) , INTERVAL 7 DAY ) , INTERVAL 14 DAY ) <= u.created
WHERE id IN ( 2024 )
AND c.user_id IS NULL
AND u.user_id IS NULL
Firstly, try something like this using joins. Which should be easier to read and (depending on the version of MySQL) faster
SELECT DISTINCT users.*
FROM users
INNER JOIN accounts ON users.id = accounts.user_id
LEFT OUTER JOIN coupon_used ON users.id = coupon_used.user_id AND coupon_used.code = 'XXXXX'
LEFT OUTER JOIN `used` ON users.id = `used`.user_id AND DATE_SUB( DATE_ADD( CURDATE( ) , INTERVAL 7 DAY ) , INTERVAL 14 DAY ) <= `used`.created
WHERE id IN ( 2024 )
AND coupon_used.user_id IS NULL
AND `used`.user_id IS NULL
EDIT - Simplifying the date check:-
SELECT DISTINCT users.*
FROM users
INNER JOIN accounts ON users.id = accounts.user_id
LEFT OUTER JOIN coupon_used ON users.id = coupon_used.user_id AND coupon_used.code = 'XXXXX'
LEFT OUTER JOIN `used` ON users.id = `used`.user_id AND DATE_SUB( CURDATE( ) , INTERVAL 7 DAY ) <= `used`.created
WHERE id IN ( 2024 )
AND coupon_used.user_id IS NULL
AND `used`.user_id IS NULL
Related
I have a MySQL (v5.7.26) query that runs forever. Here is the query:
SELECT
ur.user_id AS user_id,
sum(r.duration) AS total_time,
count(user_id) AS number_of_workouts
FROM user_resource ur
INNER JOIN resource r ON r.id = ur.resource_id
WHERE
ur.status = 1
AND NOT ur.action_date IS NULL
AND ur.user_id IN (
SELECT user_id
FROM user_resource ur2
WHERE ur2.action_date >= now() - INTERVAL 2 DAY
)
AND r.type = 'WORKOUT'
GROUP BY ur.user_id;
I have played a bit with it, by trying to understand where is the problem. For the testing purposes, I tried breaking in two. So:
SELECT user_id
FROM user_resource ur2
WHERE ur2.action_date >= now() - INTERVAL 2 DAY;
That returns (very quickly) list of user user_id's.
When I plug the returned result in to the first part of the query, like this:
SELECT
ur.user_id AS user_id,
sum(r.duration) AS total_time,
count(user_id) AS number_of_workouts
FROM user_resource ur
INNER JOIN resource r ON r.id = ur.resource_id
WHERE
ur.status = 1
AND NOT ur.action_date IS NULL
AND ur.user_id IN (1,1,1,4,4,5,6,7,7,7);
AND r.type = 'WORKOUT'
GROUP BY ur.user_id
It runs very fast. My assumption is the IN (Subquery) is the bottleneck.
I was thinking to extract the subquery and get the user_ids, and then used it as a variable, but I am not sure is it the good approach, and additionally I am having issues with it. this is my attempt:
-- first statement
SET #v1 = (SELECT user_id
FROM user_resource ur2
WHERE ur2.action_date >= now() - INTERVAL 2 DAY)
-- second statement
SELECT
ur.user_id AS user_id,
sum(r.duration) AS total_time,
count(user_id) AS prefixes
FROM user_resource ur
INNER JOIN resource r ON r.id = ur.resource_id
WHERE
ur.status = 1
AND NOT ur.action_date IS NULL
AND ur.user_id IN (#v1);
AND r.type = 'WORKOUT'
GROUP BY ur.user_id
Problem here is that the first statement returns an error:
Subquery returns more than 1 row.
Expected result are user_id's, that can be duplicates. And I need those duplicated for the count.
How can I fix this?
Try EXISTS instead of IN
...
AND EXISTS (SELECT *
FROM user_resource ur2
WHERE ur2.user_id = ur.user_id
AND ur2.action_date >= now() - INTERVAL 2 DAY)
...
and indices on user_resource (user_id, action_date), user_resource (status, action_date, user_id) and/or user_resource (type).
You could try:
-- first statement
SET #v1 = (SELECT GROUP_CONCAT(user_id)
FROM user_resource ur2
WHERE ur2.action_date >= now() - INTERVAL 2 DAY)
-- second statement
SELECT
ur.user_id AS user_id,
sum(r.duration) AS total_time,
count(user_id) AS prefixes
FROM user_resource ur
INNER JOIN resource r ON r.id = ur.resource_id
WHERE ur.status = 1 AND NOT ur.action_date IS NULL AND FIND_IN_SET(ur.user_id,#v1)
AND r.type = 'WORKOUT'
GROUP BY ur.user_id
Additional join will be faster then sub-query:
SELECT
ur.user_id AS user_id,
sum(r.duration) AS total_time,
count(user_id) AS number_of_workouts
FROM user_resource ur
INNER JOIN resource r ON r.id = ur.resource_id
INNER JOIN (
SELECT user_id
FROM user_resource ur2
WHERE ur2.action_date >= now() - INTERVAL 2 DAY
) t ON t.user_id = ur.user_id
WHERE
ur.status = 1
AND NOT ur.action_date IS NULL
AND r.type = 'WORKOUT'
GROUP BY ur.user_id;
I'm getting a "Subquery returns more than 1 row" error while running a query that's meant to return results of two subqueries. Why is returning more than one row a problem here, and how can I get around this problem?
Data tables and relevant fields look like this:
Accounts
id
Meetings
account_id
assigned_user_id
start_date
Users
id
last_name
A meeting is assigned to an account and to a user. I'm trying to create a table that will display the quantities of meetings per assigned user per account where the meeting start date is within different date ranges. The date ranges should be arranged in the same row, as a table with these headings:
Account | User's Last Name | Meetings 1-31 days in the future | Meetings 31-60 days in the future
as shown in this image:
.
This is my query:
SELECT
(SELECT
a.name
FROM
accounts AS a
JOIN
meetings AS m ON a.id = m.account_id
AND date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Account',
(SELECT
u.last_name
FROM
accounts AS a
JOIN
meetings AS m ON a.id = m.account_id
AND date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Name',
(SELECT
COUNT(m.id)
FROM
accounts AS a
JOIN
meetings AS m ON a.id = m.account_id
AND date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 30 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Meetings 1-30 days',
(SELECT
COUNT(m.id)
FROM
accounts AS a2
JOIN
meetings AS m ON a.id = m.account_id
AND m.date_start BETWEEN DATE_ADD(CURDATE(),INTERVAL 31 DAY) AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Meetings 31-60 days'
Columns containing the names of accounts and names of users had to be added as subqueries in order to avoid "Operand should contain 1 column(s)" errors. Columns corresponding to the counts of meetings had to be subqueries because no single row of the joined table can fit both date ranges at the same time. Each subquery returns the expected results when run individually. But I get "Subquery returns more than 1 row" when the subqueries are put together as shown. I tried assigning different aliases to each subquery, but that did not help.
SQL queries do not return nested result sets; so an expression (such as a subquery) used in a SELECT clause cannot have multiple values, as that would "nest" it's values. You more likely just need to use conditional aggregation, like so:
SELECT a.id, u.id, a.name, u.last_name
, COUNT(CASE WHEN m.date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 30 DAY) THEN 1 ELSE NULL END) AS `Meetings 1-30 days`
, COUNT(CASE WHEN m.date_start BETWEEN DATE_ADD(CURDATE(),INTERVAL 31 DAY) AND DATE_ADD(CURDATE(),INTERVAL 60 DAY) THEN 1 ELSE NULL END) AS `Meetings 31-60 days`
, COUNT(CASE WHEN THEN 1 ELSE NULL END) AS
FROM accounts AS a
JOIN meetings AS m ON a.id = m.account_id
JOIN users AS u ON m.assigned_user_id = u.id
WHERE m.status = 'Planned' AND m.deleted = 0
AND m.date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
GROUP BY a.id, u.id, a.name, u.last_name
;
Notes: ELSE NULL is technically automatic, and can be omitted; it is just there for clarity. Aggregate functions, such as COUNT, ignore NULL values; the only time null values affect such functions is when they encounter only null values (in which case their results are null).
Sidenote: You could have continued with your query in a form similar to what you originally had; if you included the grouping fields in the subqueries' results, the subqueries could have been joined together (but that would have been a lot of redundant joining of accounts, meetings, and users).
I have following query:
SELECT *
FROM users
WHERE id NOT
IN (
SELECT user_id
FROM `bids`
WHERE DATE_SUB( DATE_ADD( CURDATE( ) , INTERVAL 7
DAY ) , INTERVAL 14
DAY ) <= created
)
AND id NOT
IN (
SELECT user_id
FROM coupon_used WHERE code = 'ACTNOW'
)
AND id
IN (
SELECT user_id
FROM accounts
)
I just want to take specific users and search on them, instead of searching on all users in the table. Like I have the list of users with id 1,2,3,4,5 I only want to search on these users
Just add a WHERE clause using IN()
SELECT *
FROM users
WHERE id IN(1,2,3,4,5)
I believe using left outer joins will simplify your query and hopefully improve performance
SELECT users.*
FROM users
LEFT OUTER JOIN bids on bids.user_id = users.id AND DATE_SUB(DATE_ADD(CURDATE(), INTERVAL 7 DAY), INTERVAL 14 DAY) <= bids.created
LEFT OUTER JOIN coupon_used on coupon_used.user_id = users.id AND coupon_used.code = 'ACTNOW'
INNER JOIN accounts on accounts.user_id = users.id
WHERE bids.id is null AND coupon_used.id is null
AND users.id in (1,2,3,4,5)
I'm trying to write a query to join a user table to an activity logging table and return the following for each user:
A) The time they last logged in.
B) The number of logins in the last 3 months.
This is what I have come up with so far:
SELECT A.UserID, COUNT( Activity ) AS Logins, MAX( TIME ) AS LastLogin
FROM UserMaster A
LEFT JOIN UserWebActivity B ON A.UserID = B.UserID
AND Activity = 'Login'
AND TIME BETWEEN DATE_SUB( NOW( ) , INTERVAL 3 MONTH ) AND NOW( )
GROUP BY A.UserID
This almost works, but it doesn't return the latest login for any user that hasn't logged in within the last 3 months. How can I get count() and max() to work together properly?
First solve each problem separately:
SELECT A.UserID, MAX(TIME) AS LastLogin
FROM UserMaster A
LEFT JOIN UserWebActivity B
ON A.UserID = B.UserID
AND Activity = 'Login'
GROUP BY A.UserID
SELECT A.UserID, COUNT(Activity) AS Logins
FROM UserMaster A
LEFT JOIN UserWebActivity B ON A.UserID = B.UserID
AND Activity = 'Login'
AND TIME BETWEEN (NOW() - INTERVAL 3 MONTH) AND NOW( )
GROUP BY A.UserID
Test them separately to ensure that each of these queries works as you want, and adjust them if necessary.
Then when you are happy that they both work, join the results together:
SELECT T1.UserID, T1.LastLogin, T2.Logins
FROM
(
SELECT A.UserID, MAX(TIME) AS LastLogin
FROM UserMaster A
LEFT JOIN UserWebActivity B
ON A.UserID = B.UserID
AND Activity = 'Login'
GROUP BY A.UserID
) AS T1
JOIN
(
SELECT A.UserID, COUNT(Activity) AS Logins
FROM UserMaster A
LEFT JOIN UserWebActivity B
ON A.UserID = B.UserID
AND Activity = 'Login'
AND TIME BETWEEN (NOW() - INTERVAL 3 MONTH) AND NOW()
GROUP BY A.UserID
) AS T2
ON T1.UserID = T2.UserID
This will allow MySQL to make best use of the indexes for the different queries.
You can use a CASE statement in your COUNT:
SELECT A.UserID,
COUNT(
CASE WHEN TIME BETWEEN DATE_SUB( NOW( ) , INTERVAL 3 MONTH ) AND NOW( )
THEN Activity
END ) AS Logins,
MAX( TIME ) AS LastLogin
FROM UserMaster A
LEFT JOIN UserWebActivity B ON A.UserID = B.UserID
AND Activity = 'Login'
GROUP BY A.UserID
This solution however will be less efficient than #MarkByers if you have a large amount of data.
Just as one more crazy idea - instead of MAX( TIME )
coalesce(max(time), (select max(time) from UserWebActivity where ...) )
I have a table carts and cartitems, the second one has a foreign key to the first. Now I want to delete all rows from carts that are older than 3 months and have no related cartitems. The following query gives me the correct result:
SELECT *
FROM `carts` c
LEFT OUTER JOIN `cartitems` i ON ( `c`.`id` = `i`.`cart_id` )
WHERE `c`.`last_updated` < DATE_SUB(NOW() , INTERVAL 3 MONTH)
GROUP BY `c`.`id`
HAVING COUNT( `i`.`id` ) = 0;
But I can't figure out how to turn this query into a DELETE.
Also, since there are ~10 million rows in the carts table, I'd be thankful for suggestions on how to improve this query :-)
You can run the following without LIMIT or with a LIMIT to delete the rows in batches.
DELETE c
FROM carts c
WHERE c.last_updated < DATE_SUB(NOW() , INTERVAL 3 MONTH)
AND NOT EXISTS
( SELECT *
FROM cartitems i
WHERE c.id = i.cart_id
)
LIMIT 10000 ; --- optional
You can try with this
DELETE FROM
(
SELECT *
FROM `carts` c
LEFT OUTER JOIN `cartitems` i ON ( `c`.`id` = `i`.`cart_id` )
WHERE `c`.`last_updated` < DATE_SUB(NOW() , INTERVAL 3 MONTH)
GROUP BY `c`.`id`
HAVING COUNT( `i`.`id` ) = 0
);
I am not sure about it though :)
That exists query should work:
DELETE FROM `carts` WHERE EXISTS (SELECT *
FROM `carts` c
LEFT OUTER JOIN `cartitems` i ON ( `c`.`id` = `i`.`cart_id` )
WHERE `c`.`last_updated` < DATE_SUB(NOW() , INTERVAL 3 MONTH)
GROUP BY `c`.`id`
HAVING COUNT( `i`.`id` ) = 0);
This query should delete all rows that are in the result set.
Please note that you can exchange the asterisk with null or any other value.
A second try:
DELETE FROM `carts` WHERE `carts`.`id` IN (SELECT `c`.`id`
FROM `carts` c
LEFT OUTER JOIN `cartitems` i ON ( `c`.`id` = `i`.`cart_id` )
WHERE `c`.`last_updated` < DATE_SUB(NOW() , INTERVAL 3 MONTH)
GROUP BY `c`.`id`
HAVING COUNT( `i`.`id` ) = 0);
That one generates a list of ids (I hope that c.id is your primary key) and delete all rows related to that ids.