08. 刪除重復&海量數據


重復數據,通常有兩種:一是完全重復的記錄,也就是所有字段的值都一樣;二是部分字段值重復的記錄。

一. 刪除完全重復的記錄
完全重復的數據,通常是由於沒有設置主鍵/唯一鍵約束導致的。
測試數據:

if OBJECT_ID('duplicate_all') is not null
drop table duplicate_all
GO
create table duplicate_all
(
c1 int,
c2 int,
c3 varchar(100)
)
GO
insert into duplicate_all
select 1,100,'aaa' union all
select 1,100,'aaa' union all
select 1,100,'aaa' union all
select 1,100,'aaa' union all
select 1,100,'aaa' union all
select 2,200,'bbb' union all
select 3,300,'ccc' union all
select 4,400,'ddd' union all
select 5,500,'eee'
GO

 

(1) 借助臨時表

利用DISTINCT得到單條記錄,刪除源數據,然后導回不重復記錄。

if OBJECT_ID('tempdb..#tmp') is not null
drop table #tmp
GO
select distinct * into #tmp
from duplicate_all
where c1 = 1
GO
delete duplicate_all where c1 = 1
GO
insert into duplicate_all 
select * from #tmp

如果表不大的話,可以把所有記錄導出一次,然后truncate表后再用distinct導回,這樣可以避免delete的日志操作。

 

(2) 使用ROW_NUMBER

with tmp
as
(
select *,ROW_NUMBER() OVER(PARTITION BY c1,c2,c3 ORDER BY(getdate())) as num
from duplicate_all
where c1 = 1
)
delete tmp where num > 1

如果多個表有完全重復的行,可以考慮通過UNION將多個表聯合,插到一個新的同結構的表,SQL Server會幫助去掉表和表之間的重復行。

 

二. 刪除部分重復的記錄
部分列重復的數據,通常表上是有主鍵/唯一鍵約束的,可能是程序邏輯造成某些非主鍵/唯一鍵列值的數據重復。
測試數據:

if OBJECT_ID('duplicate_col') is not null
drop table duplicate_col
GO
create table duplicate_col
(
c1 int primary key,
c2 int,
c3 varchar(100)
)
GO
insert into duplicate_col
select 1,100,'aaa' union all
select 2,100,'aaa' union all
select 3,100,'aaa' union all
select 4,100,'aaa' union all
select 5,500,'eee'
GO

 

(1) 唯一索引

唯一索引有個忽略重復建的選項,在創建主鍵約束/唯一鍵約束時都可以使用這個索引選項。

if OBJECT_ID('tmp') is not null
drop table tmp
GO
create table tmp
(
c1 int,
c2 int,
c3 varchar(100),
constraint UQ_01 unique(c2,c3) with(IGNORE_DUP_KEY = ON)
)
GO
insert into tmp 
select * from duplicate_col
select * from tmp

 

(2) 借助主鍵/唯一鍵來刪除
通常會選擇保留主鍵/唯一鍵的最大/最小值,其他行刪除。以下只保留重復記錄中c1最小的行:

delete from duplicate_col 
where exists(select 1 from duplicate_col b where duplicate_col.c1 > b.c1 and (duplicate_col.c2 = b.c2 and duplicate_col.c3 = b.c3))
--或者
delete from duplicate_col 
where c1 not in (select min(c1) from duplicate_col group by c2,c3)

如果要保留重復記錄中的第N行,可以參考05. 取SQL分組中的某幾行數據

 

(3) ROW_NUMBER
和刪除完全重復記錄的寫法基本一樣。

with tmp
as
(
select *,ROW_NUMBER() OVER(PARTITION BY c2,c3 ORDER BY(getdate())) as num
from duplicate_col
)
delete tmp where num > 1
select * from duplicate_col

 

三. 刪除海量數據
刪除海量數據時,如果想要提升性能,需要考慮的一個重要因素就是:如何減少日志操作?

1. 全表刪除
全表刪除的方式通常有3種:DROP, TRUNCATE, DELETE
(1) DROP/TRUNCATE
DROP和TRUNCATE是DDL操作,日志量都很少(只有回收數據頁的記錄,不記錄頁內每條數據的明細),都釋放所有數據頁,以及重置IAM、PFS、GAM、SGAM中的標志位,釋放的數據頁可被其他表使用;

所不同的是,DROP同時也刪除了系統目錄里對於表的定義,相應的,表上所有定義的對象:INDEX、CONSTRAINT、TRIGGER等等也都將被刪除,該表相關的IAM、PFS、GAM、SGAM頁也將被釋放(不只是重置標志位);

(2) DELETE
不帶條件的DELETE可以用來刪除全表數據,所有被刪除的行都將被記錄日志,做全表刪除時效率較差,不推薦;

 

2. 部分刪除
對於表中部分數據做刪除,如果是分區表的話,直接TRUNCATE分區是最好了,即使是用DELETE刪除分區中部分數據,效率也不會太差;

如果不是分區表的話:

(1) 刪除表里少部分數據
直接用DELETE刪除; 

(2) 刪除表里大部分數據
導出所需要保留的少數記錄到臨時表,然后TRUNCATE原表,再把臨時表數據導回來;
舉例:

SELECT * INTO tmp FROM TAB_NAME 
WHERE DATE_COL > = GETDATE()-1

TRUNCATE TABLE TAB_NAME

INSERT INTO TAB_NAME 
SELECT * FROM tmp

DROP TABLE tmp

如果不想再把臨時表數據導回來,也可直接刪除原表TAB_NAME,把tmp重命名為原表名,但不要忘了在tmp上創建原表的對象,如:索引/約束/觸發器等等。

EXEC sp_rename 'TAB_NAME', 'TAB_NAME_OLD'
EXEC sp_rename 'tmp', 'TAB_NAME'
--create index/constraint/trigger...on new TAB_NAME
DROP TABLE TAB_NAME_OLD

 (3) 刪除表里約一半數據

這時,如果表上沒有分區的話,就會慢的特別明顯。

不過,假設從n億數據中刪除一半數據:相比直接delete,還是情願選擇select into導出需要的數據,然后刪除原表+rename新表來得快;

因為select into一半的數據,比delete一半的數據,通常是要快的,select into是最小化日志的,而且到了2014插入操作也可以並行了;

 

另外,ORACLE中的NOLOGGING選項,類似於SQL Server中的BULK_LOGGED恢復模式,在批量數據操作時才有效,比如:SELECT…INTO(ORACLE中對應create table as select * from…),CREATE/ALTER INDEX 等等。並不是任何時候這個選項都有效的。

 

 小結

(1) 海量數據的刪除,盡量選擇日志量較小的方式進行;
(2) NOLOGGING選項/BULK_LOGGED恢復模式,在刪除數據時,派不上用場,通常用在批量導入或更新數據時。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM