Tip & Tech

데이터베이스 를 관리하다보면 중복데이터가 만들어 지는경우가 상당히 많다. 정상적으로 들어가 있지만 중복제거된 데이터를 만들어야 하거나 외부데이터를 임포트 하는 과정에서 중복 데이터가 입력되는 경우도 자주 있다. 이런 경우 중복 데이터 제거 방법에 대해 알아보자


필요 칼럼들 모두에 대한 중복제거 데이터가 필요한 경우


 
Select distinct col1, col2, col3 from test;
 

distinct 구문으로 간단하게 필요한 데이터를 뽑아올 수 있다.


특정 칼럼에 대해서만 중복 제거가 필요할 경우 

sql server 2005 이상 버전

Col1 칼럼에 대해 중복 제거가 필요한대 col2 의 값이 가장 작은것을 남기고자 할때, 예를 들어 한로우에 state, city, zipcode 정보가 있을때 각 도시별로 가장 작은 zipcode 만 남기고 싶을때

 
SELECT state, city, zipcode

FROM (

SELECT state, city, zipcode, row_number() over(partition by state, city order by state, city, zipcode) rnum FROM test

) a

WHERE rnum=1;

 


sql server 2000 이하 버전

col1 칼럼에 대해 중복 제거가 필요한대 col2 의 길이가 가장 긴것을 남기고자 할때

 
SELECT * into test_distinct FROM test where 1=2; -- 빈 테이블 복사생성

CREATE UNIQUE INDEX unique_idx ON test_distinct (col1) WITH IGNORE_DUP_KEY; --중복제거를 위한 인덱스 생성

INSERT INTO test_distinct (col1, col2, col3)

SELECT col1, col2, col3 FROM test ORDER BY col1, len(col2) DESC;
 

위와같이 실행할경우 각  col1 별로 col2 가 가장긴 레코드만 남고 나머지는 중복제거 된다.



현재 테이블을 교체할 수 없고 테이블 내에서 중복 데이터의 삭제가 필요할 경우

sql server 2005 이상 버전

 
WITH test_cte
As
(
Select col1, col2, row_number() over (partition by col1 order by col2) rnum
)

Delete test_cte where rnum>1;
 

sql server 2000 이하 버전

col1 별로 최소값을 갖는 col2 행만 남기고 나머지 행들을 삭제하는 방법 1. (삭제전 select 쿼리로 삭제될 데이터를 확인할것)

 
DELETE test

FROM test a 

	INNER JOIN 

(SELECT col1, MIN(col2) min_col2 FROM test GROUP BY col1) b ON a.col1=b.col1 and a.col2<>b.min_col2;
 


col1 별로 최소값을 갖는 col2 행만 남기고 나머지 행들을 삭제하는 방법 2. (삭제전 select 쿼리로 삭제될 데이터를 확인할것)

 
DELETE a 

FROM test a, test b WHERE a.col2 > b.col2 AND b.col1=a.col1
 


위와 반대로 col1 별로 최대값을 갖는 col2 행만 남기고 나머지 행들을 삭제하는 방법. (삭제전 select 쿼리로 삭제될 데이터를 확인할것)

 
DELETE a 

FROM test a, test b WHERE a.col2 < b.col2 AND b.col1=a.col1
 



Select 결과를 이용하여 테이블 업데이트 하는 방법 (조인 업데이트)
데이터베이스 관리나 개발중에 다른 테이블의 데이터를 조인하여 데이터를 업데이트해야 하는 경우가 매우 많다.
만약 하나의 칼럼을 업데이트 하는 경우라면 서브쿼리를 사용할수도 있을것이다. 하지만 성능에 매우 좋지 않은 경우가 많다.
또한 업데이트 할 칼럼이 많을경우 쿼리문이 너무 길어지고 인덱스가 제대로 잡혀있지 않을경우 서버에도 부담이 되는 경우가 많다.
이런 상황을위해 SQL Server 에서 제공하는 조인 업데이트 방법이 있다. 구문 또한 select join 이나 마찬가지로 단순하며 조인에서 사용할수 있는 많은 실행계획을 사용하여 최적의 업데이트 성능을 확보할수 있게 된다.


테스트 테이블 생성
 
create table table_a(user_id varchar(100), name varchar(10));
insert into table_a(user_id, name) values('user1','name1')
insert into table_a(user_id, name) values('user2','name2')
insert into table_a(user_id, name) values('user3','name3')

create table table_b(user_id varchar(100), city varchar(100));
insert into table_b(user_id, city) values('user1','busan')
insert into table_b(user_id, city) values('user2','deojeon')
insert into table_b(user_id, city) values('user3','ulsan')

alter table table_a add city varchar(100);
 

일반적인 구문의 예

 
--alias 를 사용해서
UPDATE a
SET 
	city=b.city
FROM 
	table_a a 
	INNER JOIN
	table_b b ON a.user_id=b.user_id
WHERE user_id='user1'

--alias 없이
UPDATE table_a
SET 
	city=table_b.city
FROM 
	table_a 
	INNER JOIN 
	table_b ON table_a.user_id=table_b.user_id
