Check for new messages with SQL [facebook like chat] - sql

I'm currently developing a facebook like chat for my website. Right now I am asking myself, what is the best way (the best sql query) to check for new messages?
My tables look like this:
messages
messageID | userID | roomID | message | time
-----------------------------------------------------------
4 | 1 | 1 | test | 1369062603
9 | 2 | 1 | great | 1369062609
rooms
roomID | host | createTime
-----------------------------
1 | 1 | 1369062600
room_to_user
roomID | userID
----------------
1 | 1
1 | 2
I plan to add group chatting that's why I don't want to add a sepereate read column to my message table.
My next attempt would be a sql query filtered by the time column.
What would be your attempt? Thanks!

Will this work?
Select m.*
From messages m join rooms r on m.roomID = r.roomID
join room_to_user ru on r.roomID = ru.roomID
where ru.userID = #userID and m.time > #timeSinceLastRead
You didn't specify which SQL you're using (Sql Server? Oracle? MySql?) and you'll probably have to play with the format for m.time.

Related

SQL Join to the latest record in MS ACCESS

I want to join tables in MS Access in such a way that it fetches only the latest record from one of the tables. I've looked at the other solutions available on the site, but discovered that they only work for other versions of SQL. Here is a simplified version of my data:
PatientInfo Table:
+-----+------+
| ID | Name |
+-----+------+
| 1 | John |
| 2 | Tom |
| 3 | Anna |
+-----+------+
Appointments Table
+----+-----------+
| ID | Date |
+----+-----------+
| 1 | 5/5/2001 |
| 1 | 10/5/2012 |
| 1 | 4/20/2018 |
| 2 | 4/5/1999 |
| 2 | 8/8/2010 |
| 2 | 4/9/1982 |
| 3 | 7/3/1997 |
| 3 | 6/4/2015 |
| 3 | 3/4/2017 |
+----+-----------+
And here is a simplified version of the results that I need after the join:
+----+------+------------+
| ID | Name | Date |
+----+------+------------+
| 1 | John | 4/20/2018 |
| 2 | Tom | 8/8/2010 |
| 3 | Anna | 3/4/2017 |
+----+------+------------+
Thanks in advance for reading and for your help.
You can use aggregation and JOIN:
select pi.id, pi.name, max(a.date)
from appointments as a inner join
patientinfo as pi
on a.id = pi.id
group by pi.id, pi.name;
something like this:
select P.ID, P.name, max(A.Date) as Dt
from PatientInfo P inner join Appointments A
on P.ID=A.ID
group by P.ID, P.name
Both Bing and Gordon's answers work if your summary table only needs one field (the Max(Date)) but gets more tricky if you also want to report other fields from the joined table, since you would need to include them either as an aggregated field or group by them as well.
Eg if you want your summary to also include the assessment they were given at their last appointment, GROUP BY is not the way to go.
A more versatile structure may be something like
SELECT Patient.ID, Patient.Name, Appointment.Date, Appointment.Assessment
FROM Patient INNER JOIN Appointment ON Patient.ID=Appointment.ID
WHERE Appointment.Date = (SELECT Max(Appointment.Date) FROM Appointment WHERE Appointment.ID = Patient.ID)
;
As an aside, you may want to think whether you should use a field named 'ID' to refer to the ID of another table (in this case, the Apppintment.ID field refers to the Patient.ID). You may make your db more readable if you leave the 'ID' field as an identifier specific to that table and refer to that field in other tables as OtherTableID or similar, ie PatientID in this case. Or go all the way and include the name of the actual table in its own ID field.
Edited after comment:
Not quite sure why it would crash. I just ran an equivalent query on 2 tables I have which are about 10,000 records each and it was pretty instanteneous. Are your ID fields (i) unique numbers and (ii) indexed?
Another structure which should do the same thing (adapted for your field names and assuming that there is an ID field in Appointments which is unique) would be something like:
SELECT PatientInfo.UID, PatientInfo.Name, Appointments.StartDateTime, Appointments.Assessment
FROM PatientInfo INNER JOIN Appointments ON PatientInfo_UID = Appointments.PatientFID
WHERE Appointments.ID = (SELECT TOP 1 ID FROM Appointments WHERE Appointments.PatientFID = PatientInfo_UID ORDER BY StartDateTime DESC)
;
But that is starting to look a bit contrived. On my data they both produce the same result (as they should!) and are both almost instantaneous.
Always difficult to troubleshoot Access when it crashes - I guess you see no error codes or similar? Is this against a native .accdb database or another server?

