Masukkan SQL Server jika tidak ada praktik terbaik

152

Saya memiliki Competitionstabel hasil yang memuat nama anggota tim dan peringkat mereka di satu sisi.

Di sisi lain saya perlu mempertahankan daftar nama-nama pesaing yang unik :

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

Sekarang saya memiliki sekitar 200.000 hasil di tabel 1 dan ketika tabel pesaing kosong saya bisa melakukan ini:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

Dan permintaan hanya membutuhkan waktu 5 detik untuk memasukkan sekitar 11.000 nama.

Sejauh ini ini bukan aplikasi kritis sehingga saya dapat mempertimbangkan memotong tabel Kompetitor sebulan sekali, ketika saya menerima hasil kompetisi baru dengan sekitar 10.000 baris.

Tapi apa praktik terbaik ketika hasil baru ditambahkan, dengan pesaing baru DAN yang ada? Saya tidak ingin memotong tabel pesaing yang ada

Saya perlu melakukan pernyataan INSERT hanya untuk pesaing baru dan tidak melakukan apa pun jika ada.

Didier Levy
sumber
70
Tolong, jangan membuat NVARCHAR(64)kolom kunci utama Anda (dan dengan demikian: pengelompokan) !! Pertama-tama - ini adalah kunci yang sangat luas - hingga 128 byte; dan kedua itu ukuran variabel - lagi: tidak optimal ... Ini tentang pilihan terburuk yang dapat Anda miliki - kinerja Anda akan sangat buruk, dan fragmentasi tabel dan indeks akan berada di 99,9% setiap saat .....
marc_s
4
Marc punya poin bagus. Jangan gunakan nama sebagai pk Anda. Gunakan id, sebaiknya int atau sesuatu yang ringan.
richard
6
Lihat posting blog Kimberly Tripp tentang apa yang membuat kunci pengelompokan yang bagus : unik, sempit, statis, terus meningkat. cNameGagal Anda dalam tiga dari empat kategori .... (tidak sempit, mungkin tidak statis, dan jelas tidak pernah meningkat)
marc_s
Saya tidak bisa melihat intinya dalam menambahkan kunci primer INT ke tabel Nama Pesaing tempat SEMUA kueri akan ada pada namanya, seperti 'Nama WHERE seperti'% xxxxx% '' jadi saya selalu memerlukan indeks unik pada nama. Tapi ya, saya bisa melihat intinya TIDAK membuatnya panjang variabel ..
Didier Levy
3
a) menghindari fragmentasi dan b) jika itu adalah kunci asing di tabel lain, data yang diduplikasi lebih besar daripada yang diperlukan (yang merupakan pertimbangan kecepatan)
JamesRyan

Jawaban:

214

Semantik Anda bertanya "masukkan Pesaing di tempat yang belum ada":

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
gbn
sumber
2
Nah, inilah yang akan saya lakukan sebelum mengajukan pertanyaan pada SO. Tetapi inti dari pemikiran saya adalah: Seberapa baik kinerja ini terhadap membangun kembali tabel nama dari awal seminggu sekali atau lebih? (ingat ini hanya membutuhkan beberapa detik)
Didier Levy
3
@Didier Levy: Efisiensi? Mengapa terpotong, buat kembali ketika Anda hanya dapat memperbarui dengan perbedaan. Yaitu: BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN = lebih banyak pekerjaan.
gbn
@ GBN - Apakah ada cara untuk menggunakan logika if-else dengan aman di sini daripada jawaban Anda? Saya punya pertanyaan terkait. Bisakah Anda membantu saya dengan itu? stackoverflow.com/questions/21889843/…
Steam
53

Opsi lain adalah dengan bergabung dengan tabel Hasil Anda dengan pesaing Anda yang ada Tabel dan menemukan pesaing baru dengan memfilter catatan berbeda yang tidak cocok dengan bergabung:

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

Sintaks baru MERGE juga menawarkan cara yang ringkas, elegan, dan efisien untuk melakukan itu:

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);
pcofre
sumber
1
Menggabungkan mengagumkan dalam hal ini, ia melakukan persis apa yang dikatakannya.
VorobeY1326
Saya yakin ini adalah cara yang tepat untuk memberikan SQL Server petunjuk terbaik untuk mengoptimalkan, berbeda dengan pendekatan sub kueri.
Mads Nielsen
4
Pernyataan MERGE masih memiliki banyak masalah. Hanya google "Masalah SQL Merge" - banyak blogger telah membahas ini panjang lebar.
David Wilson
mengapa ada Sebagai Target dalam pernyataan MERGE, tetapi tidak ada Target dalam pernyataan INSERT? Ada lebih banyak perbedaan yang membuatnya sulit untuk memahami kesetaraan.
Peter
32

Tidak tahu mengapa orang lain belum mengatakan ini;

NORMALISASI.

Anda punya meja yang menjadi model kompetisi? Kompetisi terdiri dari Pesaing? Anda memerlukan daftar berbeda Pesaing dalam satu atau lebih Kompetisi ......

Anda harus memiliki tabel berikut ini .....

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

Dengan Kendala pada Kompetisi Pesaing. Kompetisi ID dan Pesaing ID menunjuk pada tabel lainnya.

Dengan struktur tabel seperti ini - kunci Anda semuanya INTS sederhana - sepertinya tidak ada KUNCI ALAMI yang baik yang akan cocok dengan model jadi saya pikir KUNCI SURROGATE sangat cocok di sini.

Jadi jika Anda memiliki ini maka untuk mendapatkan daftar pesaing yang berbeda dalam kompetisi tertentu Anda dapat mengeluarkan pertanyaan seperti ini:

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
        )

Dan jika Anda ingin skor untuk setiap kompetisi ada pesaing:

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]

Dan ketika Anda memiliki kompetisi baru dengan pesaing baru maka Anda cukup memeriksa mana yang sudah ada di tabel Pesaing. Jika sudah ada maka Anda tidak memasukkan ke dalam Pesaing untuk Pesaing tersebut dan melakukan penyisipan untuk yang baru.

Kemudian Anda memasukkan Kompetisi baru di Kompetisi dan akhirnya Anda hanya membuat semua tautan di Kompetitor Kompetisi.

Bertransaksi Charlie
sumber
2
Dengan asumsi bahwa OP memiliki kesembronoan saat ini untuk merestrukturisasi semua tabelnya untuk mendapatkan satu hasil di-cache. Menulis ulang db dan aplikasi Anda, alih-alih menyelesaikan masalah dalam beberapa lingkup yang ditentukan, setiap kali sesuatu tidak jatuh dengan mudah, adalah resep untuk bencana.
Jeffrey Vest
1
Mungkin dalam kasus OP seperti milik saya, Anda tidak selalu memiliki akses untuk memodifikasi database .. DAN menulis ulang / menormalkan database lama tidak selalu sesuai anggaran atau waktu yang ditentukan.
eaglei22
10

Anda harus bergabung dengan tabel bersama dan mendapatkan daftar pesaing unik yang belum ada Competitors.

Ini akan menyisipkan catatan unik.

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

Mungkin ada saatnya penyisipan ini harus dilakukan dengan cepat tanpa bisa menunggu pemilihan nama-nama unik. Dalam hal ini, Anda bisa memasukkan nama-nama unik ke dalam tabel sementara, dan kemudian menggunakan tabel sementara itu untuk memasukkan ke dalam tabel asli Anda. Ini berfungsi dengan baik karena semua pemrosesan terjadi pada saat Anda memasukkan ke tabel sementara, sehingga tidak mempengaruhi tabel asli Anda. Kemudian ketika Anda memiliki semua pemrosesan selesai, Anda melakukan memasukkan cepat ke tabel sebenarnya. Saya bahkan mungkin membungkus bagian terakhir, di mana Anda memasukkan ke dalam tabel nyata, di dalam suatu transaksi.

richard
sumber
4

Jawaban di atas yang berbicara tentang normalisasi sangat bagus! Tetapi bagaimana jika Anda menemukan diri Anda dalam posisi seperti saya di mana Anda tidak diizinkan menyentuh skema atau struktur basis data sebagaimana adanya? Misalnya, DBA adalah 'dewa' dan semua revisi yang disarankan menuju / dev / null?

