2010-01-11 28 views
14

我試圖在一組多列中獲取第一個非空值。我知道我可以使用每列的子查詢來完成此操作。以表演的名義,在這種情況下確實算得上,我想一次性做到這一點。爲多列找到第一個非空值

看看下面的示例數據:

col1  col2  col3  sortCol 
==================================== 
NULL  4  8  1 
1  NULL  0  2 
5  7  NULL  3 

我的夢想查詢會發現在每一個數據列的第一個非空值,排序在​​。

例如,選擇前三列的神奇聚合物時,按​​降序排序。

col1  col2  col3 
======================== 
5  7   0 

或排序時上升:

col1  col2  col3 
======================== 
1  4   8 

有誰知道一個解決方案嗎?

+0

你需要的第一個非空列,或者第一個非空行? – feihtthief 2010-01-11 22:09:13

+1

你是否只需要第一排,或者你是否需要整套? sortCol是獨一無二的嗎? – feihtthief 2010-01-11 22:27:02

+0

@feihtthief:每列中的第一個非空值。我認爲示例輸出應該很好地顯示所需的效果。 @Mark Byers:由於我沒有一個解決方案可以一次性工作,我只能猜測它的性能,但子查詢方法還有很多不足之處。在我的實際表格中,我需要以這種方式捲起大約20行。 與子查詢方法,指標並不是特別有用。我相信單掃描方法有很多列可能會快得多。 – EvilRyry 2010-01-11 22:32:48

回答

7

在拒絕它之前,您是否已經實際測試過此解決方案?

SELECT 
    (SELECT TOP(1) col1 FROM Table1 WHERE col1 IS NOT NULL ORDER BY SortCol) AS col1, 
    (SELECT TOP(1) col2 FROM Table1 WHERE col2 IS NOT NULL ORDER BY SortCol) AS col2, 
    (SELECT TOP(1) col3 FROM Table1 WHERE col3 IS NOT NULL ORDER BY SortCol) AS col3 

如果這很慢,可能是因爲您沒有合適的索引。你有什麼指數?

+0

現在在SortCol上有一個索引正在被有效地使用。經過一番調查,我重建了大幅幫助過的指數。 這個解決方案現在大概在10ms左右,這可能夠用了。我想我可以通過添加一些其他列來消除更多的時間,以消除目前佔總時間2/3的RID查找。 – EvilRyry 2010-01-12 15:01:14

1

不完美,但它可以在單個查詢中完成。儘管這可能會使任何索引都變得毫無用處,如前所述,多重子查詢方法可能會更快。


create table Foo (data1 tinyint, data2 tinyint, data3 tinyint, seq int not null) 
go 

insert into Foo (data1, data2, data3, seq) 
values (NULL, 4, 8, 1), (1, NULL, 0, 2), (5, 7, NULL, 3) 
go 

with unpivoted as (
    select seq, value, col 
    from (select seq, data1, data2, data3 from Foo) a 
    unpivot (value FOR col IN (data1, data2, data3)) b 
), firstSeq as (
    select min(seq) as seq, col 
    from unpivoted 
    group by col 
), data as (
    select b.col, b.value 
    from firstSeq a 
    inner join unpivoted b on a.seq = b.seq and a.col = b.col 
) 
select * from data pivot (min(value) for col in (data1, data2, data3)) d 
go 

drop table Foo 
go 
6

與實施本爲聚合(你如果,例如,您實施了「第一非空」 SQL CLR聚合確實可以做)的問題是浪費的IO讀取每一行,當你通常只對前幾行感興趣。第一個非null即使其實現忽略更多值,聚合也不會停止。聚合也是無序的,所以你的結果將取決於查詢引擎選擇的索引的順序。

相比之下,子查詢解決方案爲每個查詢讀取最小行(因爲您只需要第一個匹配的行)並支持任何排序。它也適用於無法定義自定義聚合的數據庫平臺。

哪一個性能更好可能取決於表格中行和列的數量以及數據的稀疏程度。其他行需要爲聚合方法讀取更多行。其他列需要額外的子查詢。稀疏數據需要檢查每個子查詢中的更多行。

下面是一些結果關於各種表的大小:

Rows Cols Aggregation IO CPU Subquery IO CPU 
3  3     2 0    6 0 
1728 3     8 63   6 0 
1728 8     12 266   16 0 