Eloquent group messages

I´m playing with Laravel´s eloquent and a MYSQL database
I need to display a list of conversations
this is my table structure
messages:
- id
- from_id
- to_id
- message
- read
|----|-----------|---------|-------|------|
| id | message | from_id | to_id | read |
|----|---------------------|--------------|
| 1 | hello | 1 | 2 | 1 |
| 2 | hey | 2 | 1 | 0 |
| 3 | there | 2 | 1 | 0 |
| 4 | hi! | 3 | 1 | 1 |
|----|-----------|---------|-------|------|
users:
- id
- name
|-----------|
| id | name |
|-----------|
| 1 | john |
| 2 | mary |
| 3 | jack |
|-----------|
expected:
if I´m user 1 (it should show me the last message and the count of unread
|----|------|---------|--------|
| id | name | message | unread |
|----|------|---------|--------|
| 3 | mary | there | 2 |
| 4 | jack | hi! | 0 |
|----|------|-------- |--------|
I´m not sure if this is the right table structure for what I want to do but the SQL should be something like
SELECT users.name, messages.message
FROM users, messages
WHERE messages.from_id = 1 OR messages.to_id = 1
GROUP BY ??
ORDER BY messages.id DESC
I´m not sure how to GROUP because if I group by messages.from_id = 1 it will only show messages I sent and not messages I received. please help me thing :)
also how to handle the read status? for sure that read attribute is something like the to_id will set to 1. how to check that in the query as well?
THere's a couple of things I see here.
1. while aggregating your data to count the number of unread emails, you are still showing at least one message which indicates message text. In the expected response:
|----|------|---------|--------|
| id | name | message | unread |
|----|------|---------|--------|
| 3 | mary | there | 2 |
| 4 | jack | hi! | 0 |
|----|------|-------- |--------|
You have two unread messages for mary, however the message column says there, which is only the message of one the two unread emails.
I'm just wondering if that's on purpose.
However, to get you where you need to go,
you have the read column which appears binary in nature: 1 for messages read, 0 for messages not read.
That being said, you can utilize that logic to count the number of unread messages by using the algorithm: count(read) - sum(read).
the second thing is your joins. You have to join the tables together in order to establish names to user_ids. in your case, you are not using a join at all but filtering by field. if you want a list of from users, filtered by the to users, then you need to join the from_id to the user_id so you can get the name.
There you can group by name and at least get id, name, unread. as forth in this example:
SELECT users.name, count(read) - sum(read) as unread
FROM users, messages
WHERE users.id = messages.from_id and messages.to_id = 1 --to me
GROUP BY users.name
ORDER BY messages.from_id ASC
in laravel ORM, try this (I know I"m close, but I'm mobile and haven't been able to test it, so play around):
$users = DB::table('messages')
->select(DB::raw('users.name, count(read) - sum(read) as unread'))
->join('users', 'users.id', '=', 'messages.from_id')
->where('to_id', '=', 1)
->orWhere('from_id', '=', '1')
->groupBy('users.name')
->orderBy('messages.from_id','ASC')
->get();
Try this.
UPDATE 1:
to purposely get the "latest" message with the list:
select u.name, fm.message, count(m.unread) - sum(m.unread) as unread,
case when m.from_id = 1 then 'sent'
when m.to_id = 1 then 'received'
else null
end status
from messages m
join users u on u.id = m.from_id
left join (
Select max(id) as id, from_id, message from messages group by from_id
) fm on m.from_id = fm.from_id
where m.from_id = 1 or m.to_id = 1
group by u.name;
Eloquent:
DB::table('messages')
->select('u.name', 'fm.message', 'count(m.read) - sum(m.read) as unread', 'case when m.from_id = 1 then "sent" when m.to_id = 1 then "received" else null end status')
->join(users u', 'u.id', '=', 'u.from_id')
->leftjoin(DB::raw('Select max(id) as id, from_id, message from messages group by from_id', 'm.from_id', 'fm.from_id'))
->groupBy('users.name')
->orderBy('messages.from_id','ASC')
->get();

Limiting subqueries in SQL

I have a situation which is a little hard to describe. I'll try to explain with an example and the result which I want.
I have three tables like so
Employee
| id | Name |
|----+-------|
| 1 | Alice |
| 2 | Bob |
| 3 | Jane |
| 4 | Jack |
Task
| id | employee_id | description |
|----+-------------+---------------------|
| 1 | 1 | Fix bug |
| 2 | 1 | Implement feature |
| 3 | 1 | Deploy master |
| 4 | 2 | Integrate feature |
| 5 | 2 | Fix cosmetic issues |
Status
| id | task_id | time | details | Terminal |
|----+---------+-------+-----------+----------|
| 1 | 1 | 12:00 | Assigned | false |
| 2 | 1 | 12:30 | Started | false |
| 3 | 1 | 13:00 | Completed | true |
| 4 | 2 | 12:10 | Assigned | false |
| 5 | 2 | 14:00 | Started | false |
| 6 | 3 | 12:15 | Assigned | false |
| 7 | 4 | 12:20 | Assigned | false |
| 8 | 5 | 12:25 | Assigned | false |
| 9 | 4 | 12:30 | Started | false |
(I have also put these into a sqlfiddle page at http://sqlfiddle.com/#!9/728c85/1)
The basic idea is that I have some employees and tasks. The tasks can be assigned to employees and as they work on them, they keep adding "status" rows.
Each status entry has a field "terminal" which can either be true or false.
If the last status entry for a task has terminal set to true, then that task is over and there's nothing more to be done on it.
If all tasks assigned to an employee are over, then the employee is considered free.
I need to get a list of free employees. This would basically mean, given an employee, a list of all his or her tasks with statuses. So, something like this for Alice
| Task | Completed |
|-------------------+-----------|
| Fix bug | true |
| Implement feature | false |
| Deploy master | false |
From which I know that she's not free right now since there are 'false' entries in completed.
How would I do this? If my tables are not constructed properly for this kind of query, I'd very much like some advice on that too.
(I titled the question like this since I want to order the statuses of each task per user and them limit them to the last row).
Update
It was suggested to me that the status field should really go inside the tasks table and the Status table should simple be a log table.
I would go for the idea to have the status in the tasks table. (Please see my comment on your request on this.) However, here are two queries to select free employees:
If tasks cannot be re-opened, it is simple: Get all incompleted tasks by checking whether a record with terminal = true exists. Free employees are all that have no incomplete task.
select *
from employee
where id not in
(
select employee_id
from task
where id not in (select task_id from status where terminal = true)
);
If tasks can be re-opened, however, then you do the same but must find the last status. This can be done with Postgre's DISTINCT ON for instance.
select *
from employee
where id not in
(
select employee_id
from task
where not
(
select distinct on (task_id) terminal
from status
where task_id = task.id
order by task_id, id desc
)
);
(I am using the ID to find the last entry per task, as the time without a date seems inappropriate. You could only use the time column instead, if a task will always run within one day only.)
SQL fiddles:
http://sqlfiddle.com/#!15/f0ea8/2
http://sqlfiddle.com/#!15/f0ea8/1
You have to group all the statuses togheter and you can then use MAX() to find if one of them is true, like this:
SELECT t.description, MAX(s.terminal)
FROM Employee e
INNER JOIN task t ON t.employee_id = e.id
INNER JOIN status s ON s.task_id = t.id
GROUP BY t.id;
When you want this just for one user add something like this WHERE e.id = 1.
Hope this helps
select T.employee_id, T.description, S.Terminal
from Employee E
INNER JOIN Task T ON E.id=T.employee_id
INNER JOIN (Select task_id, max(id) as status_id FROM Status GROUP BY task_id) as ST on T.id=ST.task_id
INNER JOIN Status S on S.id=ST.status_id
I hope this will help you...??
select E.Name,T.id as[Task Id],T.description,S.Terminal from Employee E
inner join Task T on E.id=T.employee_id
inner join Status S on S.task_id=T.id
where e.id not in (select employee_id from Task where id in (select task_id from Status where Terminal='true' and details='Completed') )

Randomly Populating Foreign Key In Sample Data Set

I'm generating test data for a new database, and I'm having trouble populating one of the foreign key fields. I need to create a relatively large number (1000) of entries in a table (SurveyResponses) that has a foreign key to a table with only 6 entries (Surveys)
The database already has a Schools table that has a few thousand records. For arguments sake lets say it looks like this
Schools
+----+-------------+
| Id | School Name |
+----+-------------+
| 1 | PS 1 |
| 2 | PS 2 |
| 3 | PS 3 |
| 4 | PS 4 |
| 5 | PS 5 |
+----+-------------+
I'm creating a new Survey table. It will only have about 3 rows.
Survey
+----+-------------+
| Id | Col2 |
+----+-------------+
| 1 | 2014 Survey |
| 2 | 2015 Survey |
| 3 | 2016 Survey |
+----+-------------+
SurveyResponses simply ties a school to a survey.
Survey Responses
+----+----------+----------+
| Id | SchoolId | SurveyId |
+----+----------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 1 |
| 4 | 4 | 3 |
| 5 | 5 | 2 |
+----+----------+----------+
Populating the SurveyId field is what's giving me the most trouble. I can randomly select 1000 Schools, but I haven't figured out a way to generate 1000 random SurveyIds. I've been trying to avoid a while loop, but maybe that's the only option?
I've been using Red Gate SQL Data Generator to generate some of my test data, but in this case I'd really like to understand how this can be done with raw SQL.
Here is one way, using a correlated subquery to get a random survey associated with each school:
select s.schoolid,
(select top 1 surveyid
from surveys
order by newid()
) as surveyid
from schools s;
Note: This doesn't seem to work. Here is a SQL Fiddle showing the non-workingness. I am quite surprised it doesn't work, because newid() should be a
EDIT:
If you know the survey ids have no gaps and start with 1, you can do:
select 1 + abs(checksum(newid()) % 3) as surveyid
I did check that this does work.
EDIT II:
This appears to be overly aggressive optimization (in my opinion). Correlating the query appears to fix the problem. So, something like this should work:
select s.schoolid,
(select top 1 surveyid
from surveys s2
where s2.surveyid = s.schoolid or s2.surveyid <> s.schoolid -- nonsensical condition to prevent over optimization
order by newid()
) as surveyid
from schools s;
Here is a SQL Fiddle demonstrating this.

What is a better way to store status updates in a database?

I'm developing a web application that will enable users to post short status updates similar to Twitter. The only way I can think of storing these posts is to have a large "status_updates" table that stores EVERY user's status updates:
--------------------------------------
| table: status_updates |
-------------------------------------|
| id | user_who_posted | update_text |
--------------------------------------
This method requires something like this SQL query to get each user's updates:
SELECT * FROM status_updates where user_who_posted="username"
and I think that wouldn't be very inefficient. Is there a better way of doing this?
Build a user table, and have the user_id be an integer foreign key to that user table. Then, build an index on the user_id field to allow for rapid retrieval.
In short:
status_updates:
--------------------------------------
| status_id | user_id | status |
--------------------------------------
| 1 | 1 | Woot! |
--------------------------------------
| 2 | 1 | Yeah! |
--------------------------------------
| 3 | 2 | Hello! |
--------------------------------------
users:
--------------------------
| user_id | username |
--------------------------
| 1 | 'Joe' |
--------------------------
| 2 | 'John' |
--------------------------
Then, to retrieve, you would do this:
select
u.username,
s.status
from
status_updates s
inner join users u on
s.user_id = u.user_id
where
u.username = 'John'
This will retrieve:
-------------------------
| username | status |
-------------------------
| John | Hello! |
-------------------------
Do with that what you will. That will be very performant on millions of rows, so long as you build your indexes right. What RDBMS are you using, so I can point you to the right spot for that?
This actually can be very efficient as long as you properly set up an index for the status_updates table on user.
If you are truly worried about the table becoming very, very large you may want to look into horizontal partitioning of your database(s).
It would be quicker to not have a string as part of your search criteria, and instead have your user replaced with a surrogate key:
SELECT update_text
FROM status_updates
INNER JOIN users
ON status_updates.user_id = users.user_id
WHERE users.username = 'username'
Obviously, indexing and potentially partitioning your table could be useful for scalability.