MYSQL select statement - 5 results from each user - mysql

I'm trying to do a select statement and it works except that it's not limiting the number of results for each user (U.id) to 5.
SELECT F.id,F.created,U.username,U.fullname,U.id,I.id,I.cached_image
FROM favorites AS F
INNER JOIN users AS U
ON F.faver_profile_id = U.id
INNER JOIN items AS I
ON F.notice_id = I.id
WHERE faver_profile_id IN ('.$users.')
GROUP BY I.id HAVING COUNT(U.id) <= 5
ORDER BY F.faver_profile_id, F.created DESC
I'm grouping by I.id to eliminate duplicates. From my research it looks like you can only use HAVING COUNT if your also grouping by that column, but I cannot group by U.id or I'd lose results rows.

Instead of HAVING, can you slap a LIMIT 5 in there?
Edit: OP cannot LIMIT entire query,
and, AFAIK, MySQL does not support LIMIT in subqueries,
so you can create a temporary table with your five (5) user ids:
create table temp_table ( id INT );
insert into temp_table (id) SELECT U.id FROM users U LIMIT 5;
SELECT F.id,F.created,U.username,U.fullname,U.id,I.id,I.cached_image
FROM favorites AS F
INNER JOIN temp_table AS Ut
ON F.faver_profile_id = Ut.id
INNER JOIN items AS I
ON F.notice_id = I.id
WHERE faver_profile_id IN ('.$users.')
GROUP BY I.id
ORDER BY F.faver_profile_id, F.created DESC;
drop table temp_Table;
Let us know how that works.

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;

Fetch only one record from first order by and rest from second order by

