2013-02-11 11 views
43

Mam tabelę z danymi o nazwie energydataSQL oświadczenie MERGE aktualizacji danych

ma tylko trzy kolumny

(webmeterID, DateTime, kWh) 

mam nowy zestaw aktualnych danych w tabeli temp_energydata.

Modele DateTime i webmeterID pozostają bez zmian. Jednak wartości kWh wymagają aktualizacji z tabeli temp_energydata.

Jak napisać T-SQL w ten sposób?

+0

Czy w "temp_energydata" istnieją zapisy, których nie ma w "energydata"? –

Odpowiedz

96

Zakładając chcesz rzeczywiste SQL Server MERGE oświadczenie:

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target 
USING dbo.temp_energydata AS source 
    ON target.webmeterID = source.webmeterID 
    AND target.DateTime = source.DateTime 
WHEN MATCHED THEN 
    UPDATE SET target.kWh = source.kWh 
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (webmeterID, DateTime, kWh) 
    VALUES (source.webmeterID, source.DateTime, source.kWh); 

Jeśli też chcesz usunąć rekordy w tarczy, które nie są w źródle:

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target 
USING dbo.temp_energydata AS source 
    ON target.webmeterID = source.webmeterID 
    AND target.DateTime = source.DateTime 
WHEN MATCHED THEN 
    UPDATE SET target.kWh = source.kWh 
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (webmeterID, DateTime, kWh) 
    VALUES (source.webmeterID, source.DateTime, source.kWh) 
WHEN NOT MATCHED BY SOURCE THEN 
    DELETE; 

Ponieważ stało się to nieco więcej Jestem popularny, mam wrażenie, że powinienem rozwinąć tę odpowiedź nieco z pewnymi zastrzeżeniami, o których należy pamiętać.

Po pierwsze, istnieje kilka blogów, które zgłaszają concurrency issues with the MERGE statement. To może być w dużej mierze obejść podając wskazówkę HOLDLOCK lub SERIALIZABLE blokady:

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target 
[...] 

można również osiągnąć to samo z bardziej restrykcyjnych poziomów izolacji transakcji.

Istnieje several other known issues z MERGE. Z tego co wiem, większość z nich nie jest częstym problemem lub może pracować z tymi samymi wskazówkami co powyżej, ale nie testowałem ich.

Jak to jest, chociaż nigdy nie miałem żadnych problemów z rachunku MERGE siebie, ja zawsze używać wskazówkę WITH (HOLDLOCK) teraz, a ja wolę używać oświadczenie tylko w najprostszym przypadków.

+2

Klauzula 'NIE POTRZEBUJE ŹRÓDŁA 'może być w tym przypadku użyta z ostrożnością. Jeśli 'temp_energydata' zawiera aktualizacje tylko dla podzbioru członków w' energydata', twój drugi MERGE usunie dane ** wszystkich ** członków nie znalezionych w zestawie tymczasowym. –

+1

@AndriyM Dlatego właśnie powiedziałem: "Jeśli chcesz usunąć rekordy w celu, które nie są w źródle". Nie jestem pewien, jak to mogłoby być mylące? –

+0

Cóż, być może nie mylące, ale dla niedoświadczonej osoby * może * nie być całkowicie oczywiste, że jeśli chcą użyć zestawu tymczasowego do aktualizacji podzbioru wierszy (w szczególności podzbioru członków) w tabeli głównej , usunięte wiersze będą także zawierać tych członków, którzy nie powinni być aktualizowani. Nie nalegam (że to może nie być oczywiste), ale równie dobrze mogę być nieostrożny, więc proszę, zignorujcie mój komentarz, jeśli tak uważasz. –

3

Jeśli trzeba tylko aktualizować swoje rekordy w energydata na podstawie danych zawartych w temp_energydata, zakładając, że temp_enerydata nie zawiera żadnych nowych rekordów, a następnie spróbuj tego:

UPDATE e SET e.kWh = t.kWh 
    FROM energydata e INNER JOIN 
     temp_energydata t ON e.webmeterID = t.webmeterID AND 
          e.DateTime = t.DateTime 

Tu pracuje sqlfiddle

Ale jeśli temp_energydata zawiera nowe rekordy i trzeba je wstawić do energydata najlepiej z jednym stwierdzeniem, to zdecydowanie powinieneś pójść z odpowiedzią, którą dał Bacon Bits.

0
UPDATE ed 
SET ed.kWh = ted.kWh 
FROM energydata ed 
INNER JOIN temp_energydata ted ON ted.webmeterID = ed.webmeterID 
+0

To najprawdopodobniej nadpisze odczyty liczników w 'energydata' dla dat innych niż w' temp_energydata', co może być zaskakujące i niepożądane. – peterm

0
Update energydata set energydata.kWh = temp.kWh 
where energydata.webmeterID = (select webmeterID from temp_energydata as temp) 
+0

To najprawdopodobniej nadpisze odczyty liczników w 'energydata' dla dat innych niż w' temp_energydata', co może być zaskakujące i niepożądane. – peterm

-6

poprawny sposób IS:

UPDATE test1 
INNER JOIN test2 ON (test1.id = test2.id) 
SET test1.data = test2.data 
+3

Nie, jeśli są NOWYCH rekordów w 'temp_energydata'. Pewnie, możesz dodać 'INSERT INTO ... SELECT * FROM ... old LEFT JOIN new WHERE old.foo IS NULL' (przed lub po UPDATE), ale jest to dwie instrukcje i jeśli jest wystarczająco dużo danych czas wykonania może być wystarczająco długi, aby spowodować problemy, chyba że ZABLOKOWAĆ stół, a jeśli to zrobisz, prawdopodobnie rozwścieczy użytkowników (za mało miejsca, aby przejść do wszystkich scenariuszy). Wszystko, co powiedziałem, PREZENTUJĘ AKTUALIZACJĘ, a następnie INSERT (lub odwrotnie), ale nie odpowiada na pytanie OP. –

11

często wykorzystywane Bacon Bity wielką odpowiedź, po prostu nie mogę zapamiętać składnię.

Ale zwykle dodajemy WRT jako dodatek, aby uczynić część DELETE bardziej użyteczną, ponieważ bardzo często będziesz chciał zastosować scalenie tylko do części tabeli docelowej.

WITH target as (
    SELECT * FROM dbo.energydate WHERE DateTime > GETDATE() 
) 
MERGE INTO target WITH (HOLDLOCK) 
USING dbo.temp_energydata AS source 
    ON target.webmeterID = source.webmeterID 
    AND target.DateTime = source.DateTime 
WHEN MATCHED THEN 
    UPDATE SET target.kWh = source.kWh 
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (webmeterID, DateTime, kWh) 
    VALUES (source.webmeterID, source.DateTime, source.kWh) 
WHEN NOT MATCHED BY SOURCE THEN 
    DELETE 
+0

możesz również ulepszyć swoją klauzulę USING do pełnej instrukcji SELECT. Działa to dobrze, jeśli zapytanie jest proste, ale widziałem bardzo złe plany wykonania, jeśli zapytanie zawierało więcej niż 1-2 tabele. W tym przypadku użyłbym tabeli #temp lub CTE, jak na przykład –