Subquery in MySQL with joined tables - mysql

I am trying to create a subquery but am failing miserably at it. My query fails with this error...
Operand should contain 1 column(s)
What I want is to find all accounts where the schickUpdatedDate field has a date value within the last 40 days and no not have either 'Initial Waranty' or 'None' as values in the support_c field.
SELECT SQL_CALC_FOUND_ROWS * FROM accounts
LEFT JOIN accounts_cstm ON accounts.id = accounts_cstm.id_c
WHERE schickUpdatedDate BETWEEN NOW() - INTERVAL 40 DAY AND NOW()IN
(SELECT * FROM accounts_cstm WHERE support_c != 'Initial Waranty' OR support_c != 'None')
ORDER BY schickUpdatedDate ASC
All of the fields above are in the accounts_ctsm table, the accounts table if joined for another purpose other than this.

If I understand correctly you just want to show the joined output filtered by 3 criteria. I don't think you need a subquery for it then.
Does this work?
SELECT *
FROM accounts
LEFT JOIN accounts_cstm ON accounts.id = accounts_cstm.id_c
WHERE schickUpdatedDate >= (curdate() - interval 40 day)
and support_c <> 'Initial Waranty'
and support_c <> 'None'
ORDER BY schickUpdatedDate ASC;

Related

Get results from first table even if it's missing data. Right Join?

I have two tables. One has a list of all tournaments that are running, and the other has all the scores for those tournaments.
I have written a query to return all the id's and end_dates for tournaments that have ended (end_time <= now) and where all the scores for that tournament have been verified (equal to 1).
SELECT rt.id, rt.difficulty
FROM scores s JOIN running_tournaments rt
ON s.tourn_id = rt.id WHERE end_time <= NOW()
GROUP BY rt.id having min(s.verified) = 1
This query works if there is at least 1 score in the tournament. If the tournament has ended and no scores were entered, nothing is returned by the query.
How can I return results when no scores have been entered in a tournament?
You are right. You can use outer join and use count condition in the having claise as follows:
SELECT rt.id,
rt.difficulty
FROM scores s
RIGHT JOIN running_tournaments rt ON s.tourn_id = rt.id
And S.end_time <= NOW()
GROUP BY RT.ID
having min(s.verified) = 1 or count(s.verified) = 0
I recommend LEFT JOIN -- keep everything in the first table -- and reverse the order of the table references:
SELECT rt.id, rt.difficulty
FROM running_tournaments rt JOIN
scores s
ON s.tourn_id = rt.id
WHERE end_time <= NOW()
GROUP BY rt.id
HAVING MIN(s.verified) = 1

Joining 2 tables and getting data from T1 that doesn't have a match in T2

