SQL Server 2008 újdonságok 8. – MERGE utasítás 2.

A következő példát annak illusztrálására raktam össze (nem, ezt már nem loptam, mint a tegnapit :), hogy hogyan lehet a kliensen történt módosításokat, törléseket és beszúrásokat egy füst alatt betolni a szervernek.

Az elképzelt alkalmazás kb. úgy néz ki, mint amit megszoktunk disconnected datadatable-ök esetén. Mivel a datatable ma már nem menő, lehet üzleti ojjektumra is gondolni, ami az adatbázisból jött adatokat fieldekben vagy tömbökben tárolja.

Az adatok egy részhalmazát lehúzzuk a szerverről közönséges, szűrt select segítségével. A kliens ezeket a sorokat módosíthatja, beszúrhat újakat és törölheti is őket. A törlést esetünkben azzal szimulálom, hogy az egyik oszlop értékét !torlendo!-re állítom (itt persze egy olyan string kell, amit garantáltan nem vihet be Mancika). Ha az összes sort levinnénk a kliensre nem kellene ez utóbbi trükk, hisz egy outer join (meg a merge is) meg tudná mondani, mely sorok hiányoznak a kliens halmazából.

No, a kliens tehát összeállítja a módosított adathalmazt, amiben mindhárom művelet szerepel, majd betolja a szervernek. Ha van egy kis esze, a nem változott sorokat nem küldi be, de ezt is lekezeli a példa.

A beköldött adatokat az új MERGE utasítással aprítjuk be.

Kezdjünk neki! Előállítok egy kis táblácskát, ez szimbolizálja a szerver oldali táblánkat.

create schema MergeDemo
go
select EmployeeID, Title, VacationHours 
into MergeDemo.EmpServerSide
from HumanResources.Employee

Kliens oldalon valahogyan letároljuk az adatokat és módosítjuk, a módosításokat pl. tábla típusú paraméteren keresztül küldhetjük be egyszerre. Ezt a paramétert jelképezi most a következő tábla:

create table MergeDemo.EmpClientSide
(
	EmployeeID int not null identity(0, -1),
	Title nvarchar(50) not null,
	VacationHours smallint not null
)

Látható, hogy az identity 0-tól megy visszafelé, így a kliens oldalon az új sorokhoz generált id-k nem ütköznek majd a szerveroldaliakkal. Klasszikus ado.net hack. Rakjunk bele némi adatot, ez menne le a kliens apphoz:

set identity_insert MergeDemo.EmpClientSide on

insert into
MergeDemo.EmpClientSide
(
EmployeeID,
Title,
VacationHours
)
select
EmployeeID,
Title,
VacationHours
from
HumanResources.Employee
where
EmployeeID < 20 set identity_insert MergeDemo.EmpClientSide off [/source] Ha most megnézzük a táblát, ezt találjuk benne: [source='c'] EmployeeID Title VacationHours ----------- -------------------------------------------------- ------------- 1 Production Technician - WC60 21 2 Marketing Assistant 42 3 Engineering Manager 2 4 Senior Tool Designer 48 5 Tool Designer 9 6 Marketing Manager 40 7 Production Supervisor - WC60 82 8 Production Technician - WC10 83 9 Design Engineer 5 10 Production Technician - WC10 88 11 Design Engineer 6 12 Vice President of Engineering 1 13 Production Technician - WC10 84 14 Production Supervisor - WC50 79 15 Production Technician - WC10 85 16 Production Supervisor - WC60 80 17 Production Technician - WC10 86 18 Production Supervisor - WC60 81 19 Production Technician - WC10 87 [/source] Most a kliens fogja az über gridjét, és belebabrál az adatokba. Mivel mi sql táblával szimuláljuk a kliens oldali konténert, sql műveletekkel dolgozok. (Érdemes jól megnézni azt az insertet, aztán csapkodni a homlokunkat, oracle-ösök csöndben maradnak :). [source='sql'] --Beszúrás szimuláció insert into MergeDemo.EmpClientSide (Title, VacationHours) values ('Hasvakaró', 0), ('Hátvakaró', 0) --Módosítás szimuláció update MergeDemo.EmpClientSide set Title = Title + ' alma' where EmployeeID > 15

