mysql join question, joining newest record in a one to many relationship - sql

I have a simple (hopefully) SQL question for you, any help would be much, much appreciated :).
I have two tables which I would like to join.
One Is Users, lets say it's called users
One is a kind of history of that user, lets say its called users_history.
The relationship of these two is a one users to many users_history relationship.
What I'd like to do is a query which joins the tables and joins the newest record in users_history onto each user.
Lets say the tables are like this, I'm simplifying for conciseness.
users
id
name
users_history
id
user_id
date
The date is formatted YYYYMMDD.
The end result is I'd like to be able to pull out all of the users who don't have a users_history record for today, for example today is 20101021.
Any help would be very gratefully received! :)

Try
SELECT MAX(users_history.date), users.name FROM users
LEFT JOIN users_history ON users.id = users_history.user_id
GROUP BY users_history.user_id
HAVING MAX(users_history.date) < CURDATE()
If you dont want users who doesnt have eny users_history records in the resultset, change the "LEFT JOIN" to a "JOIN"

If all you really want is finding the users without a user_history entry for today, you can use a subquery like so:
SELECT * FROM users WHERE NOT EXISTS (SELECT id FROM users_history WHERE user_id = users.id AND `date` = DATE(NOW()));
IMHO, this is more readable than a join and some filtering in your host language.
edit: Judging from your date format description, you probably use a varcharcolumn to store the date. In that case, replace DATE(NOW()) with the appropriate string representation for "today" - though I'd recommend changing the column type to a date/time type.

pull out all of the users who don't have a users_history record for today, for example today is 20101021.
select
u.*
from
users u
left outer join users_history uh on
u.id = uh.user_id and uh.history_date = curdate()
where
uh.user_id is null;

Related

'Exploratory' SQL queries that uses one criteria to find more criteria

Sorry about this dreadful title. Imagine tables like this: http://sqlfiddle.com/#!9/48d921/1
Here, we run a query for all users with the name "Bob", but we are also interested in all users in the same postcode as "Bob" and also all users of the same "type" as Bob.
You can see I joined the same tables twice to achieve this. The trouble with it is it doesn't scale; the more criteria I want to "explore" the more times I have to join the same tables, making the select statement more cumbersome.
So:
What's the best way to do this?
Does this type of query have a name?
The answer updated
SELECT
FROM user
where u.name="Bob"
OR (u.postcode in (SELECT a.postcode
FROM USER u
JOIN ADDRESS a on a.user=u.id
WHERE u.name="Bob")
)
OR (u.type in (SELECT ut.type
FROM USER u
JOIN USER_TYPE ut1 on u.id=ut1.user
WHERE u.name="Bob")
)
So you scan users table just once for each record checking the linking criteria

Select a user by their username and then select data from another table using their UID

Sorry if that title is a bit convoluted... I'm spoiled by an ORM usually and my raw SQL skills are really poor, apparently.
I'm writing an application that links to a vBulletin forum. Users authenticate with their forum username, and the query for that is simple (selecting by username from the users table). The next half of it is more complex. There's also a subscriptions table that has a timestamp in it, but the primary key for these is a user id, not a username.
This is what I've worked out so far:
SELECT
forum.user.userid,
forum.user.usergroupid,
forum.user.password,
forum.user.salt,
forum.user.pmunread,
forum.subscriptionlog.expirydate
FROM
forum.user
JOIN forum.subscriptionlog
WHERE
forum.user.username LIKE 'SomeUSER'
Unfortunately this returns the entirety of the subscriptionlog table, which makes sense because there's no username field in it. Is it possible to grab the subscriptionlog row using the userid I get from forum.user.userid, or does this need to be split into two queries?
Thanks!
The issue is that you are blindly joining the two tables. You need to specify what column they are related by.
I think you want something like:
SELECT * FROM user u
INNER JOIN subscriptionlog sl ON u.id = sl.userid
WHERE u.username LIKE 'SomeUSER'
select * from user u JOIN subscriptions s ON u.id = s.id where u.username = 'someuser'
The bit in bold is what you want to add, it combines the 2 tables into one that you return results from.
try this
SELECT
forum.user.userid,
forum.user.usergroupid,
forum.user.password,
forum.user.salt,
forum.user.pmunread,
forum.subscriptionlog.expirydate
FROM
forum.user
INNER JOIN forum.subscriptionlog
ON forum.subscriptionlog.userid = forum.user.userid
WHERE
forum.user.username LIKE 'SomeUSER'

Perform SQL query and then join

