Mysql: order by two column, use filesort - mysql

I have trouble ordering two columns.
EXPLAIN SELECT * FROM articles WHERE option <>0 AND deleted=0 ORDER BY
date_added DESC, category_id DESC LIMIT 25 OFFSET 500
id select_type table type possible_keys key key_len ref rows
Extra 1 SIMPLE articles ALL NULL NULL NULL NULL 437168 Using
where; Using filesort
I add single indexes for (option, deleted, date_added, category_id)
When i used:
EXPLAIN SELECT * FROM articles WHERE option <>0 AND deleted=0 ORDER BY
date_added DESC LIMIT 25 OFFSET 500
or
EXPLAIN SELECT * FROM articles WHERE option <>0 AND deleted=0 ORDER BY
category_id DESC LIMIT 25 OFFSET 500
Using only where
I tried add index to (option, deleted, date_added, category_id) but it works only when i try sort by one column.

It will be very hard to get MySQL to use an index for this query:
SELECT *
FROM articles
WHERE option <> 0 AND deleted = 0
ORDER BY date_added DESC
LIMIT 25 OFFSET 500
You can try a composite index: articles(deleted, date_added, option). By covering the WHERE and ORDER BY, MySQL might use it.
If you can add an optionflag column for equality testing (rather than <>), then write the query as:
SELECT *
FROM articles
WHERE optionflag = 1 AND deleted = 0
ORDER BY date_added DESC
LIMIT 25 OFFSET 500;
Then an index on articles(deleted, optionflag, date_added desc) would work well.
Otherwise a subquery might work for you:
SELECT a.*
FROM (SELECT *
FROM articles
WHERE deleted = 0
ORDER BY date_added DESC
) a
WHERE option <> 0
LIMIT 25 OFFSET 500;
This materializes the intermediate result, but it is doing an order by anyway. And, the final ordering is not guaranteed to be surfaced in the outer query, but it does work in practice (and is close to guaranteed because of the materialization).

Related

Order by views limiting, then order by another column

I have a query that selects * from my database ordering by views and limiting by 4:
SELECT * FROM articles WHERE visible = 1 ORDER BY views LIMIT 4;
But in the same query I want to find all other rows ordering by column updated_at.
I haved tryied things like this, but doesn't works:
(SELECT * FROM articles ORDER BY views DESC LIMIT 4)
UNION
(SELECT * FROM articles ORDER BY updated_at DESC);
The propose this are "pinning" the 4 hotest articles on home page and then ordering by time was updated.
Have any way to ORDER BY multiple ways in the same query without repeat the rows?
How can I do this?
Continuing with your current thinking, we can take a union of two subqueries. The first subquery is what you already included in your question, and finds the 4 more frequently viewed articles. The second subquery finds everything else. The trick here is to include in each subquery a computed field which we can use to keep track of the top 4 records from everything else. Then, we order by this computed field first, followed second by the updated_at field.
(
SELECT a.*, 1 AS label
FROM articles a
WHERE visible = 1
ORDER BY views DESC
LIMIT 4
)
UNION ALL
(
SELECT a.*, 2
FROM articles a
WHERE visible = 1
ORDER BY views DESC
LIMIT 1000000 OFFSET 4 -- the limit 1000000 is arbitrary; just use a number
) -- larger than the expected size of your table
ORDER BY
label, views, updated_at
From MySQL documentation:
... The default behavior for UNION is that duplicate rows are removed from the result. ...
And
... If ORDER BY appears without LIMIT in a SELECT, it is optimized away because it will have no effect anyway. ...
So the trick here is to use limit in the second query (it is up to you to choose the limit):
(SELECT * FROM articles WHERE visible = 1 ORDER BY views DESC LIMIT 4)
UNION
(SELECT * FROM articles WHERE visible = 1 ORDER BY updated_at DESC LIMIT 100);
The query was tested in MySQL 5.6 and 5.7.
You can use a comma to separate multiple ORDERcommands.
MySQL will order from left to right.
SELECT * FROM articles WHERE visible = 1 ORDER BY views, updated_at DESC LIMIT 4;

