SQL Server chèn nếu không tồn tại thực tiễn tốt nhất


152

Tôi có một Competitionsbảng kết quả chứa tên của các thành viên trong nhóm và thứ hạng của họ trên một mặt.

Mặt khác, tôi cần duy trì một bảng các tên đối thủ cạnh tranh duy nhất :

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

Bây giờ tôi có khoảng 200.000 kết quả trong bảng 1 và khi bảng đối thủ trống, tôi có thể thực hiện việc này:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

Và truy vấn chỉ mất khoảng 5 giây để chèn khoảng 11.000 tên.

Cho đến nay, đây không phải là một ứng dụng quan trọng vì vậy tôi có thể xem xét cắt bớt bảng Đối thủ mỗi tháng một lần, khi tôi nhận được kết quả cạnh tranh mới với khoảng 10.000 hàng.

Nhưng thực tiễn tốt nhất khi kết quả mới được thêm vào là gì, với các đối thủ cạnh tranh mới VÀ hiện có? Tôi không muốn cắt bớt bảng đối thủ hiện có

Tôi cần thực hiện tuyên bố INSERT chỉ cho các đối thủ cạnh tranh mới và không làm gì nếu chúng tồn tại.


70
Xin vui lòng, không tạo một NVARCHAR(64)cột chính của bạn (và do đó: phân cụm) !! Trước hết - đó là một khóa rất rộng - lên tới 128 byte; và thứ hai là kích thước thay đổi - một lần nữa: không tối ưu ... Đây là lựa chọn tồi tệ nhất bạn có thể có - hiệu suất của bạn sẽ là địa ngục, và phân mảnh bảng và chỉ số sẽ ở mức 99,9% mọi lúc .....
marc_s

4
Marc có một điểm tốt. Đừng sử dụng tên như pk của bạn. Sử dụng một id, tốt nhất là int hoặc một cái gì đó nhẹ.
Richard

6
Xem bài đăng trên blog của Kimberly Tripp về những gì tạo nên một cụm phân cụm tốt : độc đáo, hẹp, tĩnh, không ngừng tăng lên. cNameThất bại của bạn ở ba trong bốn loại .... (nó không hẹp, có lẽ nó không tĩnh và chắc chắn nó không tăng lên)
marc_s

Tôi không thể thấy điểm khi thêm khóa chính INT vào bảng Tên của đối thủ cạnh tranh trong đó TẤT CẢ các truy vấn sẽ có tên, như 'Tên WHERE như'% xxxxx% '' vì vậy tôi luôn cần một chỉ mục duy nhất trên tên. Nhưng vâng, tôi có thể thấy điểm trong việc KHÔNG làm cho nó có chiều dài thay đổi ..
Didier Levy

3
a) tránh phân mảnh và b) nếu đó là khóa ngoại trong các bảng khác, dữ liệu trùng lặp lớn hơn cần thiết (đó là xem xét tốc độ)
JamesRyan

Câu trả lời:


214

Về mặt ngữ nghĩa, bạn đang hỏi "chèn Đối thủ cạnh tranh không tồn tại":

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

2
Chà, đây là những gì tôi sẽ thực hiện trước khi đặt ra câu hỏi về SO. Nhưng cốt lõi của suy nghĩ của tôi là: Điều này sẽ thực hiện tốt như thế nào đối với việc xây dựng lại bảng tên từ đầu mỗi tuần một lần hoặc lâu hơn? (hãy nhớ điều này chỉ mất vài giây)
Didier Levy

3
@Didier Levy: Hiệu quả? Tại sao cắt ngắn, tạo lại khi bạn chỉ có thể cập nhật với sự khác biệt. Đó là: BEGIN TRAN DELETE CompResults INSERT CompResults .. CAMIT TRAN = công việc nhiều hơn.
gbn

@gbn - Có cách nào để sử dụng logic if-other an toàn ở đây thay vì câu trả lời của bạn không? Tôi có một câu hỏi liên quan. Bạn có thể vui lòng giúp tôi với điều đó? stackoverflow.com/questions/21889843/ Mạnh
Steam

53

Một tùy chọn khác là rời khỏi tham gia bảng Kết quả của bạn với Bảng đối thủ hiện tại của bạn và tìm các đối thủ cạnh tranh mới bằng cách lọc các bản ghi riêng biệt mà donett khớp với tham gia:

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

Cú pháp mới MERGE cũng cung cấp một cách nhỏ gọn, thanh lịch và hiệu quả để làm điều đó:

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
Hợp nhất là tuyệt vời trong trường hợp này, nó thực hiện chính xác những gì nó nói.
VorobeY1326

