Büyük bir tablonun sıralı sütununda son boş olmayan değeri nasıl elde edebilirim?


13

Aşağıdaki girdiye sahibim:

 id | value 
----+-------
  1 |   136
  2 |  NULL
  3 |   650
  4 |  NULL
  5 |  NULL
  6 |  NULL
  7 |   954
  8 |  NULL
  9 |   104
 10 |  NULL

Aşağıdaki sonucu bekliyorum:

 id | value 
----+-------
  1 |   136
  2 |   136
  3 |   650
  4 |   650
  5 |   650
  6 |   650
  7 |   954
  8 |   954
  9 |   104
 10 |   104

Önemsiz çözüm bir <ilişki ile tablolara katılmak ve sonra MAXa GROUP BY:

WITH tmp AS (
  SELECT t2.id, MAX(t1.id) AS lastKnownId
  FROM t t1, t t2
  WHERE
    t1.value IS NOT NULL
    AND
    t2.id >= t1.id
  GROUP BY t2.id
)
SELECT
  tmp.id, t.value
FROM t, tmp
WHERE t.id = tmp.lastKnownId;

Ancak, bu kodun önemsiz bir şekilde yürütülmesi, dahili olarak giriş tablosundaki satır sayısının ( O (n ^ 2) ) karesini oluşturacaktır . T-sql'ın onu optimize etmesini bekledim - bir blok / kayıt düzeyinde, yapılacak iş çok kolay ve doğrusal, aslında bir for döngüsü ( O (n) ).

Ancak, denemelerimde, en son MS SQL 2016 bu sorguyu doğru şekilde optimize edemez ve bu sorguyu büyük bir giriş tablosu için yürütmeyi imkansız hale getirir.

Ayrıca, sorgu hızlı bir şekilde çalışmalı ve benzer şekilde kolay (ama çok farklı) imleç tabanlı bir çözümü olanaksız hale getirmelidir.

Bazı bellek destekli geçici tablo kullanmak iyi bir uzlaşma olabilir, ancak önemli ölçüde daha hızlı çalıştırılabilir olup olmadığını emin değilim, alt sorguları kullanarak benim örnek sorgu işe yaramadı.

Ayrıca t-sql belgelerinden bazı pencereleme işlevini kazmayı düşünüyorum, ne istediğimi yapmak için kandırılmış olabilir. Örneğin, kümülatif toplam çok benzer bir şey yapıyor, ancak en son null olmayan öğeyi vermek için kandıramadım ve önceki öğelerin toplamını değil.

İdeal çözüm, prosedür kodu veya geçici tablolar olmadan hızlı bir sorgu olacaktır. Alternatif olarak, geçici tablolar içeren bir çözüm de uygundur, ancak tabloyu prosedürel olarak yinelemek uygun değildir.

Yanıtlar:


12

Bu tür bir soruna ortak bir çözüm Itzik Ben-Gan tarafından Son NULL Puzzle olmayan makalesinde verilmiştir :

DROP TABLE IF EXISTS dbo.Example;

CREATE TABLE dbo.Example
(
    id integer PRIMARY KEY,
    val integer NULL
);

INSERT dbo.Example
    (id, val)
VALUES
    (1, 136),
    (2, NULL),
    (3, 650),
    (4, NULL),
    (5, NULL),
    (6, NULL),
    (7, 954),
    (8, NULL),
    (9, 104),
    (10, NULL);

SELECT
    E.id,
    E.val,
    lastval =
        CAST(
            SUBSTRING(
                MAX(CAST(E.id AS binary(4)) + CAST(E.val AS binary(4))) OVER (
                    ORDER BY E.id
                    ROWS UNBOUNDED PRECEDING),
            5, 4)
        AS integer)
FROM dbo.Example AS E
ORDER BY
    E.id;

Demo: db <> keman


11

T-sql'ın bunu optimize etmesini bekledim - bir blok / kayıt düzeyinde, yapılacak iş çok kolay ve doğrusal, aslında bir for döngüsü (O (n)).

Bu yazdığınız sorgu değil. Tablo şemasının diğer küçük ayrıntılarına bağlı olarak yazdığınız sorguya eşdeğer olmayabilir. Sorgu optimize ediciden çok fazla şey bekliyorsunuz.

Doğru indeksleme ile aradığınız algoritmayı aşağıdaki T-SQL ile elde edebilirsiniz:

SELECT t1.id, ca.[VALUE] 
FROM dbo.[BIG_TABLE(FOR_U)] t1
CROSS APPLY (
    SELECT TOP (1) [VALUE]
    FROM dbo.[BIG_TABLE(FOR_U)] t2
    WHERE t2.ID <= t1.ID AND t2.[VALUE] IS NOT NULL
    ORDER BY t2.ID DESC
) ca; --ORDER BY t1.ID ASC

Her satır için, sorgu işlemcisi dizini geriye doğru hareket ettirir ve null değeri olmayan bir satır bulduğunda durur [VALUE]. Makinemde bu , kaynak tabloda 100 milyon satır için yaklaşık 90 saniye içinde bitiyor . Sorgu gerekenden daha uzun çalışıyor, çünkü istemcide bu satırların tümünü atarak belirli bir zaman harcanıyor.

Sipariş edilen sonuçlara veya bu kadar büyük bir sonuç setiyle ne yapmayı planladığınıza dair net değil. Sorgu gerçek senaryoyu karşılayacak şekilde ayarlanabilir. Bu yaklaşımın en büyük avantajı, sorgu planında bir sıralama gerektirmemesidir. Bu, daha büyük sonuç kümeleri için yardımcı olabilir. Bir dezavantaj, tabloda çok sayıda NULL varsa performansın optimal olmayacağıdır, çünkü birçok satır dizinden okunacak ve atılacaktır. Bu durumda NULL'ları hariç tutan filtrelenmiş bir dizinle performansı artırabilirsiniz.

Test için örnek veriler:

DROP TABLE IF EXISTS #t;

CREATE TABLE #t (
ID BIGINT NOT NULL
);

INSERT INTO #t WITH (TABLOCK)
SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

DROP TABLE IF EXISTS dbo.[BIG_TABLE(FOR_U)];

CREATE TABLE dbo.[BIG_TABLE(FOR_U)] (
ID BIGINT NOT NULL,
[VALUE] BIGINT NULL
);

INSERT INTO dbo.[BIG_TABLE(FOR_U)] WITH (TABLOCK)
SELECT 10000 * t1.ID + t2.ID, CASE WHEN (t1.ID + t2.ID) % 3 = 1 THEN t2.ID ELSE NULL END
FROM #t t1
CROSS JOIN #t t2;

CREATE UNIQUE CLUSTERED INDEX ADD_ORDERING ON dbo.[BIG_TABLE(FOR_U)] (ID);

7

Bir yöntem kullanılarak OVER()ve MAX()ve COUNT()dayalı bu kaynaktan olabilir:

SELECT ID, MAX(value) OVER (PARTITION BY Value2) as value
FROM
(
    SELECT ID, value
        ,COUNT(value) OVER (ORDER BY ID) AS Value2
    FROM dbo.HugeTable
) a
ORDER BY ID;

Sonuç

Id  UpdatedValue
1   136
2   136
3   650
4   650
5   650
6   650
7   954
8   954
9   104
10  104

Bu kaynağa dayanan , ilk örnekle yakından ilişkili başka bir yöntem

;WITH CTE As 
( 
SELECT  value,
        Id, 
        COUNT(value) 
        OVER(ORDER BY Id) As  Value2 
FROM dbo.HugeTable
),

CTE2 AS ( 
SELECT Id,
       value,
       First_Value(value)  
       OVER( PARTITION BY Value2
             ORDER BY Id) As UpdatedValue 
FROM CTE 
            ) 
SELECT Id,UpdatedValue 
FROM CTE2;

3
Bu yaklaşımların "büyük bir tablo" ile nasıl performans gösterdiğiyle ilgili ayrıntılar eklemeyi düşünün.
Joe Obbish
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.