Masalah ini adalah tentang mengikuti tautan antar item. Ini menempatkannya dalam ranah grafik dan pemrosesan grafik. Secara khusus, seluruh dataset membentuk grafik dan kami sedang mencari komponen dari grafik itu. Ini dapat diilustrasikan oleh sebidang data sampel dari pertanyaan.
Pertanyaannya mengatakan kita bisa mengikuti GroupKey atau RecordKey untuk menemukan baris lain yang berbagi nilai itu. Jadi kita bisa memperlakukan keduanya sebagai simpul dalam grafik. Pertanyaan selanjutnya menjelaskan bagaimana GroupKeys 1–3 memiliki SupergroupKey yang sama. Ini dapat dilihat sebagai gugusan di sebelah kiri yang bergabung dengan garis tipis. Gambar juga menunjukkan dua komponen lain (SupergroupKey) yang dibentuk oleh data asli.
SQL Server memiliki beberapa kemampuan pemrosesan grafik yang dibangun ke T-SQL. Namun, saat ini cukup sedikit, dan tidak membantu dengan masalah ini. SQL Server juga memiliki kemampuan untuk memanggil R dan Python, dan paket yang kaya & kuat tersedia untuk mereka. Salah satunya adalah igraph . Ini ditulis untuk "penanganan cepat grafik besar, dengan jutaan simpul dan tepi ( tautan )."
Menggunakan R dan igraph saya dapat memproses satu juta baris dalam 2 menit 22 detik dalam pengujian lokal 1 . Inilah perbandingannya dengan solusi terbaik saat ini:
Record Keys Paul White R
------------ ---------- --------
Per question 15ms ~220ms
100 80ms ~270ms
1,000 250ms 430ms
10,000 1.4s 1.7s
100,000 14s 14s
1M 2m29 2m22s
1M n/a 1m40 process only, no display
The first column is the number of distinct RecordKey values. The number of rows
in the table will be 8 x this number.
Saat memproses baris 1M, 1m40 digunakan untuk memuat & memproses grafik, dan memperbarui tabel. Diperlukan 42s untuk mengisi tabel hasil SSMS dengan output.
Pengamatan Task Manager sementara 1M baris diproses menunjukkan sekitar 3GB memori kerja diperlukan. Ini tersedia di sistem ini tanpa paging.
Saya bisa mengkonfirmasi penilaian Ypercube tentang pendekatan CTE rekursif. Dengan beberapa ratus tombol rekam, ia menghabiskan 100% CPU dan semua RAM yang tersedia. Akhirnya tempdb tumbuh lebih dari 80GB dan SPID jatuh.
Saya menggunakan meja Paul dengan kolom SupergroupKey sehingga ada perbandingan yang adil antara solusi.
Untuk beberapa alasan R keberatan dengan aksen pada Poincaré. Mengubahnya menjadi "e" biasa memungkinkannya untuk berjalan. Saya tidak menyelidiki karena itu tidak berhubungan dengan masalah yang dihadapi. Saya yakin ada solusinya.
Ini kodenya
-- This captures the output from R so the base table can be updated.
drop table if exists #Results;
create table #Results
(
Component int not NULL,
Vertex varchar(12) not NULL primary key
);
truncate table #Results; -- facilitates re-execution
declare @Start time = sysdatetimeoffset(); -- for a 'total elapsed' calculation.
insert #Results(Component, Vertex)
exec sp_execute_external_script
@language = N'R',
@input_data_1 = N'select GroupKey, RecordKey from dbo.Example',
@script = N'
library(igraph)
df.g <- graph.data.frame(d = InputDataSet, directed = FALSE)
cpts <- components(df.g, mode = c("weak"))
OutputDataSet <- data.frame(cpts$membership)
OutputDataSet$VertexName <- V(df.g)$name
';
-- Write SuperGroupKey to the base table, as other solutions do
update e
set
SupergroupKey = r.Component
from dbo.Example as e
inner join #Results as r
on r.Vertex = e.RecordKey;
-- Return all rows, as other solutions do
select
e.SupergroupKey,
e.GroupKey,
e.RecordKey
from dbo.Example as e;
-- Calculate the elapsed
declare @End time = sysdatetimeoffset();
select Elapse_ms = DATEDIFF(MILLISECOND, @Start, @End);
Inilah yang dilakukan kode R
@input_data_1
adalah cara SQL Server mentransfer data dari tabel ke kode R dan menerjemahkannya ke bingkai data R yang disebut InputDataSet.
library(igraph)
mengimpor perpustakaan ke lingkungan eksekusi R.
df.g <- graph.data.frame(d = InputDataSet, directed = FALSE)
memuat data ke objek igraph. Ini adalah grafik yang tidak diarahkan karena kita dapat mengikuti tautan dari grup ke record atau record ke group. InputDataSet adalah nama default SQL Server untuk dataset yang dikirim ke R.
cpts <- components(df.g, mode = c("weak"))
proses grafik untuk menemukan sub-grafik terpisah (komponen) dan langkah-langkah lainnya.
OutputDataSet <- data.frame(cpts$membership)
SQL Server mengharapkan bingkai data kembali dari R. Nama default-nya adalah OutputDataSet. Komponen disimpan dalam vektor yang disebut "keanggotaan". Pernyataan ini menerjemahkan vektor ke bingkai data.
OutputDataSet$VertexName <- V(df.g)$name
V () adalah vektor simpul dalam grafik - daftar GroupKeys dan RecordKeys. Ini menyalinnya ke dalam bingkai data ouput, membuat kolom baru bernama VertexName. Ini adalah kunci yang digunakan untuk mencocokkan dengan tabel sumber untuk memperbarui SupergroupKey.
Saya bukan ahli R. Kemungkinan ini bisa dioptimalkan.
Data Uji
Data OP digunakan untuk validasi. Untuk tes skala saya menggunakan skrip berikut.
drop table if exists Records;
drop table if exists Groups;
create table Groups(GroupKey int NOT NULL primary key);
create table Records(RecordKey varchar(12) NOT NULL primary key);
go
set nocount on;
-- Set @RecordCount to the number of distinct RecordKey values desired.
-- The number of rows in dbo.Example will be 8 * @RecordCount.
declare @RecordCount int = 1000000;
-- @Multiplier was determined by experiment.
-- It gives the OP's "8 RecordKeys per GroupKey and 4 GroupKeys per RecordKey"
-- and allows for clashes of the chosen random values.
declare @Multiplier numeric(4, 2) = 2.7;
-- The number of groups required to reproduce the OP's distribution.
declare @GroupCount int = FLOOR(@RecordCount * @Multiplier);
-- This is a poor man's numbers table.
insert Groups(GroupKey)
select top(@GroupCount)
ROW_NUMBER() over (order by (select NULL))
from sys.objects as a
cross join sys.objects as b
--cross join sys.objects as c -- include if needed
declare @c int = 0
while @c < @RecordCount
begin
-- Can't use a set-based method since RAND() gives the same value for all rows.
-- There are better ways to do this, but it works well enough.
-- RecordKeys will be 10 letters, a-z.
insert Records(RecordKey)
select
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND())) +
CHAR(97 + (26*RAND()));
set @c += 1;
end
-- Process each RecordKey in alphabetical order.
-- For each choose 8 GroupKeys to pair with it.
declare @RecordKey varchar(12) = '';
declare @Groups table (GroupKey int not null);
truncate table dbo.Example;
select top(1) @RecordKey = RecordKey
from Records
where RecordKey > @RecordKey
order by RecordKey;
while @@ROWCOUNT > 0
begin
print @Recordkey;
delete @Groups;
insert @Groups(GroupKey)
select distinct C
from
(
-- Hard-code * from OP's statistics
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
union all
select FLOOR(RAND() * @GroupCount)
) as T(C);
insert dbo.Example(GroupKey, RecordKey)
select
GroupKey, @RecordKey
from @Groups;
select top(1) @RecordKey = RecordKey
from Records
where RecordKey > @RecordKey
order by RecordKey;
end
-- Rebuild the indexes to have a consistent environment
alter index iExample on dbo.Example rebuild partition = all
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
-- Check what we ended up with:
select COUNT(*) from dbo.Example; -- Should be @RecordCount * 8
-- Often a little less due to random clashes
select
ByGroup = AVG(C)
from
(
select CONVERT(float, COUNT(1) over(partition by GroupKey))
from dbo.Example
) as T(C);
select
ByRecord = AVG(C)
from
(
select CONVERT(float, COUNT(1) over(partition by RecordKey))
from dbo.Example
) as T(C);
Saya baru saja menyadari bahwa saya mendapatkan rasio yang salah dari definisi OP. Saya tidak percaya ini akan mempengaruhi timing. Catatan & Grup simetris dengan proses ini. Untuk algoritme mereka semua hanya simpul dalam grafik.
Dalam pengujian data selalu membentuk komponen tunggal. Saya percaya ini karena distribusi data yang seragam. Jika alih-alih rasio statis 1: 8 yang dikodekan ke dalam rutinitas pembangkitan, saya telah membiarkan rasionya bervariasi, lebih mungkin ada komponen lebih lanjut.
1 Spesifikasi mesin: Microsoft SQL Server 2017 (RTM-CU12), Edisi Pengembang (64-bit), Windows 10 Home. RAM 16GB, SSD, 4 core hyperthreaded i7, 2,8GHz nominal. Pengujian adalah satu-satunya item yang berjalan pada saat itu, selain aktivitas sistem normal (sekitar 4% CPU).