MySQL - Sort table efficiently when querying by another attribute - mysql

On this JS fiddle:
http://sqlfiddle.com/#!9/4c7bc0/7
CREATE TABLE big_table(id INTEGER AUTO_INCREMENT PRIMARY KEY,
user_id INTEGER NOT NULL);
The second query
SELECT * FROM big_table WHERE user_id IN ( 1, 100, 1000 )
ORDER BY id DESC LIMIT 10;
Is planned as: Using where; Using index; Using filesort
When big_table contains tens of millions of rows, the filesort kills the performance.
How do I ORDER BY faster?

For this query:
SELECT *
FROM big_table
WHERE user_id IN ( 1, 100, 1000 )
ORDER BY id DESC
LIMIT 10;
You cannot easily get rid of the work for the ORDER BY (there is one possible way, see below). Most importantly, you need an index on big_table(user_id):
CREATE INDEX idx_bigtable_userid ON big_table(user_id);
If there are only a few dozen or hundreds of rows that match, then this should be fine.
Another possibility is to rewrite the query, and use an index on:
CREATE INDEX idx_bigtable_userid ON big_table(user_id, id);
I should start by saying that I'm not 100% sure that this approach will work because of the DESC on id. But, the following query should use the index:
SELECT *
FROM big_table
WHERE user_id = 1
ORDER BY id DESC;
So you can do:
SELECT *
FROM ((SELECT t.*
FROM big_table t
WHERE t.user_id = 1
ORDER BY id DESC
LIMIT 10
) UNION ALL
(SELECT t.*
FROM big_table t
WHERE t.user_id = 100
ORDER BY id DESC
LIMIT 10
) UNION ALL
(SELECT t.*
FROM big_table t
WHERE t.user_id = 1000
ORDER BY id DESC
LIMIT 10
)
) t
ORDER BY id DESC
LIMIT 10;

Related

How to combine four select queries which select an equal number of rows randomly into one?

