My table has a TIME field.
I want to keep only 5 newest rows.
Can I delete the old rows without using SELECT?
I think logic should be something like this:
DELETE FROM tbl WHERE row_num > 5 ORDER BY TIME
How can I implement this in MySQL whitout using SELECT to get list of TIME values?
Without proper ORDER BY clause, SQL result set have to be considered as unordered.
You have to provide a column to explicitly store your rows sequence numbers. This could be a time stamp or the auto_increment column of your table.
Please keep in mind you could have concurrent access to your table as well. What should be the expected behavior if someone else is inserting while you are deleting? As far as I can tell this could lead to situation where you keep only the "5 latest rows" + "those inserted on the other transaction".
If your have the time column for that purpose on your table and a PRIMARY KEY (or some other UNIQUE NOT NULL column) you could write:
DELETE tbl FROM tbl LEFT JOIN (SELECT * FROM tbl ORDER BY tm DESC LIMIT 5) AS k
ON (tbl.pk) = (k.pk)
WHERE k.`time` IS NULL;
If you have composite primary key (a,b) You could write:
DELETE tbl FROM tbl LEFT JOIN (SELECT * FROM tbl ORDER BY tm DESC LIMIT 5) AS k
ON (tbl.a,tbl.b) = (k.a,k.b)
WHERE k.tm IS NULL;
DELETE FROM TBL
WHERE ROW_NUM = (SELECT ROW_NUM FROM TBL LIMIT 6, 99999)
ORDER BY TIME DESC;
This will delete records from 6, 7, 8, 9, 10, ....., 200005
Because LIMIT range starts here from 6 to 9999 records, means 200005
Maybe this would be an alternative:
DELETE FROM tbl
WHERE primary_key NOT IN (SELECT primary_key
FROM tbl
ORDER BY time
DESC LIMIT 5)
If you want to exclude the top 5 rows, use something like:
DELETE FROM table WHERE primary_key IN
(SELECT primary_key FROM table LIMIT 1 OFFSET 5,1000000)
100000 can be a very large no
Related
Id like to run a query only if a value in the last row is correct. In my exemple if the value in ColumnA is 1 on the last row then i want to run MyQuery. But if the value is not 1 stop there and do nothing.
i've try with case and count(*) and also with If exists. but i keep getting error
SELECT CASE WHEN ((SELECT COUNT(*) FROM
(Select a.* from table as a order by a.index desc limit 1) as b
where b.ColumnA = 1)) > 0 )
THEN (MyQuery)
END
i've also try with if exists but it doesn'work either
if exists Select b.* from (Select a.* from table as a order by a.index desc limit 1) where b.ColumnA = 1
begin
(MyQuery)
end
can you point me what wrong in those query or maybee there's a better way to achive this.
EDIT. This query will be run on a trigger after each insert in that table the goal is to avoid running MyQuery on row that dont required it. MyQuery is a bit slow and most row dont required it to run.
I think we can rephrase your logic here to make it work as you want:
WITH cte AS (
SELECT ColumnA, ROW_NUMBER() OVER (ORDER BY index DESC) rn
FROM yourTable
)
(your query here)
WHERE (SELECT ColumnA FROM cte WHERE rn = 1) = 1;
The WHERE clause above would return either true or false, and would apply to all records in the potential result set from your query. That is, if the ColumnA value from the "last" record were 1, then you would get back the entire result set, otherwise it would be empty set.
Assuming your version of MariaDB supports neither ROW_NUMBER nor CTEs, then use:
(your query here)
WHERE (SELECT ColumnA FROM yourTable ORDER BY index DESC LIMIT 1) = 1;
It depends on what your query is.
INSERT ...
SELECT ... WHERE ... -- this could lead to zero rows being inserted
DELETE ...
WHERE NOT EXISTS ( SELECT ... ) -- this could lead to zero rows being deleted
UPDATE t1 JOIN t2 ... -- the JOIN may cause no rows to be updated
Note:
(Select a.* from table as a order by a.index desc limit 1) as b
where b.ColumnA = 1)) > 0 )
can be simplified (and sped up) to
( ( SELECT ColumnA FROM table ORDER BY index DESC LIMIT 1 ) = 1 )
Note that that is a true/false "expression", so it can be used in various places.
I have a table :
ID | time
1 | 300
1 | 100
1 | 200
2 | 200
2 | 500
I want to get 2nd row for every ID
I know that I can get 1st row as
select ID,time from T group by ID;
But I don't know about how to get 2nd row for every ID.
I know about limit and offset clause in mysql, but can't figure out how to use them here.
How can I do it ?
EDIT : Actually, time is not ordered. I forgot to specify that. I have made an edit in the table.
i have just an idee how to make it but i couldnt fix it , maybe you can fix it. any suggest is appreciated to correct my query
first this to select the first row of each id.
SELECT min(id) id
FROM TableName t2
group by id
then select the min(id) which are not in the first query to select to min(id) (which is second row)
like that
SELECT min(id) id ,time
FROM TableName
WHERE id NOT IN (
SELECT min(id) id
FROM TableName
GROUP BY id
)
GROUP BY id
** as i said its just suggest . it returns me 0 values.if u fix it let me edit my post to be helpful
here a demo
SELECT ID, MAX(time) time
FROM
(
select ID, Time
from TableName a
where
(
select count(*)
from TableName as f
where f.ID = a.ID and f.time <= a.time
) <= 2
) s
GROUP BY ID
SQLFiddle Demo
SELECT x.*
FROM test x
JOIN test y
ON y.id = x.id
AND y.time >= x.time
GROUP
BY id,time
HAVING COUNT(*) = n;
Note that any entries with less than n results will be omitted
You cannot do this with the tables that you have. You could make a valiant attempt with:
select id, time
from (select id, time
from t
group by t
) t
where not exists (select 1 from t t2 where t2.id = t.id and t2.time = t.time)
group by id
That is, attempt to filter out the first row.
The reason this is not possible is because tables are inherently unordered, so there is not real definition of "second" in your tables. This gives the SQL engine the opportunity to rearrange the rows as it sees fit during processing -- which can result in great performance gains.
Even the construct that you are using:
select id, time
from t
group by id
is not guaranteed to return time from the first row. This is a (mis)feature of MySQL called Hidden Columns. It is really only intended for the case where all the values are the same. I will admit that in practice it seems to get the value from the first row, but you cannot guarantee that.
Probably your best solution is to select the data into a new table that has an auto-incrementing column:
create table newtable (
autoid int auto_increment,
id int,
time int
);
insert into newtable(id, time)
select id, time from t;
In practice, this will probably keep the same order as the original table, and you can then use the autoid to get the second row. I want to emphasize, though, the "in practice". There is no guarantee that the values are in the correct order, but they probably will be.
I have a query that contains several conditions to extract data from a table of 5 million rows. A composite index has been built to partially cover some of these conditions to the extend that I am not able to cover the sorting with an index:
SELECT columns FROM Table WHERE conditions='conditions' ORDER BY id DESC LIMIT N;
The id itself is an auto-increment column. The above query can be very slow (4-5s) as filesort is being used. By removing the ORDER BY clause, I am able to speed up the query by up to 4 times. However the data extracted will be mostly old data.
Since post-processing can be carried out to sort the extracted data, I am more interested in extracting data from roughly the latest N rows from the resultset. My question is, is there a way to do something like this:
SELECT columns FROM Table WHERE conditions='conditions' LIMIT -N;
Since I do not really need a sort and I know that there is very high likelihood that the bottom N rows contain newer data.
Here you go. Keep in mind that there should be no problem in using ORDER BY with any indexed columns, including id.
SET #seq:=0;
SELECT `id`
FROM (
SELECT #seq := #seq +1 AS `seq` , `id`
FROM `Table`
WHERE `condition` = 'whatever'
)t1
WHERE t1.seq
BETWEEN (
(
SELECT COUNT( * )
FROM `Table`
WHERE `condition` = 'whatever'
) -49
)
AND (
SELECT COUNT( * )
FROM `Table`
WHERE `condition` = 'whatever'
);
You can replace the "-49" with an expression like: -1 * ($quantity_desired -1);
Also check out this answer as it might help you:
https://stackoverflow.com/a/725439/631764
And here's another one:
https://stackoverflow.com/a/1441164/631764
Grab the last "few" rows using a between:
SELECT columns
FROM Table
WHERE conditions = 'conditions'
AND id between (select max(id) from table) - 50 AND (select max(id) from table)
ORDER BY id
DESC LIMIT N;
This example gets the last 50 rows, but the id index will be used efficiently. The other conditions and ordering will then be only over 50 rows. Should work a treat.
I just need to know if a query returns or not a record.
Of course I can do this:
SELECT COUNT(*) FROM tbl WHERE conds;
But this returns the exact number of rows (of course), and I don't need this overhead.
So I thought this query:
SELECT COUNT(*) FROM (SELECT id FROM tbl WHERE conds LIMIT 1) as t1
Limiting the internal query to 1.
Is this faster? Or considering I am doing a subquery it cancels the benefits of LIMIT 1?
Note: for everyone asking theirself, I can't apply LIMIT 1 to the first query because it doens't work
The inner-select in the second query is redundant.
If you just want to check at-least of one row :-
SELECT 1 FROM tbl // return 1
WHERE conds // depends on your index and query
ORDER BY NULL // avoid file-sort
LIMIT 1; // minimum row
Why not just:
SELECT 1 FROM tbl WHERE conds LIMIT 1
You could do:
SELECT 1 WHERE EXISTS(SELECT id FROM tbl WHERE CONDITION)
Or something like:
SELECT 1 WHERE EXISTS (SELECT id FROM tbl WHERE id IN( 1000, 1001))
SELECT id FROM table LIMIT 8, 3
results in 8,9,10
but I need 10,9,8
How can you do this? If you add "ORDER BY id DESC" it gets 3,2,1
Put your query in a subselect and then reverse the order in the outer select:
SELECT id from (
SELECT id FROM table ORDER BY id LIMIT 8, 3
) AS T1 ORDER BY id DESC
Test data:
CREATE TABLE table1 (id INT NOT NULL);
INSERT INTO table1 (id) VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11);
SELECT id from (
SELECT id FROM table1 ORDER BY id LIMIT 8, 3
) AS T1 ORDER BY id DESC
Result:
10
9
8
Note that the ORDER BY in the subquery is required otherwise the order is undefined. Thanks to Lasse for pointing this out!
First of all, if you're not ordering at all, that you got 8,9,10 right now might be related to the index used. Are you sure this isn't going to change in the future?
What I'm getting at is that unless you specify an order, you should not rely on it being the one you want.
So I would definitely add an order to that select to specify what you want. Otherwise you're only saying "give me 3 numbers from this table", not "give me 3 numbers from this table according to these rules". Why is 3,2,1 wrong but 8,9,10 right? You're not specifying.
Anyway, to answer your question, you must order after the limit, which means a subselect:
SELECT id FROM (
SELECT id FROM table LIMIT 8, 3
) AS dummy ORDER BY id DESC
However, I would try this SQL instead, related to the part about specifying:
SELECT id FROM (
SELECT id FROM table ORDER BY id LIMIT 8, 3
) AS dummy ORDER BY id DESC