WHERE
	user_id='user1';

--from 절에 join 쿼리 없이
UPDATE table_a
SET 
	city = b.city 
FROM (
    SELECT user_id, city
    FROM table_b WHERE user_id='user1') b
WHERE 
    table_a.user_id = b.user_id

MERGE INTO 구문 예

 --기본적인 merge into 구문을 이용한 업데이트
MERGE INTO table_a a
   USING table_b b
      ON a.user_id = b.user_id
         AND a.user_id = 'user1'
WHEN MATCHED THEN
   UPDATE 
      SET city = b.city;

--상대 테이블의 집계가 필요한 경우
MERGE INTO table_a a
   USING (
          SELECT user_id, city
            FROM table_b
           WHERE user_id = 'user2'
         ) b
      ON a.user_id = b.user_id
WHEN MATCHED THEN
   UPDATE 
      SET city = b.city;
 

CTE 구문 예

 
WITH test_cte
     AS (SELECT a.city,
                b.city AS bcity
         FROM   table_a a
                JOIN table_b b
                  ON a.user_id = b.user_id
       )

UPDATE test_cte
SET    city = bcity
WHERE city is null;
 


데이터베이스를 관리하다보면 기존테이블에 디폴트 값을 할당하거나 새로운 칼럼울 추가하면서 디폴트값을 지정해야 하는경우가 있다. 하지만 default 제약조건을 추가하는 경우가 많지 않아 구문을 외우는게 쉽지 않다. 근 20년을 SQL Server 를 관리 하지만 아직도 default 제약 조건을 추가할때는 구문을 찾아보고 하고있다.
Default 제약조건을 추가하는 여러가지 방법에 대해 알아보자


구문

 
ALTER TABLE {TABLENAME} ADD {COLUMNNAME} {TYPE} {NULL|NOT NULL} CONSTRAINT {CONSTRAINT_NAME} DEFAULT {DEFAULT_VALUE} WITH VALUES
 


실제 적용예

칼럼을 추가하며 default 값을 지정할때

 
ALTER TABLE test ADD col1 varchar(20) NULL --또는 NOT NULL. 

CONSTRAINT dft_test_col1 -- 이름을 생략하면 자동으로 생성된다. 

DEFAULT ('not checked') -- default 값 지정 옵션

WITH VALUES -- 기존에 입력된 데이터에 default 값을 업데이트 하고자 하는 경우.
 
  • with values 를 지정하면 칼럼 설정이 nullable 이든 not nullable 이든 기존 레코드에도 default 값을 업데이트한다
  • 추가 칼럼이 nullable 이고 with values 를 지정하지 않으면 기존 레코드에는 영향이 없고 default 제약조건 추가후 입력되는 레코드에 대해서만 default 값이 할당된다.
  • 추가칼럼이 not nullable 일경우 with values 설정을 지정하지 않아도 기존 레코드에도 default 값으로 업데이트 한다.
대용량 테이블의경우 with values 를 칼럼생성과 동시에 지정해야 하는지는 꼭 확인해야 한다. 실제 테이블의 데이터를 변경해야 하는 과정이 포함되기 때문에 해당 테이블에 락이 걸려 사용하지 못하는 경우가 생길수 있다.

나 같은 경우 아래와 같은 방법을 사용한다.

 
ALTER TABLE test ADD col1 varchar(20) NULL

CONSTRAINT dft_test_col1

DEFAULT ('not checked');


WHILE 1=1
BEGIN
	UPDATE top (3000) test SET col1='gg' WHERE col1 is null
	IF @@rowcount > 0 CONTINUE
	ELSE BREAK
END;


ALTER TABLE test ALTER COLUMN col1 varchar(20) NOT NULL;
 



Default 제약조건에 이름을 부여하지 않고 가장 단순하게 default 값을 지정할때

 
ALTER TABLE test ADD col1 int NOT NULL DEFAULT(1) 
 



이미 생성돼 있는 칼럼에 default 값을 지정할때

 
ALTER TABLE test ADD col3 datetime null;

ALTER TABLE test ADD CONSTRAINT df_col3_getdate DEFAULT getdate() FOR col3;
 



기존에 default 값을 가지고 있는 칼럼의 default 칼럼을 변경하고자 할때

ALTER TABLE test ADD col3 datetime null DEFAULT(1) 이와 같은 구문울 보면 default 절이 칼럼의 정의 인것처럼 보이지만 default 는 사실 제약조건을 추가하는 것이다. 따라서 기존 default 값을 변경하고자 하면 기존 제약조건을 삭제하고 새로운 제약조건을 추가하는 방법을 사용하여야 한다.


EXEC SP_HELP test -- 할당된 제약조건 이름을 확인한 후

ALTER TABLE test DROP CONSTRAINT DF___test__x__5D3B11F9 ; -- 기존 default 제약조건을 삭제

ALTER TABLE _temp ADD CONSTRAINT DF_temp_x default 2 FOR col1; -- 원하는 새로운 값을 가진 제약조건을 추가한다.