I have a query
SELECT s.*
, g.*
from tbl_section1 as s
, tbl_game as g
LEFT
JOIN tbl_game_visit_count AS gvc
ON g.game_id = gvc.game_id
where s.category_id = g.game_id
ORDER
BY g.udate DESC
, gvc.visit_count DESC
which works fine.
But I want to fetch the first record ordered by g.udate, and then the rest of the records ordered by gvc.visit_count.
Is this possible using mysql query?
Thanks in advance.
It could be possible by using UNION(not UNION ALL, since we don't want to duplicate rows ) between two queries with ORDER BY and LIMIT clauses inside parentheses
SELECT q.*
FROM
(
SELECT s.*, g.*
FROM tbl_section1 as s
INNER JOIN tbl_game as g ON s.category_id = g.game_id
LEFT JOIN tbl_game_visit_count AS gvc ON g.game_id = gvc.game_id
ORDER BY g.udate DESC
LIMIT 1
) q
UNION
SELECT s.*, g.*
FROM tbl_section1 as s
INNER JOIN tbl_game as g ON s.category_id = g.game_id
LEFT JOIN tbl_game_visit_count AS gvc ON g.game_id = gvc.game_id
ORDER BY gvc.visit_count DESC;
P.S. Because of your original query I kept DESC options for ORDER BY, you can get rid of them if you want regular ascending ordering.

How to improve performance of multiple joined table in sql query

How do I improve performance this query while also get all the information needed..
SELECT
tr.id, tr.request_status, tr.note, tr.created_date,
c.name AS customer_name, c.mobile_phONe,
u.full_name AS created_by_name, tt.name AS ticket_type_name
FROM
ticket_request tr
LEFT JOIN
ticket_type tt ON tt.id = tr.ticket_type_id
LEFT JOIN
users u ON u.id = tr.created_by
LEFT JOIN
customer c ON c.id = tr.customer_id
WHERE
tr.is_deleted != 1
AND tr.user_id IN (SELECT u.id FROM users u WHERE u.status = '1')
GROUP BY
tr.id
ORDER BY
tr.created_date DESC
LIMIT 0,20
Currently, this query runs in 7-10 seconds.
ticket_request table has about 100k rows
customers table has about 300k rows
users table and ticket_type don't have that much (about 1k rows)
The speedup technique below is to dispense with the LIMIT first, and only after that, do all the JOINs.
SELECT tr3.id, tr3.request_status, tr3.note, tr3.created_date,
c.name AS customer_name, c.mobile_phONe,
u2.full_name AS created_by_name,
tt.name AS ticket_type_name
FROM
(
SELECT tr1.id
FROM ticket_request tr1
JOIN users u1 ON u1.id = tr1.created_by
WHERE u1.status = '1'
AND tr1.is_deleted != 1
ORDER BY tr1.created_date DESC
LIMIT 0,20
) AS tr2
JOIN ticket_request AS tr3 ON tr3.id = tr2.id
JOIN user AS u2 ON u2.id = tr3.created_by
LEFT JOIN ticket_type tt ON tt.id = tr3.ticket_type_id
LEFT JOIN customer c ON c.id = tr3.customer_id
ORDER BY tr3.created_date
The JOINs, after the one in the "derived" table tr2, are touching only 20 rows; this is much of the speedup.
This may be equally good:
SELECT d.id, d.request_status, d.note, d.created_date,
c.name AS customer_name, c.mobile_phONe, d.created_by_name,
tt.name AS ticket_type_name
FROM
(
SELECT tr.id AS tr_id, tr.request_status, tr.note, tr.created_date,
tr.ticket_type_id, tr.customer_id
u.full_name AS created_by_name
FROM ticket_request tr
JOIN users u ON u.id = tr.created_by
WHERE u.status = '1'
AND tr.is_deleted != 1
ORDER BY tr.created_date DESC
LIMIT 0,20
) AS d
LEFT JOIN ticket_type tt ON tt.id = d.ticket_type_id
LEFT JOIN customer c ON c.id = d.customer_id
ORDER BY d.created_date
I'm assuming that you are using MySQL. If not, this answer can be slightly modified to fit another database, but the concept should remain the same. You can add indices to all the ID columns which are involved in the right hand side of your left joins with the ticket_request column:
ALTER TABLE ticket_type ADD INDEX (id);
ALTER TABLE users ADD INDEX (id);
ALTER TABLE customer ADD INDEX (id); -- important
To explain why an index would help, consider the first LEFT JOIN between your ticket_request table and the ticket_type table. Without an index, for each record in ticket_request the database would have to potentially scan the entire ticket_type table to find records which match the join condition. This is costly from a performance point of view. But with an index, the database can complete this operation much faster, since it "knows" where to look exactly (or almost exactly) for the matching records.
Though you mentioned that only the customer table is very large, you can still add indices to the other tables. In the future, they might get larger too. Most likely the join involving customer is the bottleneck in your query.
SELECT
tr.id, tr.request_status, tr.note, tr.created_date,
c.name AS customer_name, c.mobile_phONe,
u.full_name AS created_by_name, tt.name AS ticket_type_name
FROM
ticket_request tr
LEFT JOIN
ticket_type tt ON tt.id = tr.ticket_type_id and tr.is_deleted != 1
LEFT JOIN
users u ON u.id = tr.created_by
JOIN
users u1 ON u1.id = tr.user_id and u1.status = '1'
LEFT JOIN
customer c ON c.id = tr.customer_id
GROUP BY
tr.id
ORDER BY
tr.created_date DESC
LIMIT 0,20
try this it will work with improved performance and tweak as per your requirement
The biggest opportunity for optimization here is with LIMIT 0,20
GROUP BY tr.id make no sense and should be removed.
create index ticket_request_ix_is_deleted_created_date on ticket_request (is_deleted,created_date) and change tr.is_deleted != 1 to tr.is_deleted = 0.
Or
create index ticket_request_ix_created_date on ticket_request (created_date)
Other than indexing , On application level you can use Memcached ( in case you are using php) like stuffs. This will also give you great performance.

MySQL LEFT JOIN only one row, ordered by column without subquery

Is there a possibility to do LEFT JOIN with only one row from other table ordered by column (date) without using sub query. My query is below. It works but it's super slow.
SELECT * FROM clients c
LEFT JOIN loan l ON c.id = l.id_client AND l.id = (
SELECT id FROM loan ll
WHERE ll.id_client = c.id
ORDER BY `create_date` DESC
LIMIT 1)
GROUP BY k.id DESC
ORDER BY c.register_date DESC
LIMIT n , m; (n,m is from pagination)
Is there a way to speed it up?
Im interpreting your question as "Get me all loan details for the most recent loan for each client"
This should work... note the assumption though.
SELECT *
FROM
clients c
LEFT JOIN (select id_client, Max(id) id -- this assumes that a loan with a later create date will also have a higher id.
from loan
group by id_client) il
on il.id_client = c.id
inner join loan l
on l.id = il.id
GROUP BY k.id DESC -- Dont know what "k" is
ORDER BY c.register_date DESC
LIMIT n , m; (n,m is from pagination)

SQL query to check if value doesn't exist in another table

I have a SQL query which does most of what I need it to do but I'm running into a problem.
There are 3 tables in total. entries, entry_meta and votes.
I need to get an entire row from entries when competition_id = 420 in the entry_meta table and the ID either doesn't exist in votes or it does exist but the user_id column value isn't 1.
Here's the query I'm using:
SELECT entries.* FROM entries
INNER JOIN entry_meta ON (entries.ID = entry_meta.entry_id)
WHERE 1=1
AND ( ( entry_meta.meta_key = 'competition_id' AND CAST(entry_meta.meta_value AS CHAR) = '420') )
GROUP BY entries.ID
ORDER BY entries.submission_date DESC
LIMIT 0, 25;
The votes table has 4 columns. vote_id, entry_id, user_id, value.
One option I was thinking of was to SELECT entry_id FROM votes WHERE user_id = 1 and include it in an AND clause in my query. Is this acceptable/efficient?
E.g.
AND entries.ID NOT IN (SELECT entry_id FROM votes WHERE user_id = 1)
A left join with an appropriate where clause might be useful:
SELECT
entries.*
FROM
entries
INNER JOIN entry_meta ON (entries.ID = entry_meta.entry_id)
LEFT JOIN votes ON entries.ID = votes.entry_id
WHERE 1=1
AND (
entry_meta.meta_key = 'competition_id'
AND CAST(entry_meta.meta_value AS CHAR) = '420')
AND votes.entry_id IS NULL -- This will remove any entry with votes
)
GROUP BY entries.ID
ORDER BY entries.submission_date DESC
Here's an implementation of Andrew's suggestion to use exists / not exists.
select
e.*
from
entries e
join entry_meta em on e.ID = em.entry_id
where
em.meta_key = 'competition_id'
and cast(em.meta_value as char) = '420'
and (
not exists (
select 1
from votes v
where
v.entry_id = e.ID
)
or exists (
select 1
from votes v
where
v.entry_id = e.ID
and v.user_id != 1
)
)
group by e.ID
order by e.submission_date desc
limit 0, 25;
Note: it's generally not a good idea to put a function inside a where clause (due to performance reasons), but since you're also joining on IDs you should be OK.
Also, The left join suggestion by Barranka may cause the query to return more rows than your are expecting (assuming that there is a 1:many relationship between entries and votes).