The following is my db table:
id category_id name
--------------------
1 4 A
2 5 B
3 6 C
I have four simple select queries which pull 15 rows by random from specific categories:
select * from table where category_id = 4 order by rand() limit 15;
select * from table where category_id = 5 order by rand() limit 15;
select * from table where category_id = 6 order by rand() limit 15;
select * from table where category_id = 7 order by rand() limit 15;
I want to combine them into a single query rather than four separate queries. I've tried using the UNION operator but it wasn't pulling 15 rows EQUALLY from each category:
(
select * from table where category_id = 4
union
select * from table where category_id = 5
union
select * from table where category_id = 6
union
select * from table where category_id = 7
) order by rand() limit 60;
How can I achieve this? Or, do I have to run separate queries?
I've tagged Laravel because I'm using Laravel as the backend and maybe Eloquent has a smarter way to achieve this.
Have you tried this one?:
(select * from table where category_id = 4 ORDER BY rand() LIMIT 15)
union all
(select * from table where category_id = 5 ORDER BY rand() LIMIT 15)
union all
(select * from table where category_id = 6 ORDER BY rand() LIMIT 15)
union all
(select * from table where category_id = 7 ORDER BY rand() LIMIT 15)
you could use CTE and ROW_NUMBER() as such
WITH CTE AS (
SELECT id,
category_id,
ROW_NUMBER() OVER(PARTITION BY category_id
ORDER BY RAND() DESC) AS rank
FROM table)
SELECT *
FROM CTE
WHERE rank <= 15 AND category_id IN (4,5,6,7)```

Avoid "filesort" on UNION RESULT

Sub-query 1:
SELECT * from big_table
where category = 'fruits' and name = 'apple'
order by yyyymmdd desc
Explain:
table | key | extra
big_table | name_yyyymmdd | using where
Looks great!
Sub-query 2:
SELECT * from big_table
where category = 'fruits' and (taste = 'sweet' or wildcard = '*')
order by yyyymmdd desc
Explain:
table | key | extra
big_table | category_yyyymmdd | using where
Looks great!
Now if I combine those with UNION:
SELECT * from big_table
where category = 'fruits' and name = 'apple'
UNION
SELECT * from big_table
where category = 'fruits' and (taste = 'sweet' or wildcard = '*')
Order by yyyymmdd desc
Explain:
table | key | extra
big_table | name | using index condition, using where
big_table | category | using index condition
UNION RESULT| NULL | using temporary; using filesort
Not so good, it uses filesort.
This is a trimmed down version of a more complexed query, here are some facts about the big_table:
big_table has 10M + rows
There are 5 unique "category"s
There are 5 unique "taste"s
There are about 10,000 unique "name"s
There are about 10,000 unique "yyyymmdd"s
I have created single index on each of those fields, plus composite idx such as yyyymmdd_category_taste_name but Mysql is not using it.
SELECT * FROM big_table
WHERE category = 'fruits'
AND ( name = 'apple'
OR taste = 'sweet'
OR wildcard = '*' )
ORDER BY yyyymmdd DESC
And have INDEX(catgory) or some index starting with category. However, if more than about 20% of the table is category = 'fruits' will probably decide to ignore the index and simply do a table scan. (Since you say there are only 5 categories, I suspect the optimizer will rightly eschew the index.)
Or this might be beneficial: INDEX(category, yyyymmdd), in this order.
The UNION had to do a sort (either in memory on disk, it is not clear) because it was unable to fetch the rows in the desired order.
A composite index INDEX(yyyymmdd, ...) might be used to avoid the 'filesort', but it won't use any columns after yyyymmdd.
When constructing a composite index, start with any WHERE columns compared '='. After that you can add one range or group by or order by. More details.
UNION is often a good choice for avoiding a slow OR, but in this case it would need three indexes
INDEX(category, name)
INDEX(category, taste)
INDEX(category, wildcard)
and adding yyyymmdd would not help unless you add a LIMIT.
And the query would be:
( SELECT * FROM big_table WHERE category = 'fruits' AND name = 'apple' )
UNION DISTINCT
( SELECT * FROM big_table WHERE category = 'fruits' AND taste = 'sweet' )
UNION DISTINCT
( SELECT * FROM big_table WHERE category = 'fruits' AND wildcard = '*' )
ORDER BY yyyymmdd DESC
Adding a limit would be even messier. First tack yyyymmdd on the end of each of the three composite indexes, then
( SELECT ... ORDER BY yyyymmdd DESC LIMIT 10 )
UNION DISTINCT
( SELECT ... ORDER BY yyyymmdd DESC LIMIT 10 )
UNION DISTINCT
( SELECT ... ORDER BY yyyymmdd DESC LIMIT 10 )
ORDER BY yyyymmdd DESC LIMIT 10
Adding an OFFSET would be even worse.
Two other techniques -- "covering" index and "lazy lookup" might help, but I doubt it.
Yet another technique is to put all the words in the same column and use a FULLTEXT index. But this may be problematical for several reasons.
This must be also work without UNION
SELECT * from big_table
where
( category = 'fruits' and name = 'apple' )
OR
( category = 'fruits' and (taste = 'sweet' or wildcard = '*')
ORDER BY yyyymmdd desc;

Order mysql query in the same order I provide the OR statements in

Here's a query:
SELECT *
FROM table
WHERE id = 1
OR id = 100
OR id = 50
Note that I provided the ids in this order: 1,100,50.
I want the rows to come back in that order: 1,100,50.
Currently, i comes back 1,50,100 - basically in ascending order. Assume the rows in the table were inserted in ascending order also.
Use the MySQL specific FIND_IN_SET function:
SELECT t.*
FROM table t
WHERE t.id IN (1, 100, 50)
ORDER BY FIND_IN_SET(CAST(t.id AS VARCHAR(8)), '1,100,50')
Another way to approach this would put the list in a subquery:
select table.*
from table join
(select 1 as id, 1 as ordering union all
select 100 as id, 2 as ordering union all
select 50 as id, 3 as ordering
) list
on table.id = list.id
order by list.ordering
You can just do this with ORDER BY:
ORDER BY
id = 1 DESC, id = 100 DESC, id = 50 DESC
0 is before 1 in ORDER BY.
Try this
SELECT *
FROM new
WHERE ID =1
OR ID =100
OR ID =50
ORDER BY ID=1 DESC,ID=100 DESC,ID=50 DESC ;
http://www.sqlfiddle.com/#!2/796e2/5
... WHERE id IN (x,y,x) ORDER BY FIELD (id,x,y,z)

Retrieving target record & and surrounding n records in one query?

I want to be able to target one record, and get back that record, and a variable amount of records on each side of it.
Like say you have a key id of 2356. So you need 2356 and the 3 records before and after 2356, with an order by on create_ts (for example).
I am wondering if there is a way to do this in one query (mysql)?
The table (call it mytable) has to have a compound index on create_ts and id
ALTER TABLE mytable ADD INDEX create_ts_id_index (create_ts,id);
You evidently need two queries to pick up needed keys: 3 before id, the id itself, 3 after id.
That's 7 ids.
This query picks up 4 keys (id + 3 after)
SELECT create_ts,id FROM mytable WHERE id >= 2356 ORDER BY create_ts LIMIT 4;
This query picks up 4 keys (id + 3 before)
SELECT create_ts,id FROM mytable WHERE id < 2356 ORDER BY create_ts DESC LIMIT 4;
A UNION of the two queries should eliminate a duplicate of id 2356
Let's combine these and perform an INNER JOIN of it to mytable
SELECT B.* FROM
(
SELECT * FROM
(SELECT create_ts,id FROM mytable
WHERE id >= 2356 ORDER BY create_ts
LIMIT 4) AA
UNION
(SELECT create_ts,id FROM mytable
WHERE id <= 2356 ORDER BY create_ts DESC
LIMIT 4)
) A
INNER JOIN mytable B USING (create_ts,id)
ORDER BY A.create_ts,A.id;
The subquery A should only have 7 keys. Once those 7 keys are retrieved, the INNER JOIN should be quick.
Whenever you need N keys before and N keys after for this query, just use LIMIT N+1.
I would suggest this method because we can neither assume that 3 keys back is id-3, nor assume 3 keys after is id+3. This is especially true if the table has gaps in the ids for any reason.
Give it a Try !!!
CAVEAT : I did not try it out. This is what you want algorithmically. The MySQL syntax may or may not allow for it. It the syntax is not right, I'll try to construct an example and fix the syntax.
Just in case, here is a more stable approach.
CREATE TABLE create_ts_ids SELECT create_ts,id FROM mytable WHERE 1=2;
ALTER TABLE create_ts_ids ADD PRIMARY KEY (id);
INSERT INTO create_ts_ids
SELECT create_ts,id FROM mytable
WHERE id >= 2356 ORDER BY create_ts LIMIT 4;
INSERT IGNORE INTO create_ts_ids
SELECT create_ts,id FROM mytable
WHERE id <= 2356 ORDER BY create_ts DESC LIMIT 4;
SELECT B.* FROM create_ts_ids A
INNER JOIN mytable B USING (create_ts,id)
ORDER BY A.create_ts,A.id;
If your criteria for selection is simply the id:
SELECT *
FROM table
WHERE key_id <= target_id + 3
AND key_id >= target_id - 3
ORDER BY create_ts;
Replace 3 with number of records desired around the target record.

How to delete all rows except the 50 newest

how i can delete all rows in table recentposts
DELETE FROM recentposts WHERE recentposts.`userId` = 12 AND recentposts.`Id`
NOT IN (SELECT * FROM recentposts WHERE `userId` = 12 ORDER BY viewdate LIMIT 50)
i try many similar to this but they not worked. can someone tell me how i can do this in Mysql.
What about this?
DELETE FROM recentposts
WHERE
recentposts.`userId` = 12
AND
recentposts.`Id` NOT IN (SELECT Id
FROM recentposts
WHERE `userId` = 12
ORDER BY viewdate DESC LIMIT 50)
DELETE FROM `recentpost`
WHERE WHERE userId = 12 AND id NOT IN (
SELECT id
FROM (
SELECT id
FROM `table`
ORDER BY id DESC
LIMIT 50
) foo
);
PS: only working (MySQL 5.0.67 and upper) AS in earlier versions These are limitations of MySQL.
you can't DELETE and SELECT from a given table in the same query.
MySQL does not support LIMIT in a subquery.
so for previous version u can come up with is to do this in two stages:
first step
SELECT id FROM mytable ORDER BY id DESC LIMIT n;
Collect the id's and make them into a comma-separated string:
DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );