Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Menyesuaikan Pasokan Dengan Permintaan — Solusi, Bagian 2

[ Langsung ke:Tantangan asli | Solusi:Bagian 1 | Bagian 2 | Bagian 3]

Dalam artikel ini, saya melanjutkan liputan solusi untuk tantangan penawaran dan permintaan yang menarik dari Peter Larsson. Sekali lagi terima kasih kepada Luca, Kamil Kosno, Daniel Brown, Brian Walker, Joe Obbish, Rainer Hoffmann, Paul White, Charlie, dan Peter Larsson, yang telah mengirimkan solusi Anda.

Bulan lalu saya membahas solusi berdasarkan persimpangan interval, menggunakan tes persimpangan interval berbasis predikat klasik. Saya akan menyebut solusi itu sebagai persimpangan klasik . Pendekatan simpang interval klasik menghasilkan denah dengan skala kuadrat (N^2). Saya menunjukkan kinerjanya yang buruk terhadap input sampel mulai dari 100K hingga 400K baris. Butuh solusi 931 detik untuk menyelesaikan input 400K-baris! Bulan ini saya akan mulai dengan mengingatkan Anda secara singkat tentang solusi bulan lalu dan mengapa skala dan kinerjanya sangat buruk. Saya kemudian akan memperkenalkan pendekatan berdasarkan revisi tes persimpangan interval. Pendekatan ini digunakan oleh Luca, Kamil, dan mungkin juga Daniel, dan memungkinkan solusi dengan kinerja dan skala yang jauh lebih baik. Saya akan menyebut solusi itu sebagai persimpangan yang direvisi .

Masalah Dengan Uji Persimpangan Klasik

Mari kita mulai dengan pengingat cepat tentang solusi bulan lalu.

Saya menggunakan indeks berikut pada tabel input dbo.Auctions untuk mendukung penghitungan total berjalan yang menghasilkan interval permintaan dan penawaran:

-- Index to support solution
 
CREATE UNIQUE NONCLUSTERED INDEX idx_Code_ID_i_Quantity
  ON dbo.Auctions(Code, ID)
  INCLUDE(Quantity);
 
-- Enable batch-mode Window Aggregate
 
CREATE NONCLUSTERED COLUMNSTORE INDEX idx_cs
  ON dbo.Auctions(ID)
  WHERE ID = -1 AND ID = -2;

Kode berikut memiliki solusi lengkap yang menerapkan pendekatan persimpangan interval klasik:

-- Drop temp tables if exist
 
SET NOCOUNT ON;
DROP TABLE IF EXISTS #MyPairings, #Demand, #Supply;
GO
 
WITH D0 AS
 
-- D0 computes running demand as EndDemand
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndDemand
  FROM dbo.Auctions
  WHERE Code = 'D'
),
 
-- D extracts prev EndDemand as StartDemand, expressing start-end demand as an interval
 
D AS
(
  SELECT ID, Quantity, EndDemand - Quantity AS StartDemand, EndDemand
  FROM D0
)
SELECT ID,
  CAST(ISNULL(StartDemand, 0.0) AS DECIMAL(19, 6)) AS StartDemand,
  CAST(ISNULL(EndDemand,   0.0) AS DECIMAL(19, 6)) AS EndDemand
INTO #Demand
FROM D;
 
WITH S0 AS
 
-- S0 computes running supply as EndSupply
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndSupply
  FROM dbo.Auctions
  WHERE Code = 'S'
),
 
-- S extracts prev EndSupply as StartSupply, expressing start-end supply as an interval
 
S AS
(
  SELECT ID, Quantity, EndSupply - Quantity AS StartSupply, EndSupply
  FROM S0
)
SELECT ID, 
  CAST(ISNULL(StartSupply, 0.0) AS DECIMAL(19, 6)) AS StartSupply,
  CAST(ISNULL(EndSupply, 0.0) AS DECIMAL(19, 6)) AS EndSupply
INTO #Supply
FROM S;
 
CREATE UNIQUE CLUSTERED INDEX idx_cl_ES_ID ON #Supply(EndSupply, ID);
 
-- Trades are the overlapping segments of the intersecting intervals
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S WITH (FORCESEEK)
    ON D.StartDemand < S.EndSupply AND D.EndDemand > S.StartSupply;

Kode ini dimulai dengan menghitung interval permintaan dan penawaran dan menuliskannya ke tabel sementara yang disebut #Demand dan #Supply. Kode kemudian membuat indeks berkerumun di #Supply dengan EndSupply sebagai kunci utama untuk mendukung pengujian persimpangan sebaik mungkin. Terakhir, kode tersebut menggabungkan #Supply dan #Demand, mengidentifikasi perdagangan sebagai segmen yang tumpang tindih dari interval berpotongan, menggunakan predikat gabungan berikut berdasarkan uji persimpangan interval klasik:

D.StartDemand < S.EndSupply AND D.EndDemand > S.StartSupply

Rencana untuk solusi ini ditunjukkan pada Gambar 1.

Gambar 1:Rencana kueri untuk solusi berdasarkan persimpangan klasik

Seperti yang saya jelaskan bulan lalu, di antara dua predikat rentang yang terlibat, hanya satu yang dapat digunakan sebagai predikat pencarian sebagai bagian dari operasi pencarian indeks, sedangkan yang lain harus diterapkan sebagai predikat residual. Anda dapat dengan jelas melihat ini dalam rencana untuk kueri terakhir pada Gambar 1. Itu sebabnya saya hanya repot-repot membuat satu indeks di salah satu tabel. Saya juga memaksa penggunaan pencarian di indeks yang saya buat menggunakan petunjuk FORCESEEK. Jika tidak, pengoptimal dapat mengabaikan indeks itu dan membuatnya sendiri menggunakan operator Index Spool.

Paket ini memiliki kompleksitas kuadrat karena per baris di satu sisi—#Permintaan dalam kasus kami—pencarian indeks harus mengakses rata-rata setengah baris di sisi lain—#Penawaran dalam kasus kami—berdasarkan predikat pencarian, dan mengidentifikasi pertandingan final dengan menerapkan predikat residual.

Jika masih belum jelas bagi Anda mengapa rencana ini memiliki kompleksitas kuadrat, mungkin penggambaran visual dari pekerjaan tersebut dapat membantu, seperti yang ditunjukkan pada Gambar 2.

Gambar 2:Ilustrasi pekerjaan dengan solusi berdasarkan persimpangan klasik

Ilustrasi ini mewakili pekerjaan yang dilakukan oleh Penggabungan Nested Loops dalam rencana untuk kueri terakhir. Tumpukan #Demand adalah input luar dari gabungan dan indeks berkerumun pada #Supply (dengan EndSupply sebagai kunci utama) adalah input dalam. Garis merah mewakili aktivitas pencarian indeks yang dilakukan per baris di #Permintaan dalam indeks pada #Supply dan baris yang mereka akses sebagai bagian dari pemindaian rentang di daun indeks. Menuju nilai StartDemand yang sangat rendah di #Demand, pemindaian rentang perlu mengakses hampir semua baris di #Supply. Menuju nilai StartDemand yang sangat tinggi di #Demand, pemindaian rentang perlu mengakses hampir nol baris di #Supply. Rata-rata, pemindaian rentang perlu mengakses sekitar setengah baris di #Supply per baris dalam permintaan. Dengan baris permintaan D dan baris penawaran S, jumlah baris yang diakses adalah D + D*S/2. Itu di samping biaya pencarian yang membawa Anda ke baris yang cocok. Misalnya, saat mengisi dbo.Auctions dengan 200.000 baris permintaan dan 200.000 baris pasokan, ini berarti Penggabungan Nested Loops mengakses 200.000 baris permintaan + 200.000*200.000/2 baris pasokan, atau 200.000 + 200.000^2/2 =20.000.200.000 baris yang diakses. Ada banyak pemindaian ulang baris persediaan yang terjadi di sini!

Jika Anda ingin tetap berpegang pada ide persimpangan interval tetapi mendapatkan kinerja yang baik, Anda perlu menemukan cara untuk mengurangi jumlah pekerjaan yang dilakukan.

Dalam solusinya, Joe Obbish mengelompokkan interval berdasarkan panjang interval maksimum. Dengan cara ini dia dapat mengurangi jumlah baris yang diperlukan untuk menangani dan mengandalkan pemrosesan batch. Dia menggunakan tes persimpangan interval klasik sebagai filter terakhir. Solusi Joe bekerja dengan baik selama panjang interval maksimum tidak terlalu tinggi, tetapi runtime solusi meningkat seiring dengan bertambahnya panjang interval maksimum.

Jadi, apa lagi yang bisa kamu lakukan…?

Uji Persimpangan yang Direvisi

Luca, Kamil, dan mungkin Daniel (ada bagian yang tidak jelas tentang solusi yang dipostingnya karena pemformatan situs web, jadi saya harus menebaknya) menggunakan uji perpotongan interval yang direvisi yang memungkinkan pemanfaatan pengindeksan yang jauh lebih baik.

Tes persimpangan mereka adalah disjungsi dua predikat (predikat dipisahkan oleh operator OR):

   (D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply) OR (S.StartSupply >= D.StartDemand AND S.StartSupply < D.EndDemand)

Dalam bahasa Inggris, baik pembatas awal permintaan berpotongan dengan interval suplai secara inklusif dan eksklusif (termasuk awal dan tidak termasuk akhir); atau pembatas awal pasokan bersinggungan dengan interval permintaan, secara inklusif dan eksklusif. Untuk memisahkan kedua predikat (tidak memiliki kasus yang tumpang tindih) namun tetap lengkap dalam mencakup semua kasus, Anda dapat menyimpan operator =hanya di satu atau yang lain, misalnya:

   (D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply) OR (S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand)

Tes persimpangan interval yang direvisi ini cukup mendalam. Setiap predikat berpotensi dapat menggunakan indeks secara efisien. Pertimbangkan predikat 1

D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply
^^^^^^^^^^^^^                      ^^^^^^^^^^^^^

Dengan asumsi Anda membuat indeks penutup pada #Demand dengan StartDemand sebagai kunci utama, Anda berpotensi mendapatkan Nested Loops bergabung dengan menerapkan pekerjaan yang diilustrasikan pada Gambar 3.


Gambar 3:Ilustrasi kerja teoritis yang diperlukan untuk memproses predikat 1

Ya, Anda masih membayar untuk pencarian di indeks #Demand per baris di #Supply, tetapi rentang pemindaian di akses daun indeks hampir memisahkan subset baris. Tidak ada pemindaian ulang baris!

Situasinya mirip dengan predikat 2:

S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand
^^^^^^^^^^^^^                     ^^^^^^^^^^^^^

Dengan asumsi Anda membuat indeks penutup di #Supply dengan StartSupply sebagai kunci utama, Anda berpotensi dapat bergabung dengan Nested Loops menerapkan pekerjaan yang diilustrasikan pada Gambar 4.


Gambar 4:Ilustrasi kerja teoritis yang diperlukan untuk memproses predikat 2

Juga, di sini Anda membayar untuk pencarian di indeks #Supply per baris di #Demand, dan kemudian rentang memindai di akses daun indeks subset baris yang hampir terputus-putus. Sekali lagi, tidak ada pemindaian ulang baris!

Dengan asumsi baris permintaan D dan baris penawaran S, pekerjaan dapat digambarkan sebagai:

Number of index seek operations: D  + S
Number of rows accessed:         2D + 2S

Jadi kemungkinan besar, bagian terbesar dari biaya di sini adalah biaya I/O yang terkait dengan pencarian. Tetapi bagian dari pekerjaan ini memiliki penskalaan linier dibandingkan dengan penskalaan kuadrat dari kueri persimpangan interval klasik.

Tentu saja, Anda perlu mempertimbangkan biaya awal pembuatan indeks pada tabel sementara, yang memiliki n log n penskalaan karena penyortiran yang terlibat, tetapi Anda membayar bagian ini sebagai bagian awal dari kedua solusi.

Mari kita coba dan praktikkan teori ini. Mari kita mulai dengan mengisi tabel sementara #Demand dan #supply dengan interval permintaan dan penawaran (sama seperti persimpangan interval klasik) dan membuat indeks pendukung:

-- Drop temp tables if exist
 
SET NOCOUNT ON;
DROP TABLE IF EXISTS #MyPairings, #Demand, #Supply;
GO
 
WITH D0 AS
 
-- D0 computes running demand as EndDemand
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndDemand
  FROM dbo.Auctions
  WHERE Code = 'D'
),
 
-- D extracts prev EndDemand as StartDemand, expressing start-end demand as an interval
 
D AS
(
  SELECT ID, Quantity, EndDemand - Quantity AS StartDemand, EndDemand
  FROM D0
)
SELECT ID, 
  CAST(ISNULL(StartDemand, 0.0) AS DECIMAL(19, 6)) AS StartDemand,
  CAST(ISNULL(EndDemand,   0.0) AS DECIMAL(19, 6)) AS EndDemand
INTO #Demand
FROM D;
 
WITH S0 AS
 
-- S0 computes running supply as EndSupply
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndSupply
  FROM dbo.Auctions
  WHERE Code = 'S'
),
 
-- S extracts prev EndSupply as StartSupply, expressing start-end supply as an interval
 
S AS
(
  SELECT ID, Quantity, EndSupply - Quantity AS StartSupply, EndSupply
  FROM S0
)
SELECT ID, 
  CAST(ISNULL(StartSupply, 0.0) AS DECIMAL(19, 6)) AS StartSupply,
  CAST(ISNULL(EndSupply,   0.0) AS DECIMAL(19, 6)) AS EndSupply
INTO #Supply
FROM S;
 
-- Indexing
 
CREATE UNIQUE CLUSTERED INDEX idx_cl_SD_ID ON #Demand(StartDemand, ID);
CREATE UNIQUE CLUSTERED INDEX idx_cl_SS_ID ON #Supply(StartSupply, ID);

Rencana untuk mengisi tabel sementara dan membuat indeks ditunjukkan pada Gambar 5.


Gambar 5:Rencana kueri untuk mengisi tabel temp dan membuat indeks

Tidak ada kejutan di sini.

Sekarang ke pertanyaan terakhir. Anda mungkin tergoda untuk menggunakan satu kueri dengan disjungsi dua predikat yang disebutkan di atas, seperti:

SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON (D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply) OR (S.StartSupply >  D.StartDemand AND S.StartSupply < D.EndDemand);

Rencana untuk kueri ini ditunjukkan pada Gambar 6.


Gambar 6:Rencana kueri untuk kueri akhir menggunakan persimpangan yang direvisi tes, coba 1

Masalahnya adalah pengoptimal tidak tahu cara memecah logika ini menjadi dua aktivitas terpisah, masing-masing menangani predikat berbeda dan memanfaatkan indeks masing-masing secara efisien. Jadi, itu berakhir dengan produk kartesius penuh dari kedua sisi, menerapkan semua predikat sebagai predikat residual. Dengan 200.000 baris permintaan dan 200.000 baris pasokan, gabungan memproses 40.000.000.000 baris.

Trik mendalam yang digunakan oleh Luca, Kamil, dan mungkin Daniel adalah memecah logika menjadi dua pertanyaan, menyatukan hasil mereka. Di situlah penggunaan dua predikat yang terpisah menjadi penting! Anda dapat menggunakan operator UNION ALL alih-alih UNION, menghindari biaya yang tidak perlu untuk mencari duplikat. Inilah kueri yang menerapkan pendekatan ini:

SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply
 
UNION ALL
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand;

Rencana untuk kueri ini ditunjukkan pada Gambar 7.


Gambar 7:Rencana kueri untuk kueri akhir menggunakan persimpangan yang direvisi tes, coba 2

Ini hanya indah! bukan? Dan karena sangat indah, inilah seluruh solusi dari awal, termasuk pembuatan tabel sementara, pengindeksan, dan kueri terakhir:

-- Drop temp tables if exist
 
SET NOCOUNT ON;
DROP TABLE IF EXISTS #MyPairings, #Demand, #Supply;
GO
 
WITH D0 AS
 
-- D0 computes running demand as EndDemand
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndDemand
  FROM dbo.Auctions
  WHERE Code = 'D'
),
 
-- D extracts prev EndDemand as StartDemand, expressing start-end demand as an interval
 
D AS
(
  SELECT ID, Quantity, EndDemand - Quantity AS StartDemand, EndDemand
  FROM D0
)
SELECT ID, 
  CAST(ISNULL(StartDemand, 0.0) AS DECIMAL(19, 6)) AS StartDemand,
  CAST(ISNULL(EndDemand,   0.0) AS DECIMAL(19, 6)) AS EndDemand
INTO #Demand
FROM D;
 
WITH S0 AS
 
-- S0 computes running supply as EndSupply
 
(
  SELECT ID, Quantity,
    SUM(Quantity) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS EndSupply
  FROM dbo.Auctions
  WHERE Code = 'S'
),
 
-- S extracts prev EndSupply as StartSupply, expressing start-end supply as an interval
 
S AS
(
  SELECT ID, Quantity, EndSupply - Quantity AS StartSupply, EndSupply
  FROM S0
)
SELECT ID,
  CAST(ISNULL(StartSupply, 0.0) AS DECIMAL(19, 6)) AS StartSupply,
  CAST(ISNULL(EndSupply,   0.0) AS DECIMAL(19, 6)) AS EndSupply
INTO #Supply
FROM S;
 
-- Indexing
 
CREATE UNIQUE CLUSTERED INDEX idx_cl_SD_ID ON #Demand(StartDemand, ID);
CREATE UNIQUE CLUSTERED INDEX idx_cl_SS_ID ON #Supply(StartSupply, ID);
 
-- Trades are the overlapping segments of the intersecting intervals
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
INTO #MyPairings
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON D.StartDemand >= S.StartSupply AND D.StartDemand < S.EndSupply
 
UNION ALL
 
SELECT
  D.ID AS DemandID, S.ID AS SupplyID,
  CASE WHEN EndDemand < EndSupply THEN EndDemand ELSE EndSupply END - CASE WHEN StartDemand > StartSupply THEN StartDemand ELSE StartSupply END
    AS TradeQuantity
FROM #Demand AS D
  INNER JOIN #Supply AS S
    ON S.StartSupply > D.StartDemand AND S.StartSupply < D.EndDemand;

Seperti yang disebutkan sebelumnya, saya akan merujuk ke solusi ini sebagai persimpangan yang direvisi .

Solusi Kamil

Di antara solusi Luca, Kamil, dan Daniel, Kamil adalah yang tercepat. Inilah solusi lengkap Kamil:

SET NOCOUNT ON;
DROP TABLE IF EXISTS #supply, #demand;
GO
 
CREATE TABLE #demand
(
  DemandID           INT NOT NULL,
  DemandQuantity     DECIMAL(19, 6) NOT NULL,
  QuantityFromDemand DECIMAL(19, 6) NOT NULL,
  QuantityToDemand   DECIMAL(19, 6) NOT NULL
);
 
CREATE TABLE #supply
(
  SupplyID           INT NOT NULL,
  QuantityFromSupply DECIMAL(19, 6) NOT NULL,
  QuantityToSupply   DECIMAL(19,6) NOT NULL
);
 
WITH demand AS
(
  SELECT
    a.ID AS DemandID,
    a.Quantity AS DemandQuantity,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      - a.Quantity AS QuantityFromDemand,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      AS QuantityToDemand
  FROM dbo.Auctions AS a WHERE Code = 'D'
)
INSERT INTO #demand
(
  DemandID,
  DemandQuantity,
  QuantityFromDemand,
  QuantityToDemand
)
SELECT
  d.DemandID,
  d.DemandQuantity,
  d.QuantityFromDemand,
  d.QuantityToDemand
FROM demand AS d;
 
WITH supply AS
(
  SELECT
    a.ID AS SupplyID,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      - a.Quantity AS QuantityFromSupply,
    SUM(a.Quantity) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
      AS QuantityToSupply
  FROM dbo.Auctions AS a WHERE Code = 'S'
)
INSERT INTO #supply
(
  SupplyID,
  QuantityFromSupply,
  QuantityToSupply
)
SELECT
  s.SupplyID,
  s.QuantityFromSupply,
  s.QuantityToSupply
FROM supply AS s;
 
CREATE UNIQUE INDEX ix_1 ON #demand(QuantityFromDemand) 
  INCLUDE(DemandID, DemandQuantity, QuantityToDemand);
CREATE UNIQUE INDEX ix_1 ON #supply(QuantityFromSupply) 
  INCLUDE(SupplyID, QuantityToSupply);
CREATE NONCLUSTERED COLUMNSTORE INDEX nci ON #demand(DemandID) 
  WHERE DemandID is null;
 
DROP TABLE IF EXISTS #myPairings;
 
CREATE TABLE #myPairings
(
  DemandID      INT NOT NULL,
  SupplyID      INT NOT NULL,
  TradeQuantity DECIMAL(19, 6) NOT NULL
);
 
INSERT INTO #myPairings(DemandID, SupplyID, TradeQuantity) 
  SELECT
    d.DemandID,
    s.SupplyID, 
    d.DemandQuantity
      - CASE WHEN d.QuantityFromDemand < s.QuantityFromSupply 
             THEN s.QuantityFromSupply - d.QuantityFromDemand ELSE 0 end
      - CASE WHEN s.QuantityToSupply < d.QuantityToDemand 
             THEN d.QuantityToDemand - s.QuantityToSupply ELSE 0 END AS TradeQuantity 
  FROM #demand AS d 
    INNER JOIN #supply AS s 
      ON (d.QuantityFromDemand < s.QuantityToSupply 
          AND s.QuantityFromSupply <= d.QuantityFromDemand)
 
  UNION ALL
 
  SELECT
    d.DemandID,
    s.SupplyID,
    d.DemandQuantity
      - CASE WHEN d.QuantityFromDemand < s.QuantityFromSupply 
             THEN s.QuantityFromSupply - d.QuantityFromDemand ELSE 0 END
      - CASE WHEN s.QuantityToSupply < d.QuantityToDemand 
             THEN d.QuantityToDemand - s.QuantityToSupply ELSE 0 END AS TradeQuantity
  FROM #supply AS s 
    INNER JOIN #demand AS d 
      ON (s.QuantityFromSupply < d.QuantityToDemand 
          AND d.QuantityFromDemand < s.QuantityFromSupply);

Seperti yang Anda lihat, ini sangat dekat dengan solusi persimpangan yang direvisi yang saya bahas.

Rencana query terakhir dalam solusi Kamil ditunjukkan pada Gambar 8.


Gambar 8:Rencana kueri untuk kueri akhir dalam solusi Kamil

Rencananya terlihat sangat mirip dengan yang ditunjukkan sebelumnya pada Gambar 7.

Uji Kinerja

Ingatlah bahwa solusi persimpangan klasik membutuhkan waktu 931 detik untuk diselesaikan terhadap input dengan 400 ribu baris. Itu 15 menit! Ini jauh, jauh lebih buruk daripada solusi kursor, yang membutuhkan waktu sekitar 12 detik untuk menyelesaikan input yang sama. Gambar 9 memiliki angka kinerja termasuk solusi baru yang dibahas dalam artikel ini.


Gambar 9:Uji kinerja

Seperti yang Anda lihat, solusi Kamil dan solusi persimpangan yang direvisi serupa membutuhkan waktu sekitar 1,5 detik untuk menyelesaikan input 400K-baris. Itu peningkatan yang cukup baik dibandingkan dengan solusi berbasis kursor asli. Kelemahan utama dari solusi ini adalah biaya I/O. Dengan pencarian per baris, untuk input 400K-baris, solusi ini melakukan lebih dari 1,35 juta pembacaan. Namun, ini juga dapat dianggap sebagai biaya yang dapat diterima dengan baik mengingat waktu pengoperasian dan penskalaan yang Anda dapatkan.

Apa Selanjutnya?

Upaya pertama kami untuk memecahkan tantangan penawaran versus permintaan yang cocok mengandalkan uji persimpangan interval klasik dan mendapatkan rencana berkinerja buruk dengan penskalaan kuadrat. Jauh lebih buruk daripada solusi berbasis kursor. Berdasarkan wawasan dari Luca, Kamil, dan Daniel, menggunakan uji persimpangan interval yang direvisi, upaya kedua kami adalah peningkatan signifikan yang memanfaatkan pengindeksan secara efisien dan berkinerja lebih baik daripada solusi berbasis kursor. Namun, solusi ini melibatkan biaya I/O yang signifikan. Bulan depan saya akan terus mencari solusi tambahan.

[ Langsung ke:Tantangan asli | Solusi:Bagian 1 | Bagian 2 | Bagian 3]

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Perbedaan antara Inner join dan Outer join di SQL

  2. Membangun Aplikasi Web Sederhana Dengan Botol, SQLAlchemy, dan Twitter API

  3. Cara Berkomentar di SQL

  4. Menghubungkan Meja Kerja Snowflake DB &IRI

  5. Operator SQL NOT untuk Pemula