Skenario
Sekali waktu ada database Pementasan di sebuah perusahaan kecil yang berpartisipasi dalam proses ETL, bertindak sebagai katalog penerima untuk berbagai format file dari sejumlah sumber pihak ketiga. E ditangani melalui paket DTS, dengan beberapa struktur kontrol untuk audit atau kontrol, tetapi dianggap "Cukup Baik" dan untuk semua maksud dan tujuan, itu.
Data yang disediakan oleh bagian E dimaksudkan untuk konsumsi oleh aplikasi tunggal, dikembangkan dan dikelola oleh segelintir programmer muda dan mampu. Meskipun kurang memiliki pengalaman atau pengetahuan tentang teknik pergudangan data saat itu, mereka menetapkan dan menciptakan proses T dan L mereka sendiri dari kode aplikasi. Terobosan maju, para insinyur perangkat lunak pemula menemukan apa yang orang luar mungkin sebut "roda kurang ideal," tetapi dengan "Cukup Baik" sebagai tingkat layanan yang selalu ada, mereka mampu menyediakan kerangka kerja operasional.
Untuk sementara waktu, semua baik di dunia yang sangat erat, dengan katalog Pementasan berpesta pada data selusin pihak ketiga, pada gilirannya mendapat umpan dari aplikasi. Seiring bertambahnya aplikasi, begitu juga selera, tetapi dengan pengembang ksatria putih yang terampil mengawasi sistem, selera ini ditangani dengan cepat dan dalam banyak kasus, bahkan dengan baik.
Tetapi zaman keemasan tidak bisa bertahan selamanya, tentu saja. Dengan kemakmuran yang diberikan oleh aplikasi yang sukses, bisnis tumbuh dan berkembang. Ketika ia tumbuh, lingkungan Pementasan dan aplikasi dipaksa untuk tumbuh bersamanya. Untuk semua kewaspadaan mereka, segelintir pengembang pahlawan tidak bisa mengimbangi mempertahankan sistem yang sekarang luas, dan konsumen menjadi berhak atas data mereka. Bukan lagi soal apa yang mereka butuhkan atau bahkan inginkan, tetapi rakyat merasa bahwa mereka layak mendapatkannya, menuntut lebih banyak lagi.
Berbekal sedikit lebih dari pundi-pundi penuh barang curian, bisnis menjangkau ke pasar, mempekerjakan pengembang dan administrator untuk membantu mendukung sistem yang terus berkembang. Tentara bayaran dari setiap etos berbondong-bondong ke perusahaan, tetapi dengan lonjakan pertumbuhan ini tidak banyak yang menghalangi bimbingan ahli yang tersedia. Pengembang dan administrator baru berjuang untuk memahami seluk-beluk suite buatan rumah, sampai frustrasi mengakibatkan perang habis-habisan. Masing-masing departemen mulai berusaha untuk menyelesaikan setiap masalah sendirian, melakukan lebih banyak untuk bekerja melawan satu sama lain daripada bekerja dengan satu sama lain. Satu proyek atau inisiatif akan dilaksanakan dalam beberapa cara yang berbeda, masing-masing sedikit berbeda dari yang berikutnya. Ketegangan dari semuanya terbukti terlalu banyak untuk beberapa ksatria putih dan ketika mereka jatuh, kekaisaran hancur. Segera, sistem berantakan,
Terlepas dari transformasi bidang-bidang janji ini ke kode spageti berdarah, perusahaan tetap bertahan. Lagi pula, "Cukup Baik."
Tantangan
Beberapa perubahan rezim lagi dan mempekerjakan tenaga kemudian, saya menemukan diri saya dalam pekerjaan perusahaan. Sudah bertahun-tahun sejak perang besar, tetapi kerusakan yang terjadi masih sangat terlihat. Saya telah berhasil mengatasi beberapa kelemahan dalam bagian E dari sistem dan menambahkan beberapa tabel kontrol sementara dengan kedok meningkatkan paket DTS ke SSIS, yang sekarang sedang digunakan oleh beberapa profesional pergudangan data aktual saat mereka membuat normal dan penggantian T dan L yang didokumentasikan.
Rintangan pertama adalah mengimpor data dari file pihak ketiga dengan cara yang tidak akan memotong nilai-nilai atau mengubah tipe data asli, tetapi juga menyertakan beberapa kunci kontrol untuk memuat ulang dan membersihkan. Ini semua baik dan bagus, tetapi aplikasi harus dapat mengakses tabel-tabel baru ini secara mulus dan transparan. Paket DTS dapat mengisi tabel, yang kemudian langsung dibaca oleh aplikasi. Pembaruan SSIS perlu dilakukan secara paralel karena alasan QA, tetapi paket-paket baru ini mencakup berbagai kunci kontrol dan juga meningkatkan skema partisi, belum lagi perubahan metadata yang sebenarnya saja bisa cukup signifikan untuk menjamin tabel baru sekaligus, jadi tabel baru digunakan untuk paket SSIS baru.
Dengan impor data yang andal kini berfungsi dan digunakan oleh tim pergudangan, tantangan sesungguhnya adalah melayani data baru ke aplikasi yang mengakses lingkungan Pementasan secara langsung, dengan dampak minimal (alias "Tidak") pada kode aplikasi. Untuk ini, saya telah memilih untuk menggunakan tampilan, mengubah nama tabel seperti dbo.DailyTransaction
untuk dbo.DailyTranscation_LEGACY
dan menggunakan kembali dbo.DailyTransaction
nama objek untuk tampilan, yang secara efektif hanya memilih semuanya dari sekarangLEGACY
meja yang ditunjuk. Karena memuat ulang tahun data yang terkandung dalam tabel ini bukan merupakan pilihan dari perspektif bisnis, karena tabel yang dipopulasi dan dipartisi SSIS masuk ke produksi, impor DTS lama dimatikan dan aplikasi harus dapat mengakses data baru di tabel baru juga. Pada titik ini, pandangan diperbarui untuk memilih data dari tabel baru (katakanlah,, dbo.DailyTransactionComplete
misalnya) ketika tersedia dan pilih dari tabel lama ketika tidak.
Akibatnya, sesuatu seperti berikut sedang dilakukan:
CREATE VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransaction_LEGACY l
WHERE NOT EXISTS ( SELECT 1
FROM dbo.DailyTransactionComplete t
WHERE t.FileDate = l.FileDate );
Meskipun logis, ini tidak bekerja dengan baik dalam sejumlah kasus agregasi, umumnya menghasilkan rencana eksekusi yang melakukan pemindaian indeks penuh terhadap data dalam tabel legacy. Ini mungkin baik untuk beberapa lusin juta rekaman, tetapi tidak begitu banyak untuk beberapa lusin juta rekaman. Karena yang terakhir sebenarnya adalah kasusnya, saya harus menggunakan ... "kreatif," yang menuntun saya untuk menciptakan tampilan yang diindeks.
Berikut ini adalah sedikit kasus uji yang telah saya siapkan, termasuk FileDate
kunci kontrol yang telah porting ke DateCode_FK
port yang kompatibel dengan Data Warehouse untuk menggambarkan betapa sedikit sekali saya peduli tentang pertanyaan terhadap tabel baru yang dapat ditagih untuk sementara waktu:
USE tempdb;
GO
SET NOCOUNT ON;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction_LEGACY'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.DailyTransaction_LEGACY;
CREATE TABLE dbo.DailyTransaction_LEGACY
(
DailyTransaction_PK BIGINT IDENTITY( 1, 1 ) NOT NULL,
FileDate DATETIME NOT NULL,
Foo INT NOT NULL
);
INSERT INTO dbo.DailyTransaction_LEGACY ( FileDate, Foo )
SELECT DATEADD( DAY, ( 1 - ROW_NUMBER()
OVER( ORDER BY so1.object_id ) - 800 ) % 1000,
CONVERT( DATE, GETDATE() ) ),
so1.object_id % 1000 + so2.object_id % 1000
FROM sys.all_objects so1
CROSS JOIN sys.all_objects so2;
ALTER TABLE dbo.DailyTransaction_LEGACY
ADD CONSTRAINT PK__DailyTrainsaction
PRIMARY KEY CLUSTERED ( DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransactionComplete'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.DailyTransactionComplete;
CREATE TABLE dbo.DailyTransactionComplete
(
DailyTransaction_PK BIGINT IDENTITY( 1, 1 ) NOT NULL,
DateCode_FK INTEGER NOT NULL,
Foo INTEGER NOT NULL
);
INSERT INTO dbo.DailyTransactionComplete ( DateCode_FK, Foo )
SELECT TOP 100000
CONVERT( INTEGER, CONVERT( VARCHAR( 8 ), DATEADD( DAY,
( 1 - ROW_NUMBER() OVER( ORDER BY so1.object_id ) ) % 100,
GETDATE() ), 112 ) ),
so1.object_id % 1000
FROM sys.all_objects so1
CROSS JOIN sys.all_objects so2;
ALTER TABLE dbo.DailyTransactionComplete
ADD CONSTRAINT PK__DailyTransaction
PRIMARY KEY CLUSTERED ( DateCode_FK, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO
Pada sandbox lokal saya, di atas membuat saya meja warisan dengan sekitar 4,4 juta baris dan tabel baru yang berisi 0,1 juta baris, dengan beberapa tumpang tindih dari DateCode_FK
/ FileDate
nilai.
A MAX( FileDate )
terhadap tabel lawas tanpa indeks tambahan berjalan tentang apa yang saya harapkan.
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput DATETIME;
SELECT @ConsumeOutput = MAX( FileDate )
FROM dbo.DailyTransaction_LEGACY;
SET STATISTICS IO, TIME OFF;
GO
Tabel 'DailyTransaction_LEGACY'. Pindai hitungan 1, bacaan logis 9228, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 889 ms, waktu yang berlalu = 886 ms.
Meletakkan indeks sederhana di atas meja membuat segalanya lebih baik. Masih memindai, tetapi memindai satu catatan, bukan 4,4 juta catatan. Saya senang dengan itu.
CREATE NONCLUSTERED INDEX IX__DailyTransaction__FileDate
ON dbo.DailyTransaction_LEGACY ( FileDate );
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput DATETIME;
SELECT @ConsumeOutput = MAX( FileDate )
FROM dbo.DailyTransaction_LEGACY;
SET STATISTICS IO, TIME OFF;
GO
SQL Server menguraikan dan mengkompilasi waktu: Waktu CPU = 0 ms, waktu yang berlalu = 1 ms. Tabel 'DailyTransaction_LEGACY'. Pindai hitungan 1, bacaan logis 3, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 0 ms.
Dan sekarang, membuat tampilan sehingga pengembang tidak perlu mengubah kode apa pun karena itu tampaknya akan menjadi akhir dunia seperti yang kita kenal. Semacam bencana.
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate = CONVERT(
DATETIME, CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ), Foo
FROM dbo.DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransaction_LEGACY l
WHERE NOT EXISTS ( SELECT 1
FROM dbo.DailyTransactionComplete t
WHERE CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
t.DateCode_FK ), 112 ) = l.FileDate );
GO
Ya, sub kueri itu sangat buruk, tapi ini bukan masalahnya dan saya mungkin hanya akan membuat kolom yang dihitung terus-menerus dan melemparkan indeks di atasnya untuk tujuan itu ketika masalah sebenarnya diselesaikan. Jadi tanpa basa-basi lagi,
Masalah
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
SQL Server menguraikan dan mengkompilasi waktu: Waktu CPU = 0 ms, waktu yang berlalu = 4 ms. Tabel 'DailyTransaction_LEGACY'. Pindai hitungan 1, bacaan logis 11972, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0. Tabel 'Meja Kerja'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0. Tabel 'Workfile'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca-depan lob 0. Tabel 'DailyTransactionComplete'. Pindai hitungan 2, bacaan logis 620, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 983 ms, waktu yang berlalu = 983 ms.
Oh, begitu, Sql Server mencoba memberi tahu saya bahwa apa yang saya lakukan adalah bodoh. Sementara saya sebagian besar setuju, itu tidak mengubah kesulitan saya. Ini benar-benar berfungsi dengan sangat baik untuk kueri di mana tampilan FileDate
pada dbo.DailyTransaction
termasuk dalam predikat, tetapi sementara MAX
rencana itu cukup buruk, TOP
rencana mengirimkan semuanya berjalan ke selatan. Selatan nyata.
SET STATISTICS IO, TIME ON;
SELECT TOP 10 FileDate
FROM dbo.DailyTransaction
GROUP BY FileDate
ORDER BY FileDate DESC
SET STATISTICS IO, TIME OFF;
GO
Tabel 'DailyTransactionComplete'. Pindai hitungan 2, bacaan logis 1800110, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan lob baca-depan 0. Tabel 'DailyTransaction_LEGACY'. Pindai hitungan 1, bacaan logis 1254, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0. Tabel 'Meja Kerja'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0. Tabel 'Workfile'. Pindai jumlah 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca-depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 109559 ms, waktu yang berlalu = 109664 ms.
Saya sebutkan mendapatkan "kreatif" sebelumnya, yang mungkin menyesatkan. Apa yang ingin saya katakan adalah "lebih bodoh," sehingga upaya saya untuk membuat tampilan ini berfungsi selama operasi agregasi adalah untuk membuat tampilan pada tabel dbo.DailyTransactionComplete
dan dbo.DailyTransaction_LEGACY
, skema mengikat dan mengindeks yang terakhir, kemudian menggunakan tampilan tersebut di tampilan lain dengan NOEXPAND
petunjuk pada tampilan warisan. Sementara itu lebih atau kurang bekerja untuk apa yang perlu dilakukan untuk saat ini, saya menemukan seluruh "solusi" cukup mengecewakan, memuncak dengan yang berikut:
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'v_DailyTransactionComplete'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.v_DailyTransactionComplete AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.v_DailyTransactionComplete
AS SELECT DailyTransaction_PK, FileDate = CONVERT( DATETIME,
CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ),
Foo
FROM dbo.DailyTransactionComplete;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'v_DailyTransaction_LEGACY'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.v_DailyTransaction_LEGACY AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.v_DailyTransaction_LEGACY
WITH SCHEMABINDING
AS SELECT l.DailyTransaction_PK,
l.FileDate,
l.Foo,
CountBig = COUNT_BIG( * )
FROM dbo.DailyTransaction_LEGACY l
INNER JOIN dbo.DailyTransactionComplete n
ON l.FileDate <> CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
n.DateCode_FK ), 112 )
GROUP BY l.DailyTransaction_PK,
l.FileDate,
l.Foo;
GO
CREATE UNIQUE CLUSTERED INDEX CI__v_DailyTransaction_LEGACY
ON dbo.v_DailyTransaction_LEGACY ( FileDate, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 80 );
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.v_DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.v_DailyTransaction_LEGACY WITH ( NOEXPAND );
GO
Memaksa pengoptimal untuk menggunakan indeks yang disediakan oleh tampilan yang diindeks membuat MAX
dan TOP
masalah hilang, tetapi harus ada cara yang lebih baik untuk mencapai apa yang saya coba lakukan di sini. Benar-benar ada saran / omelan akan sangat dihargai !!
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
Tabel 'v_DailyTransaction_LEGACY'. Pindai hitungan 1, bacaan logis 3, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan lob baca-depan 0. Tabel 'DailyTransactionComplete'. Pindai hitungan 1, bacaan logis 310, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 31 ms, waktu yang berlalu = 36 ms.
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT TOP 10 @ConsumeOutput1 = FileDate
FROM dbo.DailyTransaction
GROUP BY FileDate
ORDER BY FileDate DESC
SET STATISTICS IO, TIME OFF;
GO
Tabel 'v_DailyTransaction_LEGACY'. Pindai hitungan 1, bacaan logis 101, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0. Tabel 'Meja Kerja'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0. Tabel 'Workfile'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca-depan lob 0. Tabel 'DailyTransactionComplete'. Pindai hitungan 1, bacaan logis 310, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 63 ms, waktu yang berlalu = 66 ms.
TL; DR:
Bantu saya memahami apa yang harus saya lakukan untuk membuat kueri agregasi pada tampilan pertama yang saya sebutkan berjalan dalam jumlah waktu yang wajar dengan pemanfaatan sumber daya I / O yang masuk akal.
sumber
Jawaban:
Menulis ulang
NOT EXISTS
sebagaiDISTINCT
gabungan ketimpangan memang memungkinkan tampilan diindeks, tetapi ada alasan bagus hal ini tidak umum dilakukan.Rencana eksekusi yang dihasilkan untuk membangun indeks pada tampilan tidak dapat dihindari mengerikan. Ketidaksetaraan memaksa loop bersarang fisik bergabung, yang dengan pengecualian satu nilai, adalah persilangan silang. Mengurangi produk dengan grup yang berbeda atau setara dengan akan menghasilkan hasil yang benar, dengan asumsi kolom gabungan tidak dapat dibatalkan (seperti dalam kode contoh), tetapi tidak akan pernah efisien. Inefisiensi ini hanya akan bertambah buruk seiring berjalannya waktu dan tabel yang terlibat menjadi lebih besar.
Masalah serupa memengaruhi rencana eksekusi untuk pernyataan DML yang memengaruhi tabel yang direferensikan oleh tampilan (karena tampilan harus disinkronkan ke tabel dasar setiap saat di SQL Server). Lihatlah rencana eksekusi yang dihasilkan untuk menambah atau memodifikasi satu baris dalam tabel mana pun untuk melihat apa yang saya maksud.
Pada tingkat tinggi, masalah yang Anda hadapi adalah bahwa pengoptimal permintaan SQL Server tidak selalu menghasilkan rencana yang baik atas pandangan yang mencakup a
UNION ALL
. Banyak optimasi yang kami terima begitu saja (sepertiMAX
->TOP (1)
) sama sekali tidak dilaksanakan di seluruh serikat pekerja.Untuk setiap masalah yang Anda selesaikan, Anda akan menemukan kasus lain di mana optimasi normal dan yang diharapkan tidak terjadi, menghasilkan rencana eksekusi dengan kinerja yang sangat buruk. Solusi yang jelas adalah untuk menghindari penggunaan kesatuan dalam pandangan. Bagaimana Anda menerapkan ini dalam kasus Anda tergantung pada perincian yang, meskipun ada perincian dalam pertanyaan, mungkin hanya diketahui oleh Anda.
Jika Anda memiliki ruang, salah satu solusinya adalah mempertahankan
complete
danlegacy
mendasarkan tabel secara terpisah (termasuk logika yang tidak ada). Ini memang menghasilkan duplikasi data, dan dilengkapi dengan masalah sinkronisasi, tetapi dalam pengalaman saya ini jauh lebih mudah untuk diselesaikan dengan kuat daripada mencoba untuk mendapatkan pandangan serikat pekerja untuk menghasilkan rencana eksekusi yang baik untuk berbagai pertanyaan dalam semua situasi (atau bahkan sebagian besar).SQL Server menyediakan sejumlah fitur untuk membantu sinkronisasi data, karena saya yakin Anda tahu, termasuk pelacakan perubahan, perubahan pengambilan data, pemicu ... dan seterusnya. Spesifikasi implementasi berada di luar forum ini. Poin penting adalah menyajikan pengoptimal dengan tabel dasar, bukan menyatukan semua tampilan.
sumber