모범 사례가없는 경우 SQL Server 삽입


152

나는이 Competitions보유하고 결과 테이블 팀 구성원의 이름 과 한 손에 순위를.

반면에 나는 유일한 경쟁자 이름테이블 을 유지해야 합니다 .

CREATE TABLE Competitors (cName nvarchar(64) primary key)

이제 첫 번째 테이블에 약 200,000 개의 결과가 있으며 경쟁자 테이블이 비어 있으면 이것을 수행 할 수 있습니다.

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

그리고 쿼리는 약 11,000 개의 이름을 삽입하는 데 5 초 정도 걸립니다.

지금까지 이것은 중요한 응용 프로그램이 아니므 로 약 10,000 행으로 새로운 경쟁 결과를받을 때 한 달에 한 번 경쟁사 테이블을 자르는 것을 고려할 수 있습니다 .

그러나 새로운 경쟁 업체와 기존 경쟁 업체에서 새로운 결과를 추가 할 때 가장 좋은 방법은 무엇입니까? 기존 경쟁사 테이블을 자르고 싶지 않습니다.

새로운 경쟁사에 대해서만 INSERT 문을 수행해야하며 존재하는 경우 아무 것도하지 않아도됩니다.


70
제발 하지 않는 a를 NVARCHAR(64)열 기본 (따라서 및 클러스터링) 키! 우선, 그것은 매우 넓은 키입니다-최대 128 바이트; 두 번째로 가변 크기입니다. 다시 : 최적이 아닙니다 ... 이것은 당신이 할 수있는 최악의 선택입니다-성능은 지옥이 될 것이고 테이블과 인덱스 조각화는 항상 99.9 %가 될 것입니다 .....
marc_s

4
마크는 좋은 지적이 있습니다. 이름을 pk로 사용하지 마십시오. ID, 바람직하게는 int 또는 가벼운 것을 사용하십시오.
richard mar

6
독특하고, 좁고, 정적으로, 계속 증가 하는 좋은 클러스터링 키를 만드는 것에 대한 Kimberly Tripp의 블로그 게시물 을 참조하십시오 . 귀하는 (가, 아마 정적 아니라, 좁은 아니에요 그리고 계속 증가하지 확실히이다) ... 4 명 중 3 부문에서 실패cName
marc_s

'WHERE name like'% xxxxx % '와 같이 모든 쿼리가 이름에있는 경쟁사 이름 테이블에 INT 기본 키를 추가하는 시점을 알 수 없으므로 항상 이름에 고유 인덱스가 필요합니다. 그러나 예, 가변 길이로 만들지 않는 점을 알 수 있습니다.
Didier Levy

3
a) 조각화를 피하고 b) 다른 테이블에서 외래 키인 경우 복제 된 데이터가 필요한 것보다 빠릅니다 (속도 고려 사항)
JamesRyan

답변:


214

의미 적으로 "아직 존재하지 않는 경쟁 업체 삽입"을 요청합니다.

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)

2
음, 이것이 SO에 대한 질문을하기 전에 내가 할 일입니다. 그러나 내 생각의 핵심은 : 일주일에 한 번 정도 이름 테이블을 처음부터 다시 작성하는 데 얼마나 효과적입니까? (이것은 몇 초 밖에 걸리지 않습니다)
Didier Levy

3
@Didier Levy : 효율성? 차이점만으로 업데이트 할 수있는 경우 잘리는 이유를 다시 작성하십시오. 즉 : BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN = 더 많은 작업입니다.
gbn

@gbn-당신의 대답 대신 if-else 논리를 안전하게 사용하는 방법이 있습니까? 관련 질문이 있습니다. 저 좀 도와 주실 래요? stackoverflow.com/questions/21889843/…
Steam

53

또 다른 옵션은 결과 테이블을 기존 경쟁자 테이블과 결합하고 조인과 일치하지 않는 고유 한 레코드를 필터링하여 새 경쟁자를 찾는 것입니다.

INSERT Competitors (cName)
SELECT  DISTINCT cr.Name
FROM    CompResults cr left join
        Competitors c on cr.Name = c.cName
where   c.cName is null

새로운 구문 MERGE 는 또한 작고 우아하며 효율적인 방법을 제공합니다.

MERGE INTO Competitors AS Target
USING (SELECT DISTINCT Name FROM CompResults) AS Source ON Target.Name = Source.Name
WHEN NOT MATCHED THEN
    INSERT (Name) VALUES (Source.Name);

1
이 경우 병합은 굉장합니다. 정확히 말한대로 수행합니다.
VorobeY1326

하위 쿼리 방식과 달리 SQL Server에 최적화를위한 최상의 힌트를 제공하는 것이 올바른 방법이라고 생각합니다.
Mads Nielsen

