Mysql select oldest record for each user - php

I'm trying to create a list in PHP of the oldest entries for each user in the database.
SELECT *,
MIN(`entries`.`entry_date`)
AS entry_date
FROM (`entries`)
JOIN `user_profiles`
ON `user_profiles`.`user_id` = `entries`.`user_id`
WHERE `status` = 1
GROUP BY `entries`.`user_id`
I'm using the query to retrieve from the entries table the oldest dated entry using MIN()and joining with table user_profiles for other data. The query should select the oldest entry for each user. It seems to work but it retrieves the wrong entry_date field on some entries when I echo them. Please help, I can't spot what I'm doing wrong..

You need to use a subquery to obtain the (user_id, entry_date) pairs for each user, then join that with a query that selects the records of interest:
SELECT *
FROM entries
NATURAL JOIN (
SELECT user_id, MIN(entry_date) AS entry_date
FROM entries
GROUP BY user_id
) AS tmin
JOIN user_profiles USING (user_id)
WHERE status = 1

Have you tried approaching the problem from the user_profiles table instead of the entries table? If a user has no entries, they will not appear in the above query.
This may help, but I'm not sure if it's the full solution:
SELECT *, MIN(entries.entry_date) as entry_date
FROM user_profiles LEFT JOIN entries USING (user_id)
WHERE status = 1
GROUP BY user_profiles.user_id
Also, you're renaming the MIN(entires.entry_date) as entry_date... but you already have a column named entry_date. Try renaming the derived columns to something unique like "min_entry_date"?

Related

SQL: How do I filter (WHERE) a joined (ON) table in SQL?

I have two tables (users and posts) and I want to write out all posts (among other things) by one user. I'm thinking I should use a JOIN and WHERE but I get an error for using WHERE.
This is my code:
SELECT username, post, joinDate, title, info FROM users
WHERE userId='18'
JOIN posts ON users.userId=posts.userId
ORDER BY date DESC
I'm new to this and perhaps there is a better way but I can't figure it out atm.
Thankful for all answers!
The JOIN clause comes before the WHERE clause, after the FROM clause. First you join together all the tables you need, then you do your filtering with WHERE. Like this:
SELECT username, post, joinDate, title, info
FROM users
JOIN posts ON users.userId=posts.userId
WHERE users.userId='18'
ORDER BY date DESC
try like below
SELECT u.*,p.*
FROM users u JOIN posts p ON u.userId=p.userId
WHERE u.userId=18
ORDER BY date DESC
where will be after join and 18 is int datatype value so not need single quote for this and use alias for avoiding ambigous column name
SELECT username, post, joinDate, title, info
FROM users
JOIN posts ON users.userId=posts.userId and users.userId='18'
ORDER BY date DESC
if userId is in users table then YES you can use where userId='18'.
If userId is in posts table then it should be userId='18' be in join part.

Pagination query mysql+php take 20-40 seconds