Dalam hal itu, saya merasa seperti ini telah dijawab dengan posting Stack Overflow ini juga dalam hal semua pengguna di atas memberikan contoh kode.

Saya memposting ulang kode dari INSERT VALUES WHERE NOT EXISTS yang paling membantu saya karena saya tidak bisa mengubah tabel database yang mendasarinya:

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

Kode di atas menggunakan bidang yang berbeda dari yang Anda miliki, tetapi Anda mendapatkan inti umum dengan berbagai teknik.

Perhatikan bahwa sesuai jawaban asli pada Stack Overflow, kode ini disalin dari sini .

Pokoknya poin saya adalah "praktik terbaik" sering kali mengarah pada apa yang Anda bisa dan tidak bisa lakukan serta teori.

  • Jika Anda dapat menormalkan dan membuat indeks / kunci - hebat!
  • Jika tidak dan Anda memiliki kode peretasan seperti saya, semoga bantuan di atas membantu.

Semoga berhasil!


sumber
Jika tidak jelas, ini adalah empat pendekatan berbeda untuk masalah ini, jadi pilih satu.
nasch
3

Normalisasi tabel operasional Anda seperti yang disarankan oleh Transact Charlie, adalah ide yang baik, dan akan menghemat banyak sakit kepala dan masalah seiring waktu - tetapi ada hal-hal seperti tabel antarmuka , yang mendukung integrasi dengan sistem eksternal, dan tabel pelaporan , yang mendukung hal-hal seperti analitis pengolahan; dan tipe-tipe tabel itu tidak harus dinormalisasi - pada kenyataannya, sangat sering itu jauh, jauh lebih nyaman dan berkinerja bagi mereka untuk tidak .

Dalam hal ini, saya pikir proposal Transact Charlie untuk tabel operasional Anda adalah yang baik.

Tapi saya akan menambahkan indeks (tidak harus unik) ke CompetitorName di tabel Pesaing untuk mendukung sambungan efisien di CompetitorName untuk keperluan integrasi (memuat data dari sumber eksternal), dan saya akan menempatkan tabel antarmuka ke dalam campuran: CompetitionResults.

CompetitionResults harus berisi data apa pun yang dimiliki hasil kompetisi Anda di dalamnya. Maksud dari tabel antarmuka seperti ini adalah untuk membuatnya secepat dan semudah mungkin untuk memotong dan memuatnya kembali dari lembar Excel atau file CSV, atau dalam bentuk apa pun Anda menyimpan data tersebut.

Tabel antarmuka itu tidak boleh dianggap sebagai bagian dari set tabel operasional yang dinormalisasi. Kemudian Anda dapat bergabung dengan CompetitionResults seperti yang disarankan oleh Richard, untuk menyisipkan catatan ke dalam Pesaing yang belum ada, dan memperbarui yang ada (misalnya jika Anda benar-benar memiliki informasi lebih lanjut tentang pesaing, seperti nomor telepon atau alamat email mereka).

Satu hal yang ingin saya catat - dalam kenyataannya, Nama Pesaing, bagi saya, tampaknya sangat unik dalam data Anda . Dalam 200.000 pesaing, Anda mungkin memiliki 2 atau lebih David Smiths, misalnya. Jadi saya sarankan Anda mengumpulkan lebih banyak informasi dari pesaing, seperti nomor telepon atau alamat email mereka, atau sesuatu yang lebih cenderung unik.

Tabel operasional Anda, Pesaing, seharusnya hanya memiliki satu kolom untuk setiap item data yang berkontribusi terhadap kunci alami komposit; misalnya harus memiliki satu kolom untuk alamat email utama. Tetapi tabel antarmuka harus memiliki slot untuk nilai lama dan baru untuk alamat email utama, sehingga nilai lama dapat digunakan untuk mencari catatan di Pesaing dan memperbarui bagian itu ke nilai baru.

