I have two tables, posts and posts_relationship.
posts:id, title, text, lang, timestamp
posts_relationship: id, post_id, subcategory_id
This is my query:
SELECT posts.title,
posts.timestamp
FROM posts_relationship
INNER JOIN posts ON posts.id = posts_relationship.post_id
WHERE posts.lang = 'it' AND
posts.timestamp <= NOW() AND
posts_relationship.subcategory_id = 21
ORDER BY posts.timestamp DESC
I have added indexes on posts.lang, posts.timestamp, posts_relationships.post_id, and posts_relationship.subcategory_id.
But with explain I have always Temporary or Filesort.
How can I have only "Using where"?
It is unclear to me what this line is doing:
posts.timestamp <= NOW() AND
How do you get a timestamp that is in the future?
Without this line, then the query would look like:
SELECT p.title, p.timestamp
FROM posts_relationship pr INNER JOIN
posts p
ON p.id = pr.post_id
WHERE p.lang = 'it' AND
pr.subcategory_id = 21
ORDER BY p.timestamp DESC;
With this query, an index on posts(lang, timestamp, id) might prevent the filesort. The following version of the query would probably ensure this even more:
SELECT p.title, p.timestamp
FROM posts p
WHERE p.lang = 'it' AND
exists (select 1
from posts_relationship pr
where p.id = pr.post_id and pr.subcategory_id = 21
)
ORDER BY p.timestamp DESC;
I don't think this query will use a file sort with the following two indexes:
posts(lang, id, timestamp)
posts_relationship(post_id, subcategory_id)
(By the way, You can keep the condition on timestamp, I just don't understand why it is necessary.)
Related
I have a query in mysql with 2 select from's. When I run these queries individually they run quick within 1 second. But when I combine them with union all, the website freeze's and it takes atleast 20 seconds for the same query to execute in union all.
Any idea why this happens? Can't figure it out.
See the query below:
SELECT p.user_id, p.description, p.post_id, p.created_at, ps.username AS size_name, ps.username AS user_name, ps.avatar AS avatar, pz.title AS title, pz.slug AS slug
FROM comments p
JOIN users ps ON ps.id = p.user_id
JOIN posts pz ON pz.id = p.post_id
UNION ALL
SELECT p2.user_id, p2.description, p2.post_id, p2.created_at, ps2.username AS size_name, ps2.username AS user_name, ps2.avatar AS avatar, pz2.title AS title, pz2.slug AS slug
FROM reply p2
JOIN users ps2 ON ps2.id = p2.user_id
JOIN posts pz2 ON pz2.id = p2.post_id
order by created_at DESC
LIMIT 10
Before merging with UNION, reduce the size of the tables returned by each subquery. Since you only want the top 10, you only need the top 10 of each subquery.
SELECT *
FROM (
(SELECT p.user_id, p.description, p.post_id, p.created_at, ps.username AS size_name, ps.username AS user_name, ps.avatar AS avatar, pz.title AS title, pz.slug AS slug
FROM comments p
JOIN users ps ON ps.id = p.user_id
JOIN posts pz ON pz.id = p.post_id
ORDER BY created_at DESC
LIMIT 10)
UNION ALL
(
SELECT p2.user_id, p2.description, p2.post_id, p2.created_at, ps2.username AS size_name, ps2.username AS user_name, ps2.avatar AS avatar, pz2.title AS title, pz2.slug AS slug
FROM reply p2
JOIN users ps2 ON ps2.id = p2.user_id
JOIN posts pz2 ON pz2.id = p2.post_id
ORDER BY created_at DESC
LIMIT 10)
) AS x
order by created_at DESC
LIMIT 10
UNION defaults to UNION DISTINCT. If you want UNION ALL, explictly say that (and you should, wherever possible, since UNION DISTINCT has to do a lot of work to make them distinct).
The other thing is that the order by and limit apply to the query as a whole. You may want to make each unioned query have them too. See https://dev.mysql.com/doc/refman/8.0/en/union.html:
ORDER BY and LIMIT in Unions
To apply an ORDER BY or LIMIT clause to an individual SELECT, parenthesize the SELECT and place the clause inside the parentheses:
(SELECT a FROM t1 WHERE a=10 AND B=1 ORDER BY a LIMIT 10)
UNION
(SELECT a FROM t2 WHERE a=11 AND B=2 ORDER BY a LIMIT 10);
If your fast individual versions did have those, that should help. So something like this:
(SELECT p.user_id, p.description, p.post_id, p.created_at, ps.username AS size_name, ps.username AS user_name, ps.avatar AS avatar, pz.title AS title, pz.slug AS slug
FROM comments p
JOIN users ps ON ps.id = p.user_id
JOIN posts pz ON pz.id = p.post_id
order by created_at DESC
LIMIT 10)
UNION ALL
(SELECT p2.user_id, p2.description, p2.post_id, p2.created_at, ps2.username AS size_name, ps2.username AS user_name, ps2.avatar AS avatar, pz2.title AS title, pz2.slug AS slug
FROM reply p2
JOIN users ps2 ON ps2.id = p2.user_id
JOIN posts pz2 ON pz2.id = p2.post_id
order by created_at DESC
LIMIT 10)
order by created_at DESC
LIMIT 10
My apologies but I cannot get my head around this one (even not after searching and trying out a few things). All I want to do is join two tables and then sort the join descending on the created_at in the article_translations table. However, I need unique entries.
I have two tables:
articles
--------
id
user_id
article_translations
--------
id
article_id (brings this table together with the other one)
locale
title
...
created_at
updated_at
Performing mysql query:
SELECT * from articles
JOIN article_translations as t on t.article_id = articles.id
ORDER BY t.created_at desc
I get the joined tables with the corresponding related entries.
articles.id t.article_id created_at
1 1 ''
1 1 ''
2 2 ''
When I try no to get rid of the duplicates, in this case of the article with id = 1, I get a nasty error:
Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'blog.t.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
The Desired result would be:
articles.id t.article_id created_at
1 1 ''
2 2 ''
Any help please... Thank You!
The only way to get unique rows is if you want the latest (or the earliest?) date for each id, which you can do if you group by a.id, t.article_id and aggregate:
SELECT a.id, t.article_id, MAX(t.created_at) AS created_at
FROM articles AS a INNER JOIN article_translations AS t
ON t.article_id = a.id
GROUP BY a.id, t.article_id
ORDER BY MAX(t.created_at) DESC
If you want all the columns of the 2 tables, first get unique rows from article_translations with NOT EXISTS and then join to articles:
SELECT *
FROM articles AS a INNER JOIN (
SELECT t.*
FROM article_translations t
WHERE NOT EXISTS (
SELECT 1 FROM article_translations
WHERE article_id = t.article_id AND created_at > t.created_at
)
) AS t
ON t.article_id = a.id
ORDER BY t.created_at DESC
This will work if there are not more than 1 rows in article_translations with the same maximum created_at for an article_id.
For MySql 8.0+ you could use ROW_NUMBER():
SELECT t.* FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY a.id ORDER BY t.created_at DESC) rn
FROM articles AS a INNER JOIN article_translations AS t
ON t.article_id = a.id
) AS t
WHERE t.rn = 1
You are almost there with the query in the question. You just need to add the distinct keyword:
SELECT distinct * from articles
JOIN article_translations as t on t.article_id = articles.id
ORDER BY t.created_at desc`
Thanks forpas for providing the correct answer. I needed this query basically for a Laravel Eloquent model in combination with eager loading. In case anybody cares, that's how the final solution now looks like:
$articles = Article::join('article_translations as at', function ($join) {
$join->on('articles.id', '=', 'at.article_id');
})
->select('articles.id',
'articles.user_id',DB::raw('MAX(at.created_at) as created_at'))
->groupBy('at.article_id')
->orderBy('created_at', 'desc')
->with('translations')
->get();
Pure SQL
SELECT at.article_id, MAX(at.created_at) as created_at FROM articles as a
INNER JOIN article_translations as at
ON a.id = at.article_id
GROUP BY at.article_id
ORDER BY MAX(created_at) desc
Since you do want all the columns:
If you are trying to keep just the article translation with the latest creation date, then assuming the creation dates are unique for a give article's translations, one way would be to create a subquery that computes for each article_translation.article_id the maximum article_translation.created_at column value:
SELECT articles.*, t.* from articles
JOIN article_translations as t on t.article_id = articles.id
JOIN (
SELECT article_id, max(created_at) as created_at from article_translations
GROUP BY article_id
) a_t on t.article_id = a_t.article_id and t.created_at = a_t.created_at
ORDER BY t.created_at desc
If the creation dates are not unique, or even if they are, then this should also work:
SELECT articles.*, t.* from articles
JOIN article_translations as t on t.article_id = articles.id
AND t.article_id = (
SELECT t2.article_id from article_translations t2
WHERE t2.article_id = t.article_id
ORDER BY created_date DESC
LIMIT 1
)
ORDER BY t.created_at DESC
SQL Query:
SELECT
T.*,
U.nick AS author_nick,
P.id AS post_id,
P.name AS post_name,
P.author AS post_author_id,
P.date AS post_date,
U2.nick AS post_author
FROM
zero_topics T
LEFT JOIN
zero_posts P
ON
T.id = P.topic_id
LEFT JOIN
zero_players U
ON
T.author = U.uuid
LEFT JOIN
zero_players U2
ON
P.author = U2.uuid
ORDER BY
CASE
WHEN P.date is null THEN T.date
ELSE P.date
END DESC
Output:
Topics:
Posts:
Question: Why i have duplicated topic id 22? i have in mysql two topics (id 22 and 23) and two posts(id 24 and 25). I want to see topic with last post only.
If a join produces multiple results and you want only at most one result, you have to rewrite the join and/or filtering criteria to provide that result. If you want only the latest result of all the results, it's doable and reasonably easy once you use it a few times.
select a.Data, b.Data
from Table1 a
left join Table2 b
on b.JoinValue = a.JoinValue
and b.DateField =(
select Max( DateField )
from Table2
where JoinValue = b.JoinValue );
The correlated subquery pulls out the one date that is the highest (most recent) value of all the joinable candidates. That then becomes the row that takes part in the join -- or, of course, nothing if there are no candidates at all. This is a pattern I use quite a lot.
I have the above models in my MySQL database:
Blogs (id: integer, name: varchar)
Posts (id: integer, name: varchar, blog_id: integer, created_at: date)
I want to retrieve a list of all the blogs, ordered by the ones that have the newests posts.
I've reached that with the following query:
SELECT b.*, (SELECT p.created_at FROM posts p WHERE p.blog_id = b.id ORDER BY p.created_at DESC LIMIT 1) AS last_post_created_at FROM blogs b ORDER BY last_post_created_at DESC;
But this query is too slow and I'm unable to use it on my application.
Do you guys have a good solution for that?
Thank you.
A rewriting of the query:
SELECT b.*,
p.last_post_created_at
FROM blogs b
LEFT JOIN
( SELECT blog_id,
MAX(created_at) AS last_post_created_at
FROM posts
GROUP BY blog_id
) AS p
ON p.blog_id = b.id
ORDER BY last_post_created_at DESC;
An index on (blog_id, created_at) will help both this and your version.
If you want to limit the number of blogs returned, you should add the ORDER BY in the subquery and put the LIMIT there:
SELECT b.*,
p.last_post_created_at
FROM blogs b
LEFT JOIN
( SELECT blog_id,
MAX(created_at) AS last_post_created_at
FROM posts
GROUP BY blog_id
ORDER BY last_post_created_at DESC
LIMIT 100
) AS p
ON p.blog_id = b.id
ORDER BY last_post_created_at DESC;
Use a view. Great thing to know and use!
I'm trying to fetch 100 posts and order them by the number of times they've been "remixed" in the last week. Here is my query thus far:
SELECT COUNT(remixes.post_id) AS count, posts.title
FROM posts
LEFT JOIN (
SELECT * FROM remixes WHERE created_at >= 1343053513
) AS remixes ON posts.id = remixes.post_id
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
This produces the correct result; however, after running DESCRIBE I get this:
And here are my indexes on posts:
And my indexes on remixes:
And here are my questions:
Can you explain what the terms used in the extra column are really trying to tell me?
Could you provide tips on how I can optimize this query so that it'll scale better.
Thanks in advance!
Update
Per Zane's solution, I've updated my query to:
SELECT COUNT(remixes.post_id) AS count, posts.title
FROM posts
LEFT JOIN remixes ON posts.id = remixes.post_id AND remixes.created_at >= 1343053513
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
And here's the latest DESCRIBE
I'm still worried about the filesort part. Any ideas?
Try not to wrap your JOIN in a sub-select as this will create an unindexed temporary table to store the result of the subselect in, where it then joins on that unindexed table.
Instead, put created_at as an additional join condition when joining the remixes table:
SELECT
a.title, COUNT(b.post_id) AS remixcnt
FROM
posts a
LEFT JOIN
remixes b ON a.id = b.post_id AND b.created_at >= 1343053513
GROUP BY
a.id, a.title
ORDER BY
remixcnt DESC, a.created_at DESC
LIMIT 100
It seems to me that
SELECT COUNT(remixes.post_id) AS count, posts.title
FROM posts
LEFT JOIN (
SELECT * FROM remixes WHERE created_at >= 1343053513
) AS remixes ON posts.id = remixes.post_id
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
could be rewritten as
SELECT COUNT(r.post_id) AS count, posts.title
FROM posts
LEFT JOIN remixes r ON posts.id = r.post_id
WHERE r.created_at >= 1343053513
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
which should give you a better EXPLAIN plan and run faster.