Tôi chắc chắn tin rằng đây là cách đúng đắn, mang đến cho SQL Server những gợi ý tốt nhất có thể để tối ưu hóa, trái ngược với cách tiếp cận truy vấn phụ.
Mads Nielsen ngày

4
Tuyên bố MERGE vẫn còn rất nhiều vấn đề. Chỉ cần google "Vấn đề hợp nhất SQL" - nhiều blogger đã thảo luận về vấn đề này.
David Wilson

tại sao lại có As Target trong câu lệnh MERGE, nhưng không có Target trong câu lệnh INSERT? Có nhiều sự khác biệt khiến bạn khó nắm bắt được sự tương đương.
Peter

32

Không biết tại sao mọi người khác chưa nói điều này;

BÌNH THƯỜNG.

Bạn đã có một bảng mô hình các cuộc thi? Các cuộc thi được tạo thành từ các đối thủ cạnh tranh? Bạn cần một danh sách các Đối thủ cạnh tranh khác nhau trong một hoặc nhiều Cuộc thi ......

Bạn nên có các bảng sau .....

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]
        )
    )

Với các ràng buộc về Cạnh tranhCompetitor.CompetitionID và ChalleitorID chỉ vào các bảng khác.

Với kiểu cấu trúc bảng này - các khóa của bạn đều là INTS đơn giản - dường như không có một phím TỰ NHIÊN nào phù hợp với mô hình, vì vậy tôi nghĩ rằng một KHÓA KHÓA là phù hợp ở đây.

Vì vậy, nếu bạn đã có điều này thì để có được danh sách các đối thủ cạnh tranh khác nhau trong một cuộc thi cụ thể, bạn có thể đưa ra một truy vấn như thế này:

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
        )

Và nếu bạn muốn điểm số cho mỗi cuộc thi, một đối thủ sẽ tham gia:

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]

Và khi bạn có một đối thủ cạnh tranh mới với các đối thủ mới thì bạn chỉ cần kiểm tra những đối thủ nào đã tồn tại trong bảng Đối thủ cạnh tranh. Nếu chúng đã tồn tại thì bạn không chèn vào Đối thủ cạnh tranh cho các Đối thủ cạnh tranh đó và thực hiện chèn cho đối thủ cạnh tranh mới.

Sau đó, bạn chèn Cuộc thi mới vào Cạnh tranh và cuối cùng bạn chỉ cần tạo tất cả các liên kết trong Cạnh tranh cạnh tranh.


2
Giả sử rằng OP có mức thuế tại thời điểm này để cơ cấu lại tất cả các bảng của mình để có được một kết quả được lưu trong bộ nhớ cache. Viết lại db và ứng dụng của bạn, thay vì giải quyết vấn đề trong một phạm vi được xác định, mỗi khi có thứ gì đó không dễ dàng rơi vào vị trí, là một công thức cho thảm họa.
Jeffrey Vest

1
Có thể trong trường hợp của OP như của tôi, bạn không phải lúc nào cũng có quyền truy cập để sửa đổi cơ sở dữ liệu .. VÀ viết lại / bình thường hóa một cơ sở dữ liệu cũ không phải lúc nào cũng nằm trong ngân sách hoặc thời gian quy định.
Eaglei22

10

Bạn sẽ cần phải tham gia các bảng với nhau và nhận được một danh sách các đối thủ cạnh tranh duy nhất chưa tồn tại Competitors.

Điều này sẽ chèn hồ sơ duy nhất.

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

Có thể đến một lúc khi việc chèn này cần được thực hiện nhanh chóng mà không thể chờ đợi việc lựa chọn tên duy nhất. Trong trường hợp đó, bạn có thể chèn các tên duy nhất vào một bảng tạm thời, sau đó sử dụng bảng tạm thời đó để chèn vào bảng thực của bạn. Điều này hoạt động tốt bởi vì tất cả quá trình xử lý xảy ra tại thời điểm bạn đang chèn vào một bảng tạm thời, vì vậy nó không ảnh hưởng đến bảng thực của bạn. Sau đó, khi bạn đã xử lý xong, bạn thực hiện thao tác chèn nhanh vào bảng thực. Tôi thậm chí có thể gói phần cuối cùng, nơi bạn chèn vào bảng thực, bên trong một giao dịch.


4

