I'm creating a site where users can create photo albums, create events, upload videos etc. What I want to do is make a list of a given user's recent activity. Here's a small outline of my tables:
**videos**
id
user_id
uploaded
**albums**
id
user_id
created
updated
**comments**
id
user_id
date
Of course there are more fields in the table, and also more tables, but these should be enough to help me construct a query.
Now what I want to be output is a date, and the id for a given activity with these fields:
user_id, video_id, album_id, comment_id, date
Of course only one of the ID fields should be chosen, the rest should just be null, and the date should come from "uploaded" for videos, "updated" for albums and "date" for comments. The user_id should be selected in the query in a where statement so you get activity for a given user.
I've tried to construct this query but failed, quessing that COALESCE should be used for choosing the different timestamps but I just can't get around it.
Something like this?
(select user_id, id as video_id, NULL as album_id, NULL as comment_id, uploaded as date from videos)
UNION
(select user_id, NULL, id, NULL, uploaded from albums)
UNION
(select user_id, NULL, NULL, id, date from comments)
You can apply an ORDER BY clause after the whole thing, but WHERE conditions much be put inside the parenteses of each separate SELECT.
Related
This question SQL select only rows with max value on a column doesn't solve my problem although it has been marked as duplicate.
It assumes my columns from_id and to_id are primary keys, when they don't have such constraint (see code provided bellow). If they were primary keys, I couldn't store my messages in the same table. As a result the SQL query of this answer prints all duplicates multiple times, which is not what I want. Please see expected behaviour bellow.
Expected behaviour : I need to select the latest message from all conversations, regardless of whether the user is only sender, recipient, or both. Each conversation/thread should only be displayed once.
Example : when querying this table, my SQL statement should only output msg3 and msg4, ignoring all the previous messages John and Alice exchanged.
Here is the closest query I could write. Problem is this query only selects conversations where user received a message. I'm stuck adding conversations where user is only sender (he didn't get any reply) to the selection.
SELECT * FROM messages where `to_id` = '1' GROUP BY `from_id` ORDER BY `send_date` ASC
Here are users and messages tables:
CREATE TABLE users (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) NOT NULL
);
CREATE TABLE messages (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
to_id INT(11) NOT NULL, //recipient id to match to current user id
from_id INT(11) NOT NULL, //sender id to match to current user id
send_date DATETIME DEFAULT CURRENT_TIMESTAMP,
content TEXT
);
Question: How can I do this using a single SQL query ? Or should I change my data structure using three tables instead of one ?
I would first get the ids. You can do this using least() and greatest():
select least(m.to_id, m.from_id) as id1,
greatest(m.to_id, m.from_id) as id2, max(m.id) as max_id
from messages m
group by id1, id2;
You can then get the complete information about the message by joining back:
select m.*
from messages m
where m.id in (select max(m.id) as max_id
from messages m
group by least(m.to_id, m.from_id), greatest(m.to_id, m.from_id)
);
Note: In older versions of MySQL, putting the subquery in the from clause and using join is much more efficient.
I have a table with columns:
id , conversation_id , session_id , user_id , message , created_at
every time a user starts a conversation with an employee, a new session starts (different session number).all messages between every employees and users are stored in this table. the created_at column is a timestamp. I need to filter out sessions by employee number, and calculate the average response time between the first message a user sends and the first message sent back by a specific employee, for every session disregarding outlying data where either a customer or employee did not reply ( only one user in the session)
i know this is complicated but please help!
in this example in the user_id column, 4 is the employee ( keep in mind there are other employees). everytime a new conversation starts the session_id changes. i have to go through each session for a specific employee, take the timestamp of the first message sent by the customer as well as the employee, take the difference, sum all the differences and then take an average, while making sure that the session actually contains two users ( filtering outlying data).
So far, ive come up with this:
SELECT * FROM messages
WHERE session_id IN (
SELECT session_id FROM messages
WHERE user_id =4 )
GROUP BY session_id, user_id
to get the first message from each customer and employee (gives something like this)
so from this specific example, i would omit line 41040 as it only as the session contains only 1 person (column 3, id 1028) and is considered outlying data
I'm actually appalled by some of the comments... StackOverflow is meant to be a community for helping others. Why bother even taking up comment space if you're gonna complain about my ponctuation or give a vague, useless answer?
Anyways, i figured it out.
Basically, i joined the same table multiple times but only queried the necessary data. In the first join, I queried the messages table with the employee messages and grouped them by session number. In the second join, i did the same procedure but only extracted the messages from the user. By joining them on the session id, it automatically omits any sessions where either a user or employee is not present. By default, the groupby returns the first set of data from the group ( in this situation i didn't have to manipulate the groupby because I was actually looking for the first message in the session), I then took the average of the difference between the message timestamp for the user and employee.In this specific situation, the number 4 is the employee number. Here is what the query looks like Also, the HAVING AVG_RESP > 0 was necessary in this situation to remove outlying data when tests are performed :
SELECT AVG(AVG_RESP)
FROM(
SELECT TIME_TO_SEC(TIMEDIFF(t.created_at, u.created_at )) AS AVG_RESP
FROM (
SELECT * FROM messages
WHERE session_id IN (
SELECT session_id FROM messages
WHERE user_id = 4) AND user_id = 4
GROUP BY session_id
) AS t
JOIN(
SELECT * FROM messages
WHERE session_id IN (
SELECT session_id FROM messages
WHERE user_id = 4) AND user_id != 4
GROUP BY session_id
) as u
ON t.session_id = u.session_id
GROUP BY t.session_id
HAVING AVG_RESP > 0
) as ar
Hopefully this helps someone in the future, unlike the people who leave ridiculous, useless comments.
I have a books table that stores books editions. Often, the same book is inserted several times in my database because it exists in different forms (hardcover, paperback, ebook, etc.). Each book have the following fields (among others) :
id (which is a unique primary key)
title
item (which is a int, two different forms of the same book will have the same item, book that
exists in only one form will be NULL)
Now if have another sales table that stores sales and looks like that :
id (unique primary key)
book_id (which relates to book.id)
date (the date when the sale happened)
I need to get a list of best-sellers book, grouping the differents editions of the books as if there were the same book, so my sql query looks like :
SELECT `books`.`title`, COUNT(`sales`.`id`)
FROM `sales`
JOIN `books` ON `books`.`id` = `sales`.`book_id`
GROUP BY (CASE WHEN `item` IS NOT NULL THEN `item` ELSE `books`.`id` END)
The problem is that the book « Margherita Dolcevita » that has id 27057 and item NULL gets grouped with the book « Master of the storm » that has id 49522 and item 27057.
What do I need to change in my sql query (preferably) or in my database scheme to get what I want with only one query ?
The problem is on the CASE from the GROUP BY clause. You mix there "item" with "id".
Ideally, you should keep a single entry for any book in the books table and have a different table for the book formats having these fields:
id (PK, autoincrement);
book_id (FK, books.id);
format (integer, string, whatever fits your design);
Using your current design, try to make the ranges of the two IDs you use in the CASE expression to not overlap (f.e. add a prefix to books.id):
GROUP BY (CASE WHEN `item` IS NOT NULL THEN `item` ELSE CONCAT('book-', `books`.`id`) END)
A simpler form (and easier to read and understand) of the same expression is:
GROUP BY IFNULL(`item`, CONCAT('book-', `books`.`id`))
I have two tables users and sellers where user_id and seller_id is different. I want to compare the search query on the first name and the last name of sellers and users and get the results. The two tables have no foreign key associates with it, and on the sellers table its just the seller name. No first name or last name.
SELECT *
FROM users
WHERE first_name LIKE "%'.$search_string.'%"
OR last_name LIKE "%'.$search_string.'%"
that works for the users table.. how to join it with the sellers table?
From the users table I would like to get user_id,first_name,last_name. From the sellers table I would like to get seller_id, seller_name.
I don't quite get what you're trying to accomplish, because you don't mention if there's a joinable field like, for example, transaction id, to relate users and sellers.
If you're searching just any users row or sellers row where the subject matches a search string, you might try with
SELECT 'users' as origin, CONCATENATE('first_name',' ','last_name') as name
FROM users
WHERE (first_name like '%".$search_string."%' OR last_name like '%".$search_string."%')
UNION
SELECT 'sellers' as origin, seller_name as name
FROM sellers
WHERE ( seller_name like '%".$search_string."%')
keep in mind that, if you remove the 'origin' column, the UNION statement will perform an implicit DISTINCT on the resultset. If you need to display dupe results then you should use UNION ALL.
I'm quite new to databases so i apologise in advance if this sounds silly. Im creating a basic web application that simulates a micro blogging website. I have three tables authors, posts & comments.
The authors table is described as follows:
aId int(20) NO PRI NULL auto_increment
aUser varchar(30) NO UNI NULL
aPass varchar(40) NO NULL
aEmail varchar(30) NO UNI NULL
aBio mediumtext YES NULL
aReg datetime NO NULL
the posts table is described below:
pId int(20) NO PRI NULL auto_increment
pAuthor int(20) NO MUL NULL
pTitle tinytext NO NULL
pBody mediumtext NO NULL
pDate datetime NO NULL
I understand the basics of relationships, but could i ask, if on my web application i want to display the posts and include who posted them, is there a way of doing this so the result set will show the actual username, rather than the numeric ID ? each time a post is created i capture the users ID, so every post created is by a valid user ID and the post table records the user ID of the person who created it, but when viewing the posts in a select query it shows the numbers and not the names associated with them in the authors table. us there a query i could use to do this or a way of doing it so when use a select * from authors, it shows the usernames rather than the user ID.
Thank you guys.
I think you are looking for a SQL query:
SELECT pTitle, pDate, aUser
FROM posts
LEFT JOIN authors ON aId=pAuthor
ORDER BY pDate DESC
After the SELECT you tell the MySQL what columns you want to see, with the LEFT JOIN you connect the tables together (by aId and pAuthor) and with ORDER BY you tell the mysql to give them to you ordered by date starting from newest pDate DESC (highest date first)
SELECT posts.*, authors.aUser
FROM posts
LEFT JOIN authors ON aId=pAuthor
WHERE pTitle LIKE "%news%"
ORDER BY pDate DESC, aUser ASC
to see the author's name when searching for posts with title containing "news" sorted from the newest posts, and in case two posts having the same timestamp, show them ordered by users name (Adam will go before Zachariash)
In case you do not need to see more than title, date and users name, use the 1st row from the first query above