I have a single-column table Ids, which whose column ID is of type uniqueidentifier. I have another table MyTable which has an ID column as well as many other columns. I would like to delete rows from MyTable 1000 at a time, where the ID from MyTable matches an ID in Ids.
WHILE 1 = 1 BEGIN
DELETE t FROM (SELECT TOP 1000 ID FROM Ids) d INNER JOIN MyTable t ON d.ID = t.ID;
IF ##ROWCOUNT < 1 BREAK;
WAITFOR DELAY #sleeptime; -- some time to be determined later
END
This doesn't seem to work though. What should the statement actually be?
Try this:
DECLARE #BatchSize INT
SET #BatchSize = 100000
WHILE #BatchSize <> 0
BEGIN
DELETE TOP (#BatchSize) t
FROM [MyTable] t
INNER JOIN [Ids] d ON d.ID=t.ID
WHERE ????
SET #BatchSize = ##rowcount
END
Has the benefit that the only variable you need to create is the size, as it uses it for the WHILE loop check. When the delete gets below 100000, it will set the variable to that number, on the next pass there will be nothing to delete and the rowcount will be 0... and so you exit. Clean, simple, and easy to understand. Never use a CURSOR when WHILE will do the trick!
Try
Delete from MyTable
Where ID in
(select top 1000 t.ID
from Ids t inner
join MyTable d on d.Id = t.Id)
You could also try:
set rowcount 1000
delete from mytable where id in (select id from ids)
set rowcount 0 --reset it when you are done.
http://msdn.microsoft.com/en-us/library/ms188774.aspx
WHILE EXISTS (SELECT TOP 1 * FROM MyTable mt JOIN IDs i ON mt.ID = t.ID)
BEGIN
DELETE TOP (1000) FROM MyTable
FROM MyTable mt JOIN IDS i ON mt.ID = i.ID
--you can wait if you want, but probably not necessary
END
--Sorry for the quick post; was in a hurry :)
The DELETE statement in SQL Server supports two FROM clauses; the first FROM identifies the table that is having rows deleted, and the second FROM clause is used for JOINS.
See: http://msdn.microsoft.com/en-us/library/ms189835.aspx
Related
Yes, SQL UPDATE can be used with order by in a SELECT-subquery.
Anybody out there who has a workaround for following issue:
From time to time a program generates data errors in TABLE1 (we are not owner of that code but need to use the program...)
We use a trigger that protocols all changes to an AUDIT table.
We can find the error situation (and the correct old value) with following select:
select top 1 audit.OldValue
from TABLE1
left join AUDIT on AUDIT.Table1_ID = TABLE1.ID
where <...some conditions...>
order by AUDIT.UpdateDate desc
As there are several changes logged, we only need the LAST change (order by updatedate and then taking TOP 1)
We would correct the data error, if we could use the UPDATE command like
Update TABLE1
set VALUE =
( select top 1 audit.OldValue
from TABLE1
left join AUDIT on AUDIT.Table1_ID = TABLE1.ID
where <...some conditions...>
order by AUDIT.UpdateDate desc )
where TABLE1.ID = AUDIT.Table1_ID
BUT: you cannot use the order by in a subquery...
Instead of top 1 use ROW_NUMBER() and keep only rows where it is =1
something like:
Update t set Value = A.OldValue
FROM Table1 t
inner join (
select ROW_NUMBER() OVER (PARTITION BY UserCode ORDER BY MEASUREDATE DESC) N, Id Table1_ID, *
from AUDIT
) a on a.Table1_ID = t.ID
where <...some conditions...>
AND (a.N=1)
You will apply all updates to all records with only one update (pay attention to <...some conditions...>)
I changed the UPDATE-command to:
Update TABLE1
set VALUE = CORRECT_VALUES.OldValue
from
( select top 1 audit.OldValue OldValue, AUDIT.Table1_ID Table1_ID
from TABLE1
left join AUDIT on AUDIT.Table1_ID = TABLE1.ID
where <...some conditions...>
order by AUDIT.UpdateDate desc ) as CORRECT_VALUES
where TABLE1.ID = CORRECT_VALUES.Table1_ID
That works fine.
And as this only changes 1 entry (due to TOP 1) we have to use a loop:
Declare #i int = 0
Declare #NUMBER int = (select count(*) from
(select distinct AUDIT.Table1_ID
from TABLE1
left join AUDIT on AUDIT.Table1_ID = TABLE1.ID
where <...some conditions...>
) )
while #i < #NUMBER
BEGIN
SET #i = #i + 1
<... past the whole UPDATE command here ...>
END
Works fine for all records that have to be updated.
Be aware of the distict as there may be lots of changes for the same ID and you only need to change the record ones.
I have a table that its primary key used in multiple tables.
I want to delete the rows that have no relation FK in other tables.
How to delete all rows from table which has no FK relation?
First you can select like this:
select * from some_table where some_fk_column not in (
select some_column from second_table
)
if you get good result,then
delete from some_table where some_fk_column not in (
select some_column from second_table
)
If you want to do all without checking all other related tables I say a way but you should take care while using it:
loop through your table
delete the record, if any FK is exists then the record will not
delete (use TRY/CATCH blocks)
in this way you do not need to check all fk and tables
Notice: this way assumes that cascade delete is disabled.
Select *
Into #Tmp
From YOUR_TABLE
Declare #Id int
While EXISTS(SELECT * From #Tmp)
Begin
Select Top 1 #Id = Id From #Tmp
BEGIN TRY
DELETE FROM YOUR_TABLE WHERE ID=#ID
END TRY
BEGIN CATCH
END CATCH
Delete FROM #Tmp Where Id = #Id
End
It ain't pretty, but it should work:
select m.ID
from mastertable m
where not exists( select 1 from table1 where fk_id = m.ID )
and not exists( select 1 from table2 where fk_id = m.ID )
and not exists( select 1 from table3 where fk_id = m.ID )
and not exists( select 1 from table4 where fk_id = m.ID )
and so on;
You will do a table scan of the master table (you want to check every row) but as soon as any reference is found in any of the other tables, those scans halt and the master id is rejected. If the FK column in each of the others tables are indexed, those will be seeks. Anything that makes it all the way through is an ID value that is not referenced in any of the tables.
Now just feed the query above to a delete statement and you get rid of all unreferenced rows.
I need your advice to my below case.
I get data from maintable and insert into dataTable where rownum <= some value
once all data already insert into datatable, i want this data in maintable will update the staus.
The problem is if the rownum more than 500k, it take about 10 minutes. This time there could be another request was pickup the same data. How i want to prevent this?
Below is my sql.
insert into dataTable(id,num,status) select m.id,m.num,m.status from mainTable m where m.status = 'FREE' and rownum <= 100000;
update mainTable m set m.status = 'RESERVED' where m.num in (select d.num from dataTable where d.status = 'FREE');
I do some research, but i dont know whether i need to use the select for update or merge statement?
You can't use MERGE, as you can only insert into or update the target table. I would guess that the problem is either the selectivity of the column STATUS in dataTable or of the column NUM in mainTable.
Either way, if you only want to update those rows in mainTable that you've just inserted into mainTable the simplest thing to do would be to remember what you've just inserted and update that. A BULK COLLECT seems apposite.
declare
cursor c_all is
select rowid as rid, id, num, status
from maintable
where status = 'FREE'
and rownum <= 100000;
type t__all is table of c_all%rowtype index by binary_integer;
t_all t__all;
begin
open c_all;
loop
fetch c_all bulk collect into t_all limit 10000;
forall i in t_all.first .. t_all.last
insert into datatable (id, num, status)
values (t_all(i).id, t_all(i).num, t_all(i.status));
forall i in t_all.first .. t_all.last
update maintable
set status = 'RESERVED'
where rowid t_all(i).rid;
end loop;
commit;
close c_all;
end;
/
This is not equivalent to your query, it assumes that maintable is unique on NUM. If it unique on ID I would change the UPDATE to a MERGE (it's cleaner) and remove the ROWID column from the cursor:
forall i in t_all.first .. t_all.last
merge into maintable m
using ( select t_all(i).num from dual ) d
on ( m.num = d.num )
when matched then
update
set m.status = 'RESERVED'
As I've written though, if the problem is the selectivity of the columns/indexing you need to post the explain plan, indexes etc.
I think that it is better that you use EXISTS exchange of using in in your update query, it is so faster:
update mainTable m
set m.status = 'RESERVED'
where exists (select * from dataTable where m.num = d.num and d.status = 'FREE');
I am trying to do an update and i'm having problems (using microsoft sql server)
update mytable
set myvalue=
(
(select myvalue from someothertable
where someothertable.id=mytable.id)
)
from table mytable
where mytable.custname='test'
Basically the subquery could return no results if that does happen i want to call a different subquery:
(select myvalue from oldtable
where oldtable.id=mytable.id)
well, you could run the second query first, and then, the first query.
In that way, you will override the values only when the first query can bring them, and when the (original) first query won't bring any result, they will have the result of first query.
Also, I think you have a typo in your second query with the table name.
update mytable
set myvalue=
(
select myvalue from oldtable
where oldtable.id=mytable.id
)
from table mytable
where mytable.custname='test'
and exists (select 1 from oldtable
where oldtable.id=mytable.id)
update mytable
set myvalue=
(
select myvalue from someothertable
where someothertable.id=mytable.id
)
from table mytable
where mytable.custname='test'
and exists ( select 1 from someothertable
where someothertable.id=mytable.id)
edit: you will need to add the exists clause, cause if not it will update with null values, I think
You can simply join both tables,
UPDATE a
SET a.myValue = b.myValue
FROM myTable a
INNER JOIN someOtherTable b
ON a.ID = b.ID
WHERE a.CustName = 'test'
I'm trying to implement a batch delete.
I found this code on the internet:
DECLARE #rowcount int = 1
WHILE (#rowcount != 0 ) BEGIN
DELETE T1
FROM (SELECT TOP (50) * FROM Orders WHERE OrderCity = #city) T1
SELECT #rowcount = ##ROWCOUNT
END
the idea is to delete all orders from #city
It seems to work fine but on my reality, I need to delete from Orders where OrderCity in (select ID from SomeOtherTable)
If I try to do the same, it works but it takes a lot of time because SomeOtherTable will contain around 1.5 million rows and the data is being deleted from the main table, so it doesnt get any smaller (it does not contains cities, its another thing).
I also cant join both tables because it wont run saying that more than one table will be affected.
So basically my question is: Is there anyway to batch delete from tableA where tableA.ID IN (select ID from tableB)
Yes you can do it without join as:
DELETE tableA
FROM tableB
WHERE tableA.ID = tableB.ID
Delete Order
FROM
Order INNER JOIN SomeOtherTable ON Order.OrderCity = SomeOtherTable.ID
This could solve your problem
You should be able to delete based on a join. Try
DELETE FROM tableA
FROM tableA A
JOIN tableB B ON A.ID = B.ID
Also, if tableB has ~ a million rows, it would really help if you have an index on the ID column.