Các câu trả lời ở trên mà nói về bình thường hóa là tuyệt vời! Nhưng điều gì sẽ xảy ra nếu bạn thấy mình ở một vị trí như tôi khi bạn không được phép chạm vào lược đồ hoặc cấu trúc cơ sở dữ liệu khi nó đứng? Ví dụ, các DBA là 'các vị thần' và tất cả các phiên bản được đề xuất chuyển sang / dev / null?

Về mặt đó, tôi cảm thấy như điều này đã được trả lời với bài đăng Stack Overflow này liên quan đến tất cả những người dùng ở trên đưa ra các mẫu mã.

Tôi đang đăng lại mã từ GIÁ TRỊ XÁC ĐỊNH Ở ĐÂU KHÔNG HIỆN TẠI , điều này giúp tôi nhiều nhất vì tôi không thể thay đổi bất kỳ bảng cơ sở dữ liệu cơ bản nào:

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

Đoạn mã trên sử dụng các trường khác nhau so với những gì bạn có, nhưng bạn có được ý chính chung với các kỹ thuật khác nhau.

Lưu ý rằng theo câu trả lời ban đầu trên Stack Overflow, mã này đã được sao chép từ đây .

Dù sao, quan điểm của tôi là "thực hành tốt nhất" thường đi vào những gì bạn có thể và không thể làm tốt như lý thuyết.

  • Nếu bạn có thể bình thường hóa và tạo các chỉ mục / khóa - thật tuyệt!
  • Nếu không và bạn có khu nghỉ mát để hack mã như tôi, hy vọng những điều trên sẽ giúp ích.

Chúc may mắn!


Trong trường hợp không rõ ràng, đây là bốn cách tiếp cận khác nhau cho vấn đề, vì vậy hãy chọn một.
mũi

3

Bình thường hóa các bảng hoạt động của bạn theo đề xuất của Transact Charlie, là một ý tưởng hay và sẽ tiết kiệm nhiều vấn đề đau đầu và vấn đề theo thời gian - nhưng có những thứ như bảng giao diện , hỗ trợ tích hợp với các hệ thống bên ngoài và bảng báo cáo , hỗ trợ những thứ như phân tích Chế biến; và những loại bảng đó không nhất thiết phải được chuẩn hóa - trên thực tế, thông thường, nó rất tiện lợi và hiệu quả hơn nhiều đối với chúng .

Trong trường hợp này, tôi nghĩ đề xuất của Transact Charlie cho các bảng hoạt động của bạn là một điều tốt.

Nhưng tôi sẽ thêm một chỉ mục (không nhất thiết là duy nhất) vào đối thủ cạnh tranh trong bảng Đối thủ để hỗ trợ các kết nối hiệu quả trên đối thủ cạnh tranh cho mục đích tích hợp (tải dữ liệu từ các nguồn bên ngoài) và tôi sẽ đặt một bảng giao diện vào hỗn hợp: Cạnh tranh.

Cạnh tranh phải chứa bất kỳ dữ liệu nào mà kết quả cạnh tranh của bạn có trong đó. Điểm quan trọng của bảng giao diện như bảng này là làm cho nó nhanh chóng và dễ dàng nhất có thể cắt và tải lại từ bảng Excel hoặc tệp CSV hoặc bất kỳ dạng nào bạn có dữ liệu đó.

Bảng giao diện đó không nên được coi là một phần của tập hợp các bảng hoạt động được chuẩn hóa. Sau đó, bạn có thể tham gia với Cạnh tranh theo đề xuất của Richard, để chèn hồ sơ vào Đối thủ cạnh tranh chưa tồn tại và cập nhật những thông tin đó (ví dụ: nếu bạn thực sự có thêm thông tin về đối thủ cạnh tranh, như số điện thoại hoặc địa chỉ email của họ).

Một điều tôi sẽ lưu ý - trong thực tế, Tên đối thủ, dường như đối với tôi, rất khó có thể là duy nhất trong dữ liệu của bạn . Trong 200.000 đối thủ cạnh tranh, rất có thể bạn có từ 2 David Smith trở lên. Vì vậy, tôi khuyên bạn nên thu thập thêm thông tin từ các đối thủ cạnh tranh, chẳng hạn như số điện thoại của họ hoặc địa chỉ email hoặc một cái gì đó có nhiều khả năng là duy nhất.

Bảng điều hành của bạn, Đối thủ cạnh tranh, chỉ nên có một cột cho mỗi mục dữ liệu đóng góp vào khóa tự nhiên tổng hợp; ví dụ: nó nên có một cột cho một địa chỉ email chính. Nhưng bảng giao diện nên có một vị trí cho các giá trị mới cho địa chỉ email chính, để có thể sử dụng giá trị cũ để tra cứu bản ghi trong Đối thủ cạnh tranh và cập nhật phần đó thành giá trị mới.