Hi I have pagination in angular js in my app, I send the data to my big query that includes the filters that the user set
UPDATE
SQL_CALC_FOUND_ROWS this is my problem. How do I count the rows of specific filters . It is took me 2 second for 100,000 rows I need the number for the pagination as a total number
UPDATE:
I have the following inner query that I missed here :
(select count(*) from students as inner_st where st.name = inner_st.name) as names,
when I remove above inner query is much faster
rows: 50,000
Users table : 4 rows
Classes table : 4 rows
indexes: only id as primary key
query time 20-40 seconds
tables: students.
columns : id, date ,class, name,image,status,user_id,active
table user
coloumn: id,full_name,is_admin
query
SELECT SQL_CALC_FOUND_ROWS st.id,
st.date,
st.image,
st.user_id,
st.status,
st,
ck.name AS class_name,
users.full_name,
(select count(*) from students AS inner_st where st.name = inner_st.name) AS names,
FROM students AS st
LEFT JOIN users ON st.user_id = users.user_id
LEFT JOIN classes AS ck ON st.class = ck.id
WHERE date BETWEEN '2018-01-17' AND DATE_ADD('2018-01-17', INTERVAL 1 DAY)
AND DATE_FORMAT(date,'%H:%i') >= '00:00'
AND DATE_FORMAT(date,'%H:%i') <= '23:59'
AND st.active=1
-- here I can concat filters from web like "and class= 1"
ORDER BY st.date DESC
LIMIT 0, 10
How can I make it faster? when I delete the order by and SQL_CALC_FOUND_ROWS it faster but i need them
I heard about indexes but only primary key is index
Few comments before recommending a different approach to this query:
Did you consider removing SQL_CALC_FOUND_ROWS and instead running two queries (one that counts and one that selects the data)? In some cases it might be quicker than joining them both to one query.
What is the goal of these conditions? What are you trying to achieve? Can we remove them (as it seems they might always return true?) - AND DATE_FORMAT(st.date, '%H:%i') >= '00:00' AND DATE_FORMAT(st.date, '%H:%i') <= '23:59'
You only need 10 results, but the database will have to run the "names" subquery for each of the results before the LIMIT (which might be a lot?). Therefore, I would recommend to extract the subquery from the SELECT clause to a temporary table, index it and join to it (see fixed query below).
To optimize the query, let's begin with adding these indexes:
ALTER TABLE `classes` ADD INDEX `classes_index_1` (`id`, `name`);
ALTER TABLE `students` ADD INDEX `students_index_1` (`active`, `user_id`, `class`, `name`, `date`);
ALTER TABLE `users` ADD INDEX `users_index_1` (`user_id`, `full_name`);
Now create the temporary table (originally this was a subquery in the SELECT clause) and index it:
-- Transformed subquery to a temp table to improve performance
CREATE TEMPORARY TABLE IF NOT EXISTS temp1 AS SELECT
count(*) AS names,
name
FROM
students AS inner_st
WHERE
1 = 1
GROUP BY
name
ORDER BY
NULL
-- This index is required for optimal temp tables performance
ALTER TABLE
`temp1`
ADD
INDEX `temp1_index_1` (`name`, `names`);
And the modified query:
SELECT
SQL_CALC_FOUND_ROWS st.id,
st.date,
st.image,
st.user_id,
st.status,
ck.name AS class_name,
users.full_name,
temp1.names
FROM
students AS st
LEFT JOIN
users
ON st.user_id = users.user_id
LEFT JOIN
classes AS ck
ON st.class = ck.id
LEFT JOIN
temp1
ON st.name = temp1.name
WHERE
st.date BETWEEN '2018-01-17' AND DATE_ADD('2018-01-17', INTERVAL 1 DAY)
AND st.active = 1
ORDER BY
st.date DESC LIMIT 0,
10
Give this a try first:
INDEX(active, date)
Is user_id the PK for users? Is class_id the PK for classes? If not, then they should be INDEXed.
Why are you testing the times separate?
Fix the test so it is obvious which table each column is in.
Do you really need LEFT JOIN? Or would JOIN suffice? In the latter case, there are more optimization options.
Give some realistic examples of other SELECTs; different index(es) may be needed.
Is the "first" page slow? Or only later pages? See this for pagination optimization -- by not using OFFSET.

Getting a Triple Join To Work When Rows Are Missing?