4
MERGE 문에는 여전히 많은 문제가 있습니다. 구글 "SQL 병합 문제"만으로 많은 블로거들이 이에 대해 논의했습니다.
David Wilson

왜 MERGE 문에는 Target As가 있고 INSERT 문에는 Target이 없습니까? 동등성을 파악하기 어렵게하는 더 많은 차이점이 있습니다.
Peter

32

왜 다른 사람이 아직이 말을하지 않았는지 모릅니다.

정상화.

경쟁을 모델로 한 테이블이 있습니까? 경쟁은 경쟁자로 구성되어 있습니까? 하나 이상의 대회에서 다른 선수 목록이 필요합니다 ......

다음 테이블이 있어야합니다 .....

CREATE TABLE Competitor (
    [CompetitorID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitorName] NVARCHAR(255)
    )

CREATE TABLE Competition (
    [CompetitionID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitionName] NVARCHAR(255)
    )

CREATE TABLE CompetitionCompetitors (
    [CompetitionID] INT
    , [CompetitorID] INT
    , [Score] INT

    , PRIMARY KEY (
        [CompetitionID]
        , [CompetitorID]
        )
    )

다른 테이블을 가리키는 CompetitionCompetitors.CompetitionID 및 CompetitorID에 대한 제한 조건이 있습니다.

이러한 종류의 테이블 구조 (키는 모두 간단한 INTS입니다)-모델에 맞는 좋은 자연 키가없는 것처럼 보이므로 SURROGATE 키가 여기에 적합하다고 생각합니다.

따라서 특정 경쟁에서 다른 경쟁 업체 목록을 얻으려면 다음과 같이 쿼리를 발행하십시오.

DECLARE @CompetitionName VARCHAR(50) SET @CompetitionName = 'London Marathon'

    SELECT
        p.[CompetitorName] AS [CompetitorName]
    FROM
        Competitor AS p
    WHERE
        EXISTS (
            SELECT 1
            FROM
                CompetitionCompetitor AS cc
                JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]
            WHERE
                cc.[CompetitorID] = p.[CompetitorID]
                AND cc.[CompetitionName] = @CompetitionNAme
        )

그리고 당신은 각 경쟁에 대한 점수를 원한다면 경쟁자가 있습니다 :

SELECT
    p.[CompetitorName]
    , c.[CompetitionName]
    , cc.[Score]
FROM
    Competitor AS p
    JOIN CompetitionCompetitor AS cc ON cc.[CompetitorID] = p.[CompetitorID]
    JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]

그리고 새로운 경쟁자와 새로운 경쟁을 할 때 경쟁사 테이블에 어떤 경쟁자가 이미 있는지 확인하면됩니다. 이들이 이미 존재하는 경우 해당 경쟁 업체의 경쟁 업체에 삽입하지 않고 새 경쟁 업체에 대한 삽입을 수행합니다.

그런 다음 새 경쟁을 경쟁에 삽입하고 마지막으로 경쟁 경쟁에 모든 링크를 만듭니다.


2
OP가 현재 자신의 모든 테이블을 재구성하여 하나의 캐시 된 결과를 얻는 것이 적절하다고 가정합니다. 정의 된 범위 내에서 문제를 해결하는 대신 DB 및 앱을 다시 작성하면 문제가 쉽게 발생하지 않을 때마다 재난을 피할 수 있습니다.
Jeffrey Vest

1
아마도 내 것과 같은 OP의 경우 항상 데이터베이스를 수정할 수있는 액세스 권한이 없을 수도 있습니다. 오래된 데이터베이스를 다시 작성 / 정규화하는 것이 예산이나 할당 된 시간에 항상있는 것은 아닙니다.
eaglei22

10

테이블을 함께 결합하여에 존재하지 않는 고유 경쟁 업체 목록을 가져와야합니다 Competitors.

고유 한 레코드가 삽입됩니다.

INSERT Competitors (cName) 
SELECT DISTINCT Name
FROM CompResults cr LEFT JOIN Competitors c ON cr.Name = c.cName
WHERE c.Name IS NULL

고유 한 이름을 선택할 때까지 기다릴 필요없이이 삽입을 빠르게 수행해야 할 때가 있습니다. 이 경우 고유 이름을 임시 테이블에 삽입 한 다음 해당 임시 테이블을 사용하여 실제 테이블에 삽입 할 수 있습니다. 임시 테이블에 삽입 할 때 모든 처리가 수행되므로 실제 테이블에 영향을 미치지 않기 때문에이 방법이 효과적입니다. 그런 다음 모든 처리가 완료되면 실제 테이블에 빠르게 삽입합니다. 트랜잭션 내부의 실제 테이블에 삽입하는 마지막 부분을 감쌀 수도 있습니다.


