Merging 2 sql statements with where clause - mysql

I've 2 tables, first one is users(13068), the other one invitations(211343)
fbuid on users is same with inviter on invitations.
So I'm trying export this 2 tables as an excel which should looks like this;
u.name, u.adress, u.fbuid ...., COUNT(i.id)
So for I've tried;
SELECT u.*,(SELECT COUNT(i.id) FROM invitations i WHERE i.isaccepted = 1 and i.inviter = u.fbuid) as chance FROM users WHERE u.datecreated BETWEEN '2013-01-01' AND '2014-01-01' LIMIT 0,50
and
SELECT *,COUNT(i.id) as chance FROM users u LEFT JOIN invitations i ON u.fbuid = i.inviter WHERE u.datecreated BETWEEN '$startdate' AND '$enddate' and i.isaccepted=1 GROUP BY fbuid
Problem is left join gives only users with invitations, but only about 2000 users invited, I need to list all of them.
First one is with limit 50 tooks 36 seconds. I can't imagine how much took all records. Other than join what else I can do? Or how should be the correct way.

This is the query with the left join:
SELECT *, COUNT(i.id) as chance
FROM users u LEFT JOIN
invitations i
ON u.fbuid = i.inviter
WHERE u.datecreated BETWEEN '$startdate' AND '$enddate' and i.isaccepted=1
GROUP BY fbuid;
The problem is that you are filtering on the i table in the where clause. Because of the left join, this could have a value of NULL. Move that condition to the on clause:
SELECT u.*, COUNT(i.id) as chance
FROM users u LEFT JOIN
invitations i
ON u.fbuid = i.inviter and i.isaccepted = 1
WHERE u.datecreated BETWEEN '$startdate' AND '$enddate'
GROUP BY fbuid;

Related

Subquery left join refer to parent ID

I am trying to make a query to fetch the newest car for each user:
select * from users
left join
(select cars.* from cars
where cars.userid=users.userid
order by cars.year desc limit 1) as cars
on cars.userid=users.userid
It looks like it says Unknown column "users.userid" in where clause
I tried to remove cars.userid=users.userid part, but then it only fetches 1 newest car, and sticks it on to each user.
Is there any way to accomplish what I'm after? thanks!!
For this purpose, I usually use row_number():
select *
from users u left join
(select c.* , row_number() over (partition by c.userid order by c.year desc) as seqnum
from cars c
) c
on c.userid = u.userid and c.seqnum = 1;
One option is to filter the left join with a subquery:
select * -- better enumerate the columns here
from users u
left join cars c
on c.userid = u.userid
and c.year = (select max(c1.year) from cars c1 where c1.userid = c.userid)
For performance, consider an index on car(userid, year).
Note that this might return multiple cars per user if you have duplicate (userid, year) in cars. It would be better to have a real date rather than just the year.
Maybe there are better and more efficient way to query this. Here is my solution;
select users.userid, cars.*
from users
left join cars on cars.userid = users.userid
join (SELECT userid, MAX(year) AS maxDate
FROM cars
GROUP BY userid) as sub on cars.year = sub.maxDate;

Get data even if one row is NULL