Jadi CompetitionResults harus memiliki beberapa bidang "lama" dan "baru" - oldEmail, newEmail, oldPhone, newPhone, dll. Dengan begitu Anda dapat membentuk kunci komposit, di Pesaing, dari Nama Pesaing, Email, dan Telepon.

Kemudian ketika Anda memiliki beberapa hasil kompetisi, Anda dapat memotong dan memuat kembali tabel CompetitionResults Anda dari lembar excel Anda atau apa pun yang Anda miliki, dan menjalankan satu, penyisipan efisien untuk memasukkan semua pesaing baru ke dalam tabel Pesaing, dan pembaruan tunggal, efisien untuk memperbarui semua informasi tentang pesaing yang ada dari Hasil Kompetisi. Dan Anda dapat melakukan satu penyisipan untuk menyisipkan baris baru ke dalam tabel CompetitionCompetitors. Hal-hal ini dapat dilakukan dalam prosedur tersimpan ProcessCompetitionResults, yang dapat dieksekusi setelah memuat tabel CompetitionResults.

Itu semacam deskripsi yang belum sempurna dari apa yang saya lihat dilakukan berulang-ulang di dunia nyata dengan Aplikasi Oracle, SAP, PeopleSoft, dan daftar cucian suite perangkat lunak perusahaan lainnya.

Satu komentar terakhir yang saya buat adalah komentar yang pernah saya buat di SO: Jika Anda membuat kunci asing yang memastikan bahwa ada Pesaing di tabel Pesaing sebelum Anda dapat menambahkan baris dengan Pesaing di dalamnya ke Pesaing Pesaing, pastikan bahwa kunci asing diatur untuk membuat pembaruan dan menghapus . Dengan begitu jika Anda perlu menghapus pesaing, Anda bisa melakukannya dan semua baris yang terkait dengan pesaing itu akan dihapus secara otomatis. Jika tidak, secara default, kunci asing akan meminta Anda untuk menghapus semua baris terkait dari CompetCompetitors sebelum itu akan membiarkan Anda menghapus Pesaing.

(Beberapa orang berpikir kunci asing non-kaskade adalah tindakan pencegahan keamanan yang baik, tetapi pengalaman saya adalah bahwa mereka hanya rasa sakit yang luar biasa di pantat yang lebih sering daripada tidak hanya akibat dari pengawasan dan mereka membuat banyak pekerjaan. untuk DBA. Berurusan dengan orang-orang yang secara tidak sengaja menghapus hal-hal adalah mengapa Anda memiliki hal-hal seperti dialog "apakah Anda yakin" dan berbagai jenis cadangan reguler dan sumber data yang berlebihan. Jauh, jauh lebih umum untuk benar-benar ingin menghapus pesaing, yang datanya semua misalnya kacau, daripada menghapus secara tidak sengaja dan kemudian pergi "Oh tidak! Saya tidak bermaksud melakukan itu! Dan sekarang saya tidak memiliki hasil kompetisi mereka! Aaaahh!" Yang terakhir ini tentu cukup umum, jadi , Anda harus siap untuk itu, tetapi yang pertama jauh lebih umum,jadi cara termudah dan terbaik untuk mempersiapkan yang pertama, imo, adalah dengan hanya membuat pembaruan dan menghapus kunci asing.)

Shavais
sumber
1

Ok, ini ditanyakan 7 tahun yang lalu, tapi saya pikir solusi terbaik di sini adalah melepaskan meja baru sepenuhnya dan hanya melakukan ini sebagai tampilan khusus. Dengan begitu Anda tidak menggandakan data, tidak ada kekhawatiran tentang data unik, dan itu tidak menyentuh struktur database yang sebenarnya. Sesuatu seperti ini:

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

Barang-barang lain dapat ditambahkan di sini seperti bergabung di tabel lain, klausa WHERE, dll. Ini kemungkinan besar solusi yang paling elegan untuk masalah ini, karena Anda sekarang hanya dapat meminta tampilan:

SELECT *
FROM vw_competitions

... dan tambahkan klausa WHERE, IN, atau EXISTS apa pun ke kueri tampilan.

Pengundang
sumber