–Törlés szimuláció
update MergeDemo.EmpClientSide
set Title = ‘!torlendo!’
where EmployeeID > 0 and EmployeeID < 4 [/source] A kliens adatai ekkor így néznek ki: [source='c'] EmployeeID Title VacationHours ----------- -------------------------------------------------- ------------- 1 !torlendo! 21 2 !torlendo! 42 3 !torlendo! 2 4 Senior Tool Designer 48 5 Tool Designer 9 6 Marketing Manager 40 7 Production Supervisor - WC60 82 8 Production Technician - WC10 83 9 Design Engineer 5 10 Production Technician - WC10 88 11 Design Engineer 6 12 Vice President of Engineering 1 13 Production Technician - WC10 84 14 Production Supervisor - WC50 79 15 Production Technician - WC10 85 16 Production Supervisor - WC60 alma 80 17 Production Technician - WC10 alma 86 18 Production Supervisor - WC60 alma 81 19 Production Technician - WC10 alma 87 -1 Hasvakaró 0 -2 Hátvakaró 0 [/source] Látható, hogy az első három tételt kell majd kiradírozni a szerveren, a 16-19 közöttiek módosultak és a két új sort hátul látjuk, kliens oldalon generált fake id-kkel. No, most jön a főszereplő, a merge, ami minden módosítást bevisz egy szuszra: [source='sql'] merge into MergeDemo.EmpServerSide s using MergeDemo.EmpClientSide c on s.EmployeeID = c.EmployeeID when matched and c.Title = '!torlendo!' then delete when matched and c.Title != '!torlendo!' and (c.Title != s.Title or c.VacationHours != s.VacationHours) then update set s.Title = c.Title, s.VacationHours = c.VacationHours when target not matched then insert (Title, VacationHours) values (c.Title, c.VacationHours); [/source] Aki tud angolul, az majdhogynem szövegként olvashatja az utasítást. Hogy lássuk mi történik a háttérben, az output utasítással (2005 újdonság volt) generáljuk egy táblát az elvégzett műveletekről: [source='sql'] create table #Results ( ClientEmployeeID int, ClientTitle nvarchar(50), ClientVacationHours smallint, ServerEmployeeID int, OriginalServerTitle nvarchar(50), UpdatedOrInsertedServerTitle nvarchar(50), ServerVacationHours smallint, Action nvarchar(10) ) go merge into MergeDemo.EmpServerSide s using MergeDemo.EmpClientSide c on s.EmployeeID = c.EmployeeID when matched and c.Title = '!torlendo!' then delete when matched and c.Title != '!torlendo!' and (c.Title != s.Title or c.VacationHours != s.VacationHours) then update set s.Title = c.Title, s.VacationHours = c.VacationHours when target not matched then insert (Title, VacationHours) values (c.Title, c.VacationHours) output c.EmployeeID, c.Title, c.VacationHours, coalesce(inserted.EmployeeID, deleted.EmployeeID), deleted.Title, inserted.Title, inserted.VacationHours, $ACTION into #Results ; [/source] A $ACTION a merge ajándéka, segít megmutatni, mit művelt a háttérben. Lássuk hát (csúnya, széles, elnézést érte, view plain-nel, és megfelelően nagy felbontásnál tűrhető): [source='c'] (9 row(s) affected) ClientEmployeeID ClientTitle ClientVacationHours ServerEmployeeID OriginalServerTitle UpdatedOrInsertedServerTitle ServerVacationHours Action ---------------- -------------------------------------------------- ------------------- ---------------- -------------------------------------------------- -------------------------------------------------- ------------------- ---------- -1 Hasvakaró 0 291 NULL Hasvakaró 0 INSERT -2 Hátvakaró 0 292 NULL Hátvakaró 0 INSERT 1 !torlendo! 21 1 Production Technician - WC60 NULL NULL DELETE 2 !torlendo! 42 2 Marketing Assistant NULL NULL DELETE 3 !torlendo! 2 3 Engineering Manager NULL NULL DELETE 16 Production Supervisor - WC60 alma 80 16 Production Supervisor - WC60 Production Supervisor - WC60 alma 80 UPDATE 17 Production Technician - WC10 alma 86 17 Production Technician - WC10 Production Technician - WC10 alma 86 UPDATE 18 Production Supervisor - WC60 alma 81 18 Production Supervisor - WC60 Production Supervisor - WC60 alma 81 UPDATE 19 Production Technician - WC10 alma 87 19 Production Technician - WC10 Production Technician - WC10 alma 87 UPDATE [/source] Szépen, a terveknek megfelően futottak le a parancsok, örülünk. :)