4

정상화에 대해 이야기하는 위의 답변은 훌륭합니다! 그러나 데이터베이스 스키마 나 구조를 그대로 사용할 수없는 나 같은 위치에 있다면 어떻게해야합니까? 예를 들어, DBA는 '신'이며 모든 제안 된 개정판은 / dev / null?

그런 점에서 위의 코드 샘플을 제공하는 모든 사용자와 관련 하여이 스택 오버플로 게시로도 이것이 답변 된 것 같습니다 .

INSERT VALUES WHERE NOT EXISTS 에서 코드를 다시 게시하고있어 기본 데이터베이스 테이블을 변경할 수 없으므로 가장 도움이되었습니다.

INSERT INTO #table1 (Id, guidd, TimeAdded, ExtraData)
SELECT Id, guidd, TimeAdded, ExtraData
FROM #table2
WHERE NOT EXISTS (Select Id, guidd From #table1 WHERE #table1.id = #table2.id)
-----------------------------------
MERGE #table1 as [Target]
USING  (select Id, guidd, TimeAdded, ExtraData from #table2) as [Source]
(id, guidd, TimeAdded, ExtraData)
    on [Target].id =[Source].id
WHEN NOT MATCHED THEN
    INSERT (id, guidd, TimeAdded, ExtraData)
    VALUES ([Source].id, [Source].guidd, [Source].TimeAdded, [Source].ExtraData);
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT id, guidd, TimeAdded, ExtraData from #table2
EXCEPT
SELECT id, guidd, TimeAdded, ExtraData from #table1
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT #table2.id, #table2.guidd, #table2.TimeAdded, #table2.ExtraData
FROM #table2
LEFT JOIN #table1 on #table1.id = #table2.id
WHERE #table1.id is null

위의 코드는 현재와 다른 필드를 사용하지만 다양한 기술로 일반적인 요지를 얻습니다.

Stack Overflow의 원래 답변에 따라이 코드는 here 에서 복사되었습니다 .

어쨌든 내 요점은 "모범 사례"는 종종 이론뿐만 아니라 할 수있는 것과 할 수없는 것에 귀착되는 것입니다.

  • 인덱스 / 키를 정규화하고 생성 할 수 있다면 좋습니다!
  • 그렇지 않은 경우 나와 같은 해킹을 코딩하는 방법이 있다면 위의 도움이되기를 바랍니다.

행운을 빕니다!


확실하지 않은 경우이 문제에 대한 네 가지 접근 방식이므로 하나를 선택하십시오.
nasch September

3

Transact Charlie가 제안한 대로 운영 테이블 을 정규화 하는 것은 좋은 생각이며 시간이 지남에 따라 많은 두통과 문제를 줄일 것입니다. 그러나 외부 시스템과의 통합을 지원하는 인터페이스 테이블 과 분석 같은 것을 지원하는 보고 테이블과 같은 것들이 있습니다. 처리; 그리고 이러한 유형의 테이블은 반드시 정규화 되어서는 안됩니다 . 실제로, 매우 자주 , 훨씬 편리하고 성능이 좋지 않습니다 .

이 경우 운영 테이블에 대한 Transact Charlie의 제안이 좋은 것 같습니다.

그러나 통합 (외부 소스에서 데이터로드)을 위해 CompetitorName에 대한 효율적인 조인을 지원하기 위해 Competitors 테이블의 CompetitorName에 인덱스 (고유 할 필요는 없음)를 추가하고 인터페이스 테이블을 혼합에 넣습니다. CompetitionResults.

CompetitionResults에는 경쟁 결과에 포함 된 모든 데이터가 포함되어야합니다. 이와 같은 인터페이스 테이블의 요점은 Excel 시트 또는 CSV 파일 또는 해당 데이터가있는 형식에서 가능한 한 빠르고 쉽게 잘라 내고 다시로드 할 수 있도록하는 것입니다.

해당 인터페이스 테이블은 정규화 된 운영 테이블 세트의 일부로 간주되지 않아야합니다. 그런 다음 Richard가 제안한대로 CompetitionResults에 참여하여 아직 존재하지 않는 경쟁 업체에 레코드를 삽입하고 그렇지 않은 경쟁 업체에 레코드를 업데이트 할 수 있습니다 (예 : 실제로 전화 번호 또는 이메일 주소와 같은 경쟁 업체에 대한 자세한 정보가있는 경우).

실제로 주목할 것은, 경쟁사 이름은 데이터에서 고유하지 않을 것 입니다. 예를 들어, 20 만 명의 경쟁 업체에서는 2 명 이상의 David Smith가있을 수 있습니다. 따라서 경쟁 업체의 전화 번호 나 이메일 주소 또는 고유 한 정보 등 더 많은 정보를 수집하는 것이 좋습니다.

운영 테이블 경쟁사에는 복합 자연 키에 기여하는 각 데이터 항목마다 하나의 열만 있어야합니다. 예를 들어 기본 이메일 주소에 대해 하나의 열이 있어야합니다. 그러나 인터페이스 테이블 에는 기본 전자 메일 주소에 대한 이전 값 과 값에 대한 슬롯이 있어야 기존 값을 사용하여 경쟁 업체에서 레코드를 조회하고 해당 부분을 새 값으로 업데이트 할 수 있습니다.

따라서 CompetitionResults에는 "old"및 "new"필드 (oldEmail, newEmail, oldPhone, newPhone 등)가 있어야합니다. 이렇게하면 CompetitorName, Email 및 Phone의 경쟁사에서 복합 키를 구성 할 수 있습니다.

그런 다음 일부 경쟁 결과가 나오면 Excel 시트 또는 보유한 모든 항목에서 CompetitionResults 테이블을 잘라 내고 다시로드하고 모든 새 경쟁자를 경쟁사 테이블에 삽입하기위한 효율적인 단일 삽입을 실행하고 업데이트 할 단일 효율적인 업데이트를 수행 할 수 있습니다 CompetitionResults에서 기존 경쟁사에 대한 모든 정보. 또한 단일 삽입을 수행하여 CompetitionCompetitors 테이블에 새 행을 삽입 할 수 있습니다. 이러한 작업은 CompetitionResults 테이블을로드 한 후 실행될 수있는 ProcessCompetitionResults 저장 프로 시저에서 수행 할 수 있습니다.

이것은 Oracle Applications, SAP, PeopleSoft 및 기타 엔터프라이즈 소프트웨어 제품군의 세탁 목록을 사용하여 실제 세계에서 반복해서 본 것에 대한 기본적인 설명입니다.

내가 할 줄 마지막 코멘트 내가 SO에 이전했습니다 하나입니다 : 당신이 외래 키를 만들 경우 CompetitionCompetitors에 거기에 그 경쟁자와 행을 추가하기 전에 경쟁자가 경쟁자 테이블에 존재하는 것을 보장가 있다는 것을 확인 외래 키는 계단식 업데이트 및 삭제로 설정되어 있습니다. 이렇게하면 경쟁 업체를 삭제해야 할 경우 경쟁 업체를 삭제할 수 있으며 해당 경쟁 업체와 관련된 모든 행이 자동으로 삭제됩니다. 그렇지 않으면 기본적으로 외래 키를 사용하면 경쟁자를 삭제하기 전에 CompetitionCompetitors에서 모든 관련 행을 삭제해야합니다.

(일부 사람들은 계단식이 아닌 외래 키가 좋은 안전 예방책이라고 생각하지만 내 경험은 단순히 감독의 결과가 아니라 종종 엉덩이로 인해 고통을 겪는다는 사실입니다. 사람들이 실수로 물건을 삭제하는 것을 다루기 때문에 "확실히"대화 상자와 다양한 유형의 정기적 백업 및 중복 데이터 소스와 같은 것이 있기 때문에 실제로 데이터가 모두있는 경쟁자를 삭제하는 것이 훨씬 더 일반적입니다. 예를 들어 실수로 하나를 삭제 한 다음 "아뇨! 그럴 생각은 없었습니다! 이제 경쟁 결과가 없습니다! Aaaahh!" 준비를해야하지만 전자가 훨씬 일반적입니다.전자를 준비하는 가장 쉽고 가장 좋은 방법은 외래 키를 계단식으로 업데이트하고 삭제하는 것입니다.)


1

좋아, 이것은 7 년 전에 요청되었지만 여기서 가장 좋은 해결책은 새 테이블을 완전히 버리고 사용자 정의 보기로하는 것입니다. 이렇게하면 데이터를 복제하지 않고 고유 한 데이터에 대해 걱정할 필요가 없으며 실제 데이터베이스 구조에 영향을 미치지 않습니다. 이 같은:

CREATE VIEW vw_competitions
  AS
  SELECT
   Id int
   CompetitionName nvarchar(75)
   CompetitionType nvarchar(50)
   OtherField1 int
   OtherField2 nvarchar(64)  --add the fields you want viewed from the Competition table
  FROM Competitions
GO

다른 테이블의 조인, WHERE 절 등과 같은 다른 항목을 여기에 추가 할 수 있습니다. 이제 뷰를 쿼리 할 수 ​​있으므로이 문제에 대한 가장 우아한 해결책 일 것입니다.

SELECT *
FROM vw_competitions

... 그리고 WHERE, IN 또는 EXISTS 절을보기 쿼리에 추가하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.