First let me say thank you for your help and this is probably really simple and I'm just being a dummy
I have a table 'users' with fname, lname, usersnumber, active, shift
I have a second table 'checkin' with userID, dateandtime.
I'm trying to get a list of everyone that hasn't checked in in the last 8 hours. I have done that but only if they have checked in at least once in the past. If they are a new user I can't figure out how to get them to show up.
Query that I have so far:
SELECT u.lname, u.fname, u.usersnumber FROM users u
LEFT JOIN checkin c
on u.usersnumber = c.usersnumber
WHERE u.active = 'Yes' and u.shift = 'Day'
AND c.dateandtime < DATE_ADD(NOW(), INTERVAL -8 HOUR)
ORDER By lname;
I would just use not exists for this:
select u.*
from users u
where
u.active = 'Yes'
and u.shift = 'Day'
and not exists (
select 1
from checkin c
where
c.usersnumber = u.usersnumber
and u.dateandtime >= now() - interval 8 hour
)
order by u.lname
The correlated subquery checks whether the given user has any row in checkin during the last 8 hours, and not exists ensures that there is none.
This solution is usually equally or more efficient than a join, especially with the right index in place - here, that would be checkin(usersnumber, dateandtime).
Below query should work:
SELECT u.lname, u.fname, u.usersnumber FROM users u
LEFT JOIN checkin c
on u.usersnumber = c.usersnumber
AND c.dateandtime between INTERVAL '-8' hh +SYSTIMESTAMP and SYSTIMESTAMP
WHERE u.active = 'Yes' and u.shift = 'Day' and c.usersnumber is null
ORDER By lname;
Fixed it unless someone can see something wrong with it
SELECT u.lname, u.fname, u.usersnumber FROM users u
LEFT JOIN checkin c
on u.usersnumber = c.usersnumber
WHERE u.active = 'Yes' and u.shift = 'Day'
AND (c.dateandtime < DATE_ADD(NOW(), INTERVAL -8 HOUR)
OR c.dateandtiime IS NULL)
ORDER By lname;
credit to my co-worker Joe
Sounds like you are looking for an anti-join pattern.
We start with an outer join. Return all of whatever (in your case, users) along with matching whatever (in your case, checkin). In the case of a user row that doesn't have any matching checkin row, the outer join returns the user row without the login row. Actually, what MySQL does (or one way to think about what it does) is it invents a matching bogus checkin row that consists of all NULL values, that serves as a placeholder for a matching row.
If we can get our brain wrapped around that, we are ready for the trick.
We include a condition in the WHERE clause that excludes all rows that had a matching row from checkin. Leaving only rows that have a bogus invented matching row.
We can distinguish those by checking a column from the checkin table that we know will not be NULL for any valid matching row, but will be NULL for rows that didn't have a match.
Your query is so close.
One small problem is the condition on c.dateandtime which is requiring the value of the column to be non-NULL. That is rendering the outer join equivalent to an inner join. (Any bogus invented checkin row is going to have a NULL value for the column, and can't possibly satisfy that condition in the WHERE clause.)
So relocated that condition up into the ON clause of the outer join.
And to turn the query into anti-join, we can add a condition that requires usersnumber from the checkin row to be NULL. (Any valid matching row would have a non-NULL value, we are guaranteed that because the column had to be non-NULL to satisfy the = condition in the ON clause.)
Something like this:
SELECT u.lname
, u.fname
, u.usersnumber
FROM users u
-- anti-join exclude rows that have a matching row in checkin
LEFT
JOIN checkin c
ON c.usersnumber = u.usersnumber
AND c.dateandtime >= NOW() + INTERVAL -8 HOUR
WHERE c.usersnumber IS NULL
--
AND u.active = 'Yes'
AND u.shift = 'Day'
ORDER
BY u.lname
There are other query patterns that will give an equivalent result.
Some it easier to get the brain wrapped around a NOT EXISTS with a correlated subquery. This is sometimes not as performant as the anti-join pattern (suitable availabe indexes are mandatory for performance on large sets)
SELECT u.lname
, u.fname
, u.usersnumber
FROM users u
WHERE NOT EXISTS
( SELECT 1
FROM checkin c
WHERE c.usersnumber = u.usersnumber
AND c.dateandtime >= NOW() + INTERVAL -8 HOUR
)
AND u.active = 'Yes'
AND u.shift = 'Day'
ORDER
BY u.lname
EDIT: changed < to >= to return users that do not have checkin within the past eight hours
The condition c.dateandtime < DATE_ADD(NOW(), INTERVAL -8 HOUR) will get users whose last check-in time was more than 8 hours ago, but for those who have never checked in, c.dateandtime will be NULL, so you have to consider that condition as well.
Also, you need to take into account that a user who has checked in more than 8 hours ago might have also checked in less than 8 hours ago, so you really only want the most recent check-in date, which is why I'm using MAX().
SELECT u.lname, u.fname, u.usersnumber FROM users u
LEFT JOIN checkin c
on u.usersnumber = c.usersnumber
WHERE
u.active = 'Yes' and u.shift = 'Day'
AND
(
MAX(c.dateandtime) < DATE_ADD(NOW(), INTERVAL -8 HOUR) OR c.dateandtime IS NULL
)
ORDER By lname;

SQL Count on JOIN query is taking forever to execute?

I'm trying to run count query on a 2 table join. e_amazing_client table is having million entries/rows and m_user has just 50 rows BUT count query is taking forever!
SELECT COUNT(`e`.`id`) AS `count`
FROM `e_amazing_client` AS `e`
LEFT JOIN `user` AS `u` ON `e`.`cx_hc_user_id` = `u`.`id`
WHERE ((`e`.`date_created` >= '2018-11-11') AND (`e`.`date_created` >= '2018-11-18')) AND (`e`.`id` >= 1)
I don't know what is wrong with this query?
First, I'm guessing that this is sufficient:
SELECT COUNT(*) AS `count`
FROM e_amazing_client e
WHERE e.date_created >= '2018-11-11' AND e.id >= 1;
If user has only 50 rows, I doubt it is creating duplicates. The comparisons on date_created are redundant.
For this query, try creating an index on e_amazing_client(date_created, id).
Maybe you wanted this:
SELECT COUNT(`e`.`id`) AS `count`
FROM `e_amazing_client` AS `e`
LEFT JOIN `user` AS `u` ON `e`.`cx_hc_user_id` = `u`.`id`
WHERE ((`e`.`date_created` >= '2018-11-11') AND (`e`.`date_created` <= '2018-11-18')) AND (`e`.`id` >= 1)
to check between dates?
Also, do you really need
AND (`e`.`id` >= 1)
If id is what an id is usually in a table, is there a case to be <1?
Your query is pulling ALL records on/after 2018-11-11 because your WHERE clause is ID >= 1 You have no clause in there for a specific user. You also had in your original query based on a date of >= 2018-11-18. You MAY have meant you only wanted the count WITHIN the week 11/11 to 11/18 where the sign SHOULD have been >= 11-11 and <= 11-18.
As for the count, you are getting ALL people (assuming no entry has an ID less than 1) and thus a count within that date range. If you want it per user as you indicated you need to group by the cx_hc_user_id (user) column to see who has the most, or make the user part of the WHERE clause to get one person.
SELECT
e.cx_hc_user_id,
count(*) countPerUser
from
e_amazing_client e
WHERE
e.date_created >= '2018-11-11'
AND e.date_created <= '2018-11-18'
group by
e.cx_hc_user_id
You can order by the count descending to get the user with the highest count, but still not positive what you are asking.

Select on aggregate result

I have a query which does a count from another table then adds a column for the result. I then need to alter the original select results based on that but am being told unknown column.
E.g. the following query does a count from another table within the main query, and the result is named shares, I need to filter the main query result set based on whether that column is greater than 0 but I get error unknown column shares
select b.name, event_title, ue.event_vis, event_date,
(select count(*) from list_shares
where user_id = 63 and event_id=ue.user_event_id) as shares,
(DAYOFYEAR(ue.event_date) - DAYOFYEAR(CURDATE())) as days
FROM brains b
join user_events ue on b.user_id=ue.user_id
where b.user_id=63 and ((ue.event_vis='Public') OR (shares>0))
and MOD(DAYOFYEAR(ue.event_date) - DAYOFYEAR(CURDATE()) + 365, 365) <= 30
order by days asc
Is there a way to do this?
I would suggest using a derived table to deliver the aggregate value and join it as you would do with a "phyiscal" table. Example:
select
b.name,
ue.event_title,
ue.event_vis,
ue.event_date,
tmp.shares,
(DAYOFYEAR(ue.event_date) - DAYOFYEAR(CURDATE())) as days
from
brains b join user_events ue on b.user_id = ue.user_id
left join (
select
ls.user_id,
ls.event_id,
count(*) as shares
from
list_shares ls
group by
ls.user_id,
ls.event_id) tmp on b.user_id = tmp.user_id and ue.user_event_id = tmp.event_id
where
b.user_id = 63
and
((ue.event_vis = 'Public') OR (tmp.shares > 0))
and
MOD(DAYOFYEAR(ue.event_date) - DAYOFYEAR(CURDATE()) + 365, 365) <= 30
order by
days asc
Please note the "left join". Because your using the OR operator in your where clause it seems to me like you want to get also rows without a share.
Of course you could also use the same subselect in your where clause but that's duplicate code and harder to maintain.
You cannot use a computed column to filter in the same query. Try something like
SELECT x.*
FROM (
/*Your Query*/
) as x
WHERE x.shares > 0
Or you could do something like
select b.name, event_title, ue.event_vis, event_date,
shares.SharesCount as shares,
(DAYOFYEAR(ue.event_date) - DAYOFYEAR(CURDATE())) as days
FROM brains b
join user_events ue on b.user_id=ue.user_id, event_id
JOIN (select count(*) as [sharesCount], from list_shares
where user_id = 63) as shares ON shares.event_id = ue.user_event_id
where b.user_id=63 and ((ue.event_vis='Public') OR (shares>0))
and MOD(DAYOFYEAR(ue.event_date) - DAYOFYEAR(CURDATE()) + 365, 365) <= 30
AND shares.sharesCount > 0
order by days asc

Mysql count() fetches wrong result

I am trying to find the number of records for current week.
My current query is:
SELECT Week(Str_to_date(products_options_values, '%m-%d-%Y'), 1) AS order_week,
Year(Str_to_date(products_options_values, '%m-%d-%Y')) AS order_year,
order_active,
Count(op.sub_order_id) AS deliveries
FROM orders_products_attributes opa
LEFT JOIN orders_products op
ON ( opa.orders_products_id = op.orders_products_id )
GROUP BY order_week,
order_year
HAVING order_week = '31'
AND order_year >= '2013'
AND order_active = 0
ORDER BY order_week
It fetches deliveries AS 2 where as there are actually 4 records, and if I run the same query after removing COUNT and GROUP BY, it correctly shows all 4 rows. The same problem happens on other weeks too, for example week 34 has 3 records, but the above query fetches it as 4 instead. Moreover, another weird thing is, in the GROUP BY clause, if I remove either one of order_week or order_year the query returns an empty result set.
Any idea what I am doing wrong?
Try to move all HAVING conditions into WHERE. Also Count(id) - counts UNIQUE values of ID not all. If you need all records count just use COUNT(*)
SELECT Week(Str_to_date(products_options_values, '%m-%d-%Y'), 1) AS order_week,
Year(Str_to_date(products_options_values, '%m-%d-%Y')) AS order_year,
order_active,
Count(op.sub_order_id) AS deliveries
FROM orders_products_attributes opa
LEFT JOIN orders_products op
ON ( opa.orders_products_id = op.orders_products_id )
WHERE order_week = '31'
AND order_year >= '2013'
AND order_active = 0
GROUP BY order_week,
order_year
ORDER BY order_week