Vì vậy, Cạnh tranh phải có một số trường "cũ" và "mới" - oldEmail, newEmail, oldPhone, newPhone, v.v. Bằng cách đó, bạn có thể tạo một khóa tổng hợp, trong Đối thủ cạnh tranh, từ Đối thủ cạnh tranh, Email và Điện thoại.

Sau đó, khi bạn có một số kết quả cạnh tranh, bạn có thể cắt bớt và tải lại bảng Cạnh tranh từ bảng excel của bạn hoặc bất cứ thứ gì bạn có, và chạy một chèn hiệu quả để chèn tất cả các đối thủ mới vào bảng Đối thủ cạnh tranh và cập nhật một cách hiệu quả để cập nhật tất cả các thông tin về các đối thủ cạnh tranh hiện có từ Cạnh tranh. Và bạn có thể thực hiện một thao tác chèn để chèn các hàng mới vào bảng Cạnh tranh cạnh tranh. Những điều này có thể được thực hiện trong một thủ tục được lưu trữ ProcessCompetitionResults, có thể được thực thi sau khi tải bảng Cạnh tranh.

Đó là một mô tả thô sơ về những gì tôi đã thấy lặp đi lặp lại trong thế giới thực với Ứng dụng Oracle, SAP, PeopleSoft và danh sách các bộ phần mềm doanh nghiệp khác.

Một nhận xét cuối cùng tôi đưa ra là một nhận xét trước đây về SO: Nếu bạn tạo khóa ngoại bảo đảm rằng Đối thủ cạnh tranh tồn tại trong bảng Đối thủ trước khi bạn có thể thêm một hàng với Đối thủ cạnh tranh đó vào Đối thủ cạnh tranh, hãy đảm bảo rằng khóa ngoại được thiết lập để cập nhật theo tầng và xóa . Bằng cách đó nếu bạn cần xóa đối thủ cạnh tranh, bạn có thể thực hiện và tất cả các hàng được liên kết với đối thủ đó sẽ tự động bị xóa. Mặt khác, theo mặc định, khóa ngoại sẽ yêu cầu bạn xóa tất cả các hàng liên quan ra khỏi Cạnh tranh cạnh tranh trước khi nó cho phép bạn xóa Đối thủ cạnh tranh.

(Một số người cho rằng khóa ngoại không xếp tầng là một biện pháp phòng ngừa an toàn tốt, nhưng kinh nghiệm của tôi là chúng chỉ là một cơn đau kỳ lạ ở mông thường không chỉ đơn giản là kết quả của việc giám sát và họ tạo ra một loạt các công việc Đối với DBA. Đối phó với những người vô tình xóa nội dung là lý do tại sao bạn có các hộp thoại như "bạn có chắc chắn không" và nhiều loại sao lưu thông thường và nguồn dữ liệu dư thừa. Nó thực sự phổ biến đến mức muốn xóa một đối thủ cạnh tranh, có dữ liệu là tất cả Ví dụ như đã nhầm lẫn, hơn là vô tình xóa một cái rồi đi "Ồ không! Tôi không có ý đó! Và bây giờ tôi không có kết quả cạnh tranh của họ! Aaaahh!" , bạn cần phải chuẩn bị cho nó, nhưng trước đây là phổ biến hơn nhiều,Vì vậy, cách dễ nhất và tốt nhất để chuẩn bị cho cái trước, imo, là chỉ tạo các khóa ngoại và cập nhật tầng và xóa.)


1

Ok, điều này đã được hỏi 7 năm trước, nhưng tôi nghĩ giải pháp tốt nhất ở đây là từ bỏ hoàn toàn bảng mới và chỉ làm điều này như một chế độ xem tùy chỉnh. Theo cách đó, bạn không sao chép dữ liệu, không phải lo lắng về dữ liệu duy nhất và nó không chạm vào cấu trúc cơ sở dữ liệu thực tế. Một cái gì đó như thế này:

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

Các mục khác có thể được thêm vào đây như tham gia trên các bảng khác, mệnh đề WHERE, v.v ... Đây rất có thể là giải pháp thanh lịch nhất cho vấn đề này, vì bây giờ bạn chỉ có thể truy vấn chế độ xem:

SELECT *
FROM vw_competitions

... và thêm bất kỳ mệnh đề WHERE, IN hoặc EXISTS nào vào truy vấn xem.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.