2014-10-21 10 views
5

sto affrontando il seguente problema:Come evitare eccezioni chiave duplicate in modo efficiente

Sto cercando di mantenere una tabella in SQL-Server sincronizzato con più database esterni. Questi database esterni non hanno una chiave primaria univoca condivisa, quindi la tabella locale ha un PK intero semplice.

Ora per mantenere la tabella locale fino ad oggi la seguente è fatto:

  1. banche dati esterne vengono interrogati.
  2. I dati vengono convertiti in dati validi per la tabella locale.
  3. Un inserto viene utilizzato per provare a scrivere i dati nella tabella locale.
  4. Se insert restituisce un'eccezione di immissione duplicata, il PK verrà ricercato da una query di selezione ei dati verranno scritti nella tabella da una query di aggiornamento.
  5. Un'altra tabella viene modificata utilizzando il PK della riga inserita o aggiornata.

Ora questo funziona bene ma a me sembra molto inefficiente. La maggior parte delle volte i dati sono già nella tabella locale e generano un'eccezione chiave duplicata sull'inserto. Ciò significa che molte eccezioni devono essere gestite, il che è costoso. Inoltre, poiché i PK sono gestiti dal DB, è necessario utilizzare una query di selezione per trovare la riga da aggiornare.

Come posso evitare questo effetto? Non voglio utilizzare una stored procedure come mi piace per mantenere la query gestibile dal codice e inclusa nel controllo della versione.

Ho visto l'unione ma ho visto troppe persone che hanno segnalato problemi con esso.

Penso di aver bisogno di usare un modulo di upsert ma non sono sicuro di come farlo con il PK gestito dal DB.

tl; dr: Ciò di cui ho bisogno è una query che mi consenta di inserire o aggiornare una riga (a seconda della chiave duplicata o meno) che restituirà sempre il PK della riga.

+2

L'istruzione MERGE è il comando upsert per SQL Server –

+0

@SteveFord Lo so, ma ci sono segnalazioni di rallentamenti e/o errori. Non è più così? Vedere: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ http://www.sqlservercentral.com/Forums/Topic1465931-391-1.aspx –

+1

abbastanza corretto @ mike-van-leeuwen un approccio alternativo sarebbe quello di eseguire prima l'UPDATE usando un'istruzione OUTPUT per catturare le righe aggiornate in una tabella temporanea. Quindi esegui un INSERT ... SELECT .... WHERE NOT EXISTS (SELECT * FROM temptable) –

risposta

2

Ho un'implementazione che ho fatto in passato e che mi piace. Potresti trovarlo utile o meno.

Ecco come funziona ... Carico sia i dati esterni che quelli locali in memoria utilizzando un oggetto modello che funzionerà per entrambi. Per esempio ...

public class Person 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string PhoneNumber { get; set; } 
    public string Address { get; set; } 

    // This comparer will be used to find records that exist or don't exist. 
    public class KeyFieldComparer : IEqualityComparer<Person> 
    { 
     public bool Equals(Person p1, Person p2) 
     { 
      return p1.FirstName == p2.FirstName && p1.LastName == p2.LastName; 
     } 

     public int GetHashCode(Person p) 
     { 
      return p.FirstName.GetHashCode()^p.LastName.GetHashCode(); 
     } 
    } 

    // This comparer will be used to find records that are outdated and need to be updated. 
    public class OutdatedComparer : IEqualityComparer<Person> 
    { 
     public bool Equals(Person p1, Person p2) 
     { 
      return p1.FirstName == p2.FirstName && p1.LastName == p2.LastName && (p1.PhoneNumber != p2.PhoneNumber || p1.Address != p2.Address); 
     } 

     public int GetHashCode(Person p) 
     { 
      return p.FirstName.GetHashCode()^p.LastName.GetHashCode(); 
     } 
    } 
} 

Abbiamo bisogno di avere un modo per identificare in modo univoco i record che presumo che avete. In questo esempio è di FirstName e LastName (so che non è molto particolare, ma per semplicità facciamo finta che funzioni bene). Lo IEqualityComparer<> farà il lavoro di trovare i record vecchi e nuovi quando gli elenchi vengono caricati in memoria.

Ora abbiamo semplicemente separati record obsoleti esistenti e nuovi dischi come questo ...

List<Person> local = loadLocalRecords(); 
List<Person> external = loadExternalRecords(); 

var newRecordsToInsert = external.Except(local, new Person.KeyFieldComparer()); 

var outdatedRecordsToUpdate = local.Intersect(external, new Person.OutdatedComparer()); 

spero che abbia senso. Posso rispondere alle domande se li hai. La cosa buona di questo metodo è che fa il lavoro con il minor numero di accessi al database (credo). La cosa brutta è che deve caricare tutto in memoria che potrebbe non essere pratico per te. Ma la dimensione del tuo tavolo deve essere grande per essere un problema. Da qualche parte sopra alcuni milioni di record a seconda di quante colonne.

+0

Grazie per questa risposta, è un approccio interessante. Ho pensato a una soluzione simile, ma penso che proverò a unire prima. Potrei fare un piccolo test per vedere l'efficienza di questa soluzione rispetto a quella attuale con le eccezioni. Sarebbe un divertente esperimento. La memoria non sarebbe davvero un problema per la mia situazione, sto caricando i dati in parti. –

Problemi correlati