SQL queries optimization

I'm having trouble optimizing some sql queries that take in account datetime fields.
First of all, my table structure is the following:
CREATE TABLE info (
id int NOT NULL auto_increment,
name varchar(20),
infoId int,
shortInfoId int,
text varchar(255),
token varchar(60),
created_at DATETIME,
PRIMARY KEY(id)
KEY(created_at));
After using explain on some of the simple queries I added the created_at key, that improved most of my simple queries performance. I'm having now trouble with the following query:
SELECT min(created_at), max(created_at) from info order by id DESC limit 10000
With this query I want to get the timespan between tha last 10k results.
After using explain I get the following results:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE info ALL NULL NULL NULL NULL 4 NULL
Any idea on how can I improve the performance of this query?
If you want to examine the first 10k rows ordered by id then you need to use a sub-query to achieve your goal:
SELECT MIN(created_at), MAX(created_at)
FROM (
SELECT created_at
FROM info
ORDER BY id DESC
LIMIT 10000
) tenK
The inner query gets the first 10k rows from the table, sorted by id (only the field created_at is needed). The outer table computes the minimum and maximum value of created_at from the results set generated by the inner query.
I didn't run an EXPLAIN on it but I think it says 'Using temporary' in the 'Extra' column (which is not good but you cannot do better for this request). However, 10,000 rows is not that much; it runs fast and the performance does not degrade as the table size increases.
Update:
Now I noticed this sentence in the question:
With this query I want to get the timespan between tha last 10k results.
If you want to get the value of created_at of the most recent row and the row that is 10k rows in the past then you can use two simple queries that use the index on created_at and run fast:
(
SELECT created_at
FROM info
ORDER BY id DESC
LIMIT 1
)
UNION ALL
(
SELECT created_at
FROM info
ORDER BY id DESC
LIMIT 9999,1
)
ORDER BY created_at
This query produces 2 rows, the first one is the value of created_at of the 10000th row in the past, the second one is the created_at of the most recent row (I assume created_at always grows).
SELECT min(created_at), max(created_at) from info order by id DESC limit 10000
The above query will give you one row containing the minimum and maximum created_at values from info table. Because it only returns 1 row, the order by and limit clauses don't come into play.
The 10000-th record from the end can be accessed with the order by & limit condition ORDER BY id DESC LIMIT 1 OFFSET 9999 (thanks #Mörre Noseshine for the correction)
So, we can write the intended query as follows:
SELECT
min_created_at.value,
max_created_at.value
FROM
(SELECT
created_at value
FROM info
ORDER BY id DESC
LIMIT 1 OFFSET 9999) min_created_at,
(SELECT
created_at value
FROM info
ORDER BY id DESC
LIMIT 1) max_created_at

UNION ALL and ORDER BY at the same time

I have this query that does not work and I do not understand why.
Each SELECT statements should return descending results, but they're ordered ascendingly.
Why ?
(SELECT * FROM table WHERE deleted_at = 0 ORDER BY id DESC)
UNION ALL
(SELECT * FROM table WHERE deleted_at <> 0 ORDER BY id DESC)
LIMIT 0,30
I have to say, this query does not generates any error and the results are what I expect.
They are just not well ordered.
There is no guarantee of ordering when using subqueries. If you want the results ordered by id descending, then use:
(SELECT * FROM table WHERE deleted_at = 0)
UNION ALL
(SELECT * FROM table WHERE deleted_at <> 0)
order by id desc
LIMIT 0,30;
However, I think the query you really want is:
select *
from table
order by deleted_at = 0 desc, id desc
limit 0, 30;
This puts the deleted_at = 0 rows first and then fills out the data with the rest of the data.
Note: if deleted_at can be NULL and you want to filter them out too, then add a where clause for this filtering.
from the manual:
To apply ORDER BY or LIMIT to an individual SELECT, place the clause inside the parentheses that enclose the SELECT:
(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);
However, use of ORDER BY for individual SELECT statements implies nothing about the order in which the rows appear in the final result because UNION by default produces an unordered set of rows. Therefore, the use of ORDER BY in this context is typically in conjunction with LIMIT, so that it is used to determine the subset of the selected rows to retrieve for the SELECT, even though it does not necessarily affect the order of those rows in the final UNION result. If ORDER BY appears without LIMIT in a SELECT, it is optimized away because it will have no effect anyway.
If you need it sorted in total:
SELECT * FROM table WHERE deleted_at = 0
UNION ALL
SELECT * FROM table WHERE deleted_at <> 0
ORDER BY deleted_at = 0 DESC, id DESC
LIMIT 0,30
But this is of source the same as:
SELECT * FROM table
ORDER BY deleted_at = 0 DESC, id DESC
LIMIT 0,30
Because you apply the ORDER BY statement before the UNION ALL happens so on just one part of your data, you want to apply it on the whole result and it should be something like this :
SELECT * FROM table WHERE deleted_at = 0
UNION ALL
SELECT * FROM table WHERE deleted_at <> 0
ORDER BY id DESC
LIMIT 0,30

Select last N rows from MySQL

I want to select last 50 rows from MySQL database within column named id which is primary key. Goal is that the rows should be sorted by id in ASC order, that’s why this query isn’t working
SELECT
*
FROM
`table`
ORDER BY id DESC
LIMIT 50;
Also it’s remarkable that rows could be manipulated (deleted) and that’s why following query isn’t working either
SELECT
*
FROM
`table`
WHERE
id > ((SELECT
MAX(id)
FROM
chat) - 50)
ORDER BY id ASC;
Question: How is it possible to retrieve last N rows from MySQL database that can be manipulated and be in ASC order ?
You can do it with a sub-query:
SELECT * FROM
(
SELECT * FROM table ORDER BY id DESC LIMIT 50
) AS sub
ORDER BY id ASC;
This will select the last 50 rows from table, and then order them in ascending order.
SELECT * FROM table ORDER BY id DESC LIMIT 50
save resources make one query, there is no need to make nested queries
SELECT * FROM table ORDER BY id DESC, datechat DESC LIMIT 50
If you have a date field that is storing the date (and time) on which the chat was sent or any field that is filled with incrementally (order by DESC) or de-incrementally (order by ASC) data per row put it as second column on which the data should be ordered.
That's what worked for me!!!! Hope it will help!!!!
Use it to retrieve last n rows from mysql
Select * from tbl order by id desc limit 10;
use limit according to N value.
if anyone need this
you can change this into
SELECT
*
FROM
`table`
WHERE
id > ((SELECT
MAX(id)
FROM
chat) - 50)
ORDER BY id ASC;
into
SELECT
*
FROM
`table`
WHERE
id > (SELECT MAX(id)- 50 FROM chat)
ORDER BY id ASC;
select * from Table ORDER BY id LIMIT 30
Notes:
* id should be unique.
* You can control the numbers of rows returned by replacing the 30 in the query

Order by, put 0's at the end whilst maintaining the ascending search order

I have a query that uses ORDER BY ASC and as such 0's come up first. I would like them to come up last whilst still maintaining the ascending search order. How can I achieve this?
An example of this is:
SELECT product_price ORDER BY product_price ASC
So instead of
0
1
2
3
I would want
1
2
3
0
I don't have MySQL to test against, but this works in SQL Server and Advantage Database Server:
SELECT
product_price
ORDER BY
CASE product_price WHEN 0 then 99999999 ELSE product_price END
Replace the 99999999 series with the maximum value of the product_price column type.
SELECT product_price FROM tablename ORDER BY IF(product_price=0,4294967295,product_price) ASC
For the best performance, use a union.
(
SELECT
*
FROM
products
WHERE
product_price > 0
ORDER BY
product_price ASC
)
UNION
(
SELECT
*
FROM
products
WHERE
product_price = 0
)
The alternative, uses filesort which can be really slow when you have large tables.
EXPLAIN SELECT
*
FROM
products
ORDER BY
IF(product_price = 0, 1, 0) ASC
,product_price ASC
yeilds Using filesort. This means a temporary table is being written on disk. Very slow when you're dealing with large queries.