I have this query to get users from the users table and also get the latest time (a timestamp) from the logs table where the entry is "login_ok". This is intended to show a list of users and the last time them logged in.
SELECT u.`id`, u.`email`, u.`firstname`, u.`lastname`, u.`type`, u.`creation_date`, MAX(l.`time`) as `last_login`
FROM `users` AS u
JOIN `logs` AS l ON u.id = l.user_id
WHERE l.`action` = 'login_ok'
AND `visible` = 1
GROUP BY u.`id`
ORDER BY u.`id` ASC
My issue here is: if the user has never logged in, the "login_ok" entry doesn't exists for that user, so the query cannot get that user data.
Is there any way to get all user data even if the l.time on logs doesn't exist? I tried with JOINname_admin_users_logAS l ON (l.timeIS NOT NULL AND u.id = l.user_id) but still not showing that new user without login log.
Use a LEFT JOIN instead of a regular JOIN (which actually means INNER JOIN), and move the filter on action = 'login_ok' to that LEFT JOIN clause.
NB : from your query we cannot tell from which table the visible column comes from, so I assumed it is related to users...
SELECT
u.id,
u.email,
u.firstname,
u.lastname,
u.type,
u.creation_date,
MAX(l.time) as last_login
FROM users AS u
LEFT JOIN logs AS l
ON u.id = l.user_id and l.action = 'login_ok'
WHERE u.visible = 1
GROUP BY u.id
ORDER BY u.id ASC
Use a left join:
SELECT u.`id`, u.`email`, u.`firstname`, u.`lastname`, u.`type`, u.`creation_date`, MAX(l.`time`) as `last_login`
FROM `users` u LEFT JOIN
`logs` l
ON u.id = l.user_id AND l.`action` = 'login_ok'
WHERE u.`visible` = 1
GROUP BY u.`id`
ORDER BY u.`id` ASC;
This assumes that visible is in users. If it is in logs, then that condition should also be in the ON clause.
You can simply ignore the login_ok and search for all values of l.action.
But if you want only values with login_ok and null then you can do:
WHERE l.`action` = 'login_ok' OR l.`action` is null
You should use LEFT/RIGHT joins instead of JOIN(inner)
For example:
SELECT u.id, u.name, l.time
FROM users u LEFT JOIN logs l
ON u.id=l.user_id
In this case you'll get ALL the records from 'users'(the table on the left in the select: users - left, logs - right) and all records from the right('logs') table for which condition u.id=l.user_id is true.
Finally you'll get something like this:
u.id u.name l.time
1 John 10.00am
2 Mary 2.15pm
3 Mark null

Restricting query data in mysql

I am getting back into mysql after a couple years and have run into a problem. I have a query that works, but I am lost on how to optimize it better.
Here is the query:
select
u.id as 'User',
count(distinct tr.id) as Trips,
count(distinct ti.id) as 'Trip Items'
from
users u
inner join
user_emails ue on u.id = ue.user_id
inner join
trips tr on tr.user_id = u.id
inner join
trip_items ti on ti.trip_id = tr.id
where
ue.verified = true and ue.is_primary = true
and
tr.created_at between '2017-02-01 00:00:00' and '2017-02-01 00:59:59'
group by 1
having Trips < 30
I essentially need to get a count of all trips and trip items.. but only for those users who have 30 or less trips in the given date range. Right now I am accomplishing that by grouping the results by User, and then performing a 'having'. I'm looking at millions of results on a non-indexed field (created_at). ideally i'd like to just get 1 row back that has total trips, and total trip items. But still applying the "users w/ less than 30 trips" during the query. Is this possible? :)
Just a quick edit, i've tried looking around at other solutions but I am a bit lost on what I should be looking for. I'm not looking for a solution, perhaps just a "go check this out and try that".
count(distinct) can be expensive. Try aggregating before doing the join. I think the follow works (this assumes that items are not shared among different trips):
select u.id as `User`, tr.Trips, tr.items
from users u inner join
user_emails ue
on u.id = ue.user_id inner join
(select tr.user_id, count(*) as Trips, sum(items) as items
from trips tr join
(select ti.trip_id, count(*) as items
from trip_items ti
group by ti.trip_id
) ti
on ti.trip_id = tr.id
where tr.created_at >= '2017-02-01' and tr.created_at < '2017-02-01 01:00:00'
group by tr.user_id
having trips < 30
) tr
on tr.user_id = u.id inner join
where ue.verified = true and ue.is_primary = true
group by 1

SQL- How to combine two tables with two table counts with different conditions