I have 3 tables. 1 table is like the master table and I want all rows from this table where GameID = X. Then I have a guides table which will have a matching ID and finally i have a user table that defines whether the user has selected this row to be hidden. this is causing issues. This table may not have a row associated with it. This table is shared amongst ALL users. The primary key of this table is UserID+InfoID. The query below returns what I want provided there are no other rows in the table for other userIDs.
SELECT PS_Info.*, PS_Guides.Guide, PS_Userhidden.* FROM PS_Info
LEFT JOIN PS_Guides ON PS_Info.ID = PS_Guides.InfoID
LEFT JOIN PS_Userhidden ON PS_Info.ID = PS_Userhidden.InfoID
WHERE PS_Info.GameID = :ID AND (PS_Userhidden.UserID = :UserID)
OR (PS_Userhidden.UserID IS NULL AND PS_Userhidden.InfoID IS NULL)
So I will run the php script and have infoID =1 and userID=1. In the table there is infoID=1 and userid = 2, but nothing will be returned for this row. If I remove PS_Userhidden.UserID = :UserID I get multiple of the same row. The user table will grow to millions of rows. I need a way to make this query stick to the primary key of the users table so it will still return a row if no match exists in the user table and also return a row if there is a match in the users table for the specific user
I think you just need to move the condition on the hidden user to the ON clause:
SELECT i.*, g.Guide, h.*
FROM PS_Info i LEFT JOIN
PS_Guides g
ON i.ID = g.InfoID LEFT JOIN
PS_Userhidden h
ON i.ID = h.InfoID AND h.UserID = :UserID
WHERE i.GameID = :ID ;
Your description of the problem sounds like something that can happen when you start fiddling with conditions in the WHERE clause of a LEFT JOIN. It is a little hard to follow though. If this doesn't work, edit your question with sample data and desired results -- or, better yet, set up a SQL Fiddle.

MySQL INNER JOIN with different column names

Okay, so I have two tables, a news table and a users table. They are set up as below:
news:
id title user_id contents
users:
id username password
When I run this query:
SELECT news.title, users.username, news.contents FROM news INNER JOIN users ON news.user_id=users.id WHERE news.id=:id
I can only access the user id using $query['id']. So it appears that the join is using the column names from table2, although I want them to map it to the column names of table1. So even though the column is called id in the users table, I want to access it under user_id, since I specified that in the JOIN query.
I need this because if I ever need the id from table1, they would both be mapped to the column called id and I would only be able to get the id of the users table.
So is there any way to do this? Access the column from table2 under the name of the column in table1?
In MySQL what you specify in the select statement is what it is called in the result set, so you need to tell it what to give you. you do that with the AS command
SELECT users.id AS user_id ... FROM ...
That way you can call it whatever you want
or grab the user_id from news with
SELECT news.user_id ... FROM ...
SELECT users.id AS id1, news.id AS id2, news.title, users.username, news.contents
FROM news INNER JOIN users ON news.user_id=users.id WHERE news.id=:id
echo $query['id1'] . $query['id2'];

MySQL, combining DISTINCT and JOIN

I have a table of users, called ..wait for it... "users".. and then another table which records users' activity called ...you'll never guess... "activity"..
I have an HTML table which shows the users in the users table, but I want to order the table by the users' last activity.
Here's my query.
SELECT *
FROM `users`
JOIN `activity`
ON `activity`.`user_id` = `users`.`id`
ORDER BY `activity`.`timestamp`
LIMIT 25
The problem here is that it shows multiple rows for each user since there are multiple records for each user in the activity table.
How can I alter the query to only show one record for each user and order them by the last activity in the activity table.?
I did experiment with using the "DISTINCT" keyword but no luck :/
"one record for each user and order them by the last activity in the activity table.?"
Couly try this? Assuming you need both user info and activity info.
SELECT users.*, a.*
FROM users INNER JOIN (
SELECT user_id, MAX(timestamp) max_timestamp
FROM activity
GROUP BY user_id
) x ON users.user_id = x.user_id
INNER JOIN activity a ON x.user_id = a.user_id AND a.timestamp = x.max_timestamp
ORDER BY a.timestamp;
inner sub-query finds max_timestamp per user_id and outer JOIN finds activity has max_timestamp and user_id
This is not SQL solution, but what I would do is keep lastActivity column in Users table and update it when needed. Then sorting, filtering by this column would be very easy and efficient.

Categories