Lets say I have two tables:
ticket with columns [id,date, userid] userid is a foreign key that references user.id
user with columns [id,name]
Owing to really large tables I would like to first filter the tickets table by date
SELECT id FROM ticket WHERE date >= 'some date'
then I would like to do a left join with the user table. Is there a way to do it. I tried the follwoing but it doesnt work.
select ticket.id, user.name from ticket where ticket.date >= '2015-05-18' left join user on ticket.userid=user.id;
Apologies if its a stupid question. I have searched on google but most answers involve subqueries after the join instead of what I want which is to perfrom the query first and then do the join for the items returned
To make things a little more clear, the problem I am facing is that I have large tables and join takes time. I am joining 3 tables and the query takes almost 3 seconds. Whats the best way to reduce time. Instead of joining and then doing the where clause, I figured I should first select a small subset and then join.
Simply put everything in the right order:
select - from - where - group by - having - order by
select ticket.id, user.name
from ticket left join user on ticket.user_id=user.id
where ticket.date >= '2015-05-18'
Or put it in a Derived Table:
select ticket.id, user.name
from
(
select * from ticket
where ticket.date >= '2015-05-18'
) as ticket
left join user on ticket.user_id=user.id

Finding records based on no returned records from a related table

Dealing with two tables in Postgres: Members and Memberships. Memberships have an end_date column that can be NULL if the membership is still running. As soon as it has ended, or the end date is known (can be the future) the field gets populated with the date.
Now I would like to find all members that have no active membership at the moment (or: only memberships with end_dates set and in the past or 0 memberships). Members can have more than 1 membership obviously.
I am doing this in a Rails (3.2) project and would like to be able to use ActiveRecord if possible, but if necessary straight SQL will do (although I know ActiveRecord better).
I have tried to find all sorts of things with subqueries, but I cannot find a way to select the rows where a the count of specific rows in a related table is 0.
I have been googling for quite a while now, but have run out of ideas about how to search. Most answers deal with finding records that do match criteria and I feel I wan't those that do not match. Maybe it is a newbie question or I am targeting it wrong. In that case: please enlighten me, I am kinda just beginning.
Based on the partial answer by Jakub I have tried a lot more. At the moment I have the following query:
SELECT
m.id, m.first_name, m.last_name, COUNT (ms.id) AS n_memberships
FROM
members AS m
LEFT JOIN memberships AS ms
ON m.id = ms.member_id
GROUP BY m.id, ms.end_date
HAVING (max(ms.end_date) < now() AND NOT COUNT(ms.end_date = NULL) > 0) OR COUNT(ms.id) = 0;
This returns all members that have no membership at all (no past and no current). Based on the last part of the HAVING clause (the OR ... part).
I do however expect members with expired memberships and no current memberships to be returned, too, because max(ms.end_date) will return a value less than now and there will be no memberships without a given end date. This does not happen though, any ideas?
What you're looking for is an OUTER JOIN. The SQL code to find members with no memberships is:
SELECT m.id
FROM members AS m
LEFT JOIN memberships AS s
ON m.id=s.member_id
WHERE s.id IS NULL;
The full query:
SELECT m.id
FROM members AS m
LEFT JOIN memberships AS s
ON m.id=s.member_id
GROUP BY m.id
HAVING max(s.end_date)<now() OR max(s.end_date) IS NULL

Extract Both Counts AND Earliest Instance from my Dataset

Using Microsoft Sql 2000
I have a requirement to be able to email a monthly report that details a number of events.
(I have got the email bit sussed).
Amongst the data I need to email is a report on the number of certain courses people have attended. (so far so easy, couple of inner joins and a Count() and Im there.)
To add to that, some of the internal courses that we run have an expiry date which prompts a referesher course. I have been able to crudely get the data I need by using the sql code for part one and sticking the result set into a temp table, then by iterating over each row in that table, getting the user Id, querying the users course attendences, sorting it on date so that the earliest is at the top, and just taking the TOP 1 record.
This seems so inefficient, so is there any way I can ammend my current query so that I can also get the date of just the earliest course that the user attended?
i.e.
SELECT uName, COUNT(uId), [ not sure what would go in here] FROM UserDetails
INNER JOIN PassDates
ON PassDates.fkUser = uId)
GROUP BY uName, uId
where, for examples sake
UserDetails
uId
uName
and
PassDates
fkUser
CourseId
PassDate
Hope Ive explained this well enough for someone to help me.
To put an answer to the question..
SELECT uName, COUNT(uId), MIN(PassDate)
FROM UserDetails
INNER JOIN PassDates ON PassDates.fkUser = uId
GROUP BY uName, uId
You can turn it into a left join if you have users without any courses (yet)