的IO這裏測量的是邏輯讀取數。請注意,子查詢方法的邏輯讀取次數不會隨着表中的行數而改變。另請注意,每個附加子查詢執行的邏輯讀取可能會針對相同的數據頁(包含前幾行)。另一方面,聚合必須處理整個表並且需要一些CPU時間來完成。

這是我用於測試的代碼...SortCol上的聚集索引是必需的,因爲(在這種情況下)它將確定聚合的順序。

定義表和插入測試數據:

CREATE TABLE Table1 (Col1 int null, Col2 int null, Col3 int null, SortCol int); 
CREATE CLUSTERED INDEX IX_Table1 ON Table1 (SortCol); 

WITH R (i) AS 
(
SELECT null 

UNION ALL 

SELECT 0 

UNION ALL 

SELECT i + 1 
FROM R 
WHERE i < 10 
) 
INSERT INTO Table1 
SELECT a.i, b.i, c.i, ROW_NUMBER() OVER (ORDER BY NEWID()) 
FROM R a, R b, R c; 

查詢表:

SET STATISTICS IO ON; 

--aggregation 
SELECT TOP(0) * FROM Table1 --shortcut to convert columns back to their types 
UNION ALL 
SELECT 
dbo.FirstNonNull(Col1), 
dbo.FirstNonNull(Col2), 
dbo.FirstNonNull(Col3), 
null 
FROM Table1; 


--subquery 
SELECT 
    (SELECT TOP(1) Col1 FROM Table1 WHERE Col1 IS NOT NULL ORDER BY SortCol) AS Col1, 
    (SELECT TOP(1) Col2 FROM Table1 WHERE Col2 IS NOT NULL ORDER BY SortCol) AS Col2, 
    (SELECT TOP(1) Col3 FROM Table1 WHERE Col3 IS NOT NULL ORDER BY SortCol) AS Col3; 

的CLR 「第一非空」 聚集體進行測試:

[Serializable] 
[SqlUserDefinedAggregate(
    Format.UserDefined, 
    IsNullIfEmpty = true, 
    IsInvariantToNulls = true, 
    IsInvariantToDuplicates = true, 
    IsInvariantToOrder = false, 
#if(SQL90) 
    MaxByteSize = 8000 
#else 
    MaxByteSize = -1 
#endif 
)] 
public sealed class FirstNonNull : IBinarySerialize 
{ 
    private SqlBinary Value; 

    public void Init() 
    { 
    Value = SqlBinary.Null; 
    } 

    public void Accumulate(SqlBinary next) 
    { 
    if (Value.IsNull && !next.IsNull) 
    { 
    Value = next; 
    } 
    } 

    public void Merge(FirstNonNull other) 
    { 
    Accumulate(other.Value); 
    } 

    public SqlBinary Terminate() 
    { 
    return Value; 
    } 

    #region IBinarySerialize Members 

    public void Read(BinaryReader r) 
    { 
    int Length = r.ReadInt32(); 

    if (Length < 0) 
    { 
    Value = SqlBinary.Null; 
    } 
    else 
    { 
    byte[] Buffer = new byte[Length]; 
    r.Read(Buffer, 0, Length); 

    Value = new SqlBinary(Buffer); 
    } 
    } 

    public void Write(BinaryWriter w) 
    { 
    if (Value.IsNull) 
    { 
    w.Write(-1); 
    } 
    else 
    { 
    w.Write(Value.Length); 
    w.Write(Value.Value); 
    } 
    } 

    #endregion 
} 
1

這是另一種方法。如果你的數據庫在子查詢中不允許top(N)(比如我的Teradata),這將是最有用的。

爲了便於比較,這裏的解決方案的其他人提及的,使用top(1)

select top(1) Col1 
from Table1 
where Col1 is not null 
order by SortCol asc 

在一個理想的世界,這似乎對我來說,最好的方式做到這一點 - 乾淨,直觀,高效的(顯然) 。

或者你可以這樣做:

select max(Col1) -- max() guarantees a unique result 
from Table1 
where SortCol in (
    select min(SortCol) 
    from Table1 
    where Col1 is not null 
) 

這兩種解決方案檢索沿着有序列「第一」的紀錄。 Top(1)確實更優雅,可能更有效。第二種方法在概念上做同樣的事情,只是從代碼角度來看更多的手動/顯式實現。

根選擇max()的原因在於,如果值min(SortCol)Table1的多行中出現,您可以獲得多個結果。順便提一下,我不確定Top(1)如何處理這種情況。

相關問題