Query in text: "Display all active users and the completed orders entered by them + the completed orders they are assigned to for the specified date range".
Here is the query i managed to create with only one count
SELECT u.firstname, u.lastname, COUNT(l.id) AS totalCompleted
FROM users u
LEFT JOIN orders l
ON l.idDispatcher = u.id
WHERE u.disabled = '0'
AND l.smallStatus='1'
AND l.dateAdded >= :from
AND l.dateAdded <= :to
GROUP BY u.firstname;
This gives me all the orders where the user is assigned to an order:
LEFT JOIN orders l
ON l.idDispatcher = u.id
I need to combine this query with another one where the COUNT(l.id) is based on:
LEFT JOIN orders l
ON l.addedById= u.id
When I try this:
LEFT JOIN orders l
ON l.idDispatcher = u.id AND l.addedById= u.id
The COUNT(l.id) combines the result for assigned orders and orders added by the user, when i need it to be with two different numbers. I also tried putting a condition inside the COUNT, with no success
Not sure I understand exactly but if you said you got it working with two separate queries, and you need two counts, then just union the results together?
But if you need one count of ALL matches based on two separate conditions, use an "OR" in your join instead of "AND":
SELECT u.firstname, u.lastname, COUNT(l.id) AS totalCompleted
FROM users u
LEFT JOIN orders l
ON l.idDispatcher = u.id
or l.addedById= u.id
WHERE u.disabled = '0'
AND l.smallStatus='1'
AND l.dateAdded >= :from
AND l.dateAdded <= :to
GROUP BY u.firstname, u.lastname;

MySQL: Find users with no submission

I'm struggling a little bit with a query and hope you can help.
I have two tables. On with all the users and one with information from submitted forms.
Both contain the user ID.
What I would need to find out is which user from the users table does not appear on the report table.
This is what I have so far:
SELECT u.ID, u.display_name, u.user_email, r.user_id
FROM users AS u
LEFT JOIN report AS r ON u.ID = r.user_id
WHERE NOT EXISTS(
SELECT *
FROM report AS rr
WHERE u.ID = rr.user_id
)
This seems to be fine for the users who absolutely have never submitted the form.
But the reports table also contains a date column and I was wondering how I can get this grouped by day.
In the front end then I will hopefully have a table which shows:
date: user:
2015-01-01 user a
2015-01-01 user f
2015-01-02 user g
2015-01-02 user a
2015-01-03 user z
2015-01-03 user x
Where the users are those who have not submitted the form that day.
Hope you can help. Thank in advance!
If you want to get a list of users that doesn't have any rows in the report table then you can generate a set that is the Cartesian product of the users and the dates that are present in the report table, and then do a left join with that set and check for null.
The Cartesian set formed by the cross join will contain all possible combinations of dates and users; that is would the report table would contain is all users had added reports on all available dates.
select r.date, u.user_id
from report r
cross join users u
left join (select r.date, r.user_id from users as u join report as r on u.id = r.user_id)
a on a.date = r.date and a.user_id = u.user_id
where a.date is null
Sample SQL Fiddle
With most other databases this could have been done with a set difference operator (minus or except) instead of a left join.
I'm making assumptions about column names in your report table for this answer:
SELECT x.report_date, u.user_id, u.display_name
FROM users u
JOIN (
SELECT DISTINCT report_date
FROM reports
) x
LEFT JOIN reports r
ON r.user_id = u.user_id
AND r.report_date = x.report_date
WHERE r.report_date IS NULL
ORDER BY x.report_date, u.user_id
Check out this fiddle: http://sqlfiddle.com/#!9/407ac/5
Left outer join with where clause...
Here is a good link ...
http://blog.codinghorror.com/a-visual-explanation-of-sql-joins/
SELECT * FROM `users`
LEFT OUTER JOIN `report`
ON `users`.`ID` = `report`.`user_id`
WHERE `report`.`user_id` IS null
ORDER BY `report`.`Date`
Surely you could just pass in the date you wanted to check?
so something like this (using #reportDate as the parameter):
SELECT * FROM users
LEFT OUTER JOIN report
ON users.ID = report.user_id
WHERE report.user_id IS NULL
AND report.Date = #reportDate
You can get the pairs of users/dates without reports. Generate all possible rows using a cross join and then filter out the ones that exist:
select u.*, r.date
from users u cross join
(select distinct date from reports r) d left join
reports r
on u.id = r.user_id and d.date = r.date
where r.userid is null;