2013-04-07 28 views
8

我想「左連接」一個表,這樣一個值不僅可以連接到匹配的行,也可以連接到任何後續的不匹配的行,直到下一個匹配的行。換句話說,我想用先前的非空值填充空值。如何使用這種不尋常的匹配標準編寫連接?

樣本數據和期望的結果:

x

id 
---- 
    1 
    2 
    3 
    4 
    5 

y

結果的 select x.id, y.val from x left join y on x.id=y.id order by x.id;
id | val 
----+----- 
    1 | a 
    4 | b 

id | val 
----+----- 
    1 | a 
    2 | 
    3 | 
    4 | b 
    5 | 

期望的結果:

id | val 
----+----- 
    1 | a 
    2 | a 
    3 | a 
    4 | b 
    5 | b 
+5

只有應用排序順序時,才能識別「下一個」行。 「下一個」是指具有更高「id」的行嗎? – 2013-04-07 10:53:36

+0

是的,我已經更新了示例查詢以添加'by x.id'的訂單。 – jl6 2013-04-07 10:55:26

+0

您的示例表明值也按升序排列。是這樣嗎? – 2013-04-07 12:47:23

回答

6

指數

創建於x.idy.id指數 - 你可能已經有了,如果這些是你的主鍵。
多列索引可能會有幫助,太,尤其是在index only scans PG 9.2+:

CREATE INDEX y_mult_idx ON y (id DESC, val) 

然而,在我的測試中,該指數是在沒有首先使用。必須添加(否則毫無意義)valORDER BY以說服查詢規劃器排序順序匹配。見查詢。

該指數在這個合成設置中幾乎沒有什麼區別。但是對於列數更多的表格,從表中檢索val變得越來越昂貴,使得「覆蓋」索引更具吸引力。

查詢

1)簡單

SELECT DISTINCT ON (x.id) 
     x.id, y.val 
FROM x 
JOIN y ON y.id <= x.id 
ORDER BY x.id, y.id DESC; 

SQL Fiddle.

在這個相關答案與 DISTINCT技術

更多的解釋:

我跑了一些測試,因爲我懷疑第一個查詢不能很好地擴展。它有一張小桌子很快,但桌子較大時沒有好處。 Postgres沒有優化計劃,並以(有限)交叉連接開始,成本爲O(N²)

2)快速

這個查詢仍然是相當簡單,並且很好地磅秤:

SELECT x.id, y.val 
FROM x 
JOIN (SELECT *, lead(id, 1, 2147483647) OVER (ORDER BY id) AS next_id FROM y) y 
     ON x.id >= y.id 
     AND x.id < y.next_id 
ORDER BY 1; 

窗口函數lead()是工具。我使用該選項來提供默認設置以覆蓋最後一行的轉角情況:2147483647biggest possible integer。適應你的數據類型。

3)非常簡單和幾乎一樣快

SELECT x.id 
    ,(SELECT val FROM y WHERE id <= x.id ORDER BY id DESC, val LIMIT 1) AS val 
FROM x;

通常,相關子查詢往往較慢。但是這隻能從(覆蓋)指數中選擇一個價值,否則就很容易競爭。

額外的ORDER BY項目val(大膽強調)似乎毫無意義。但是添加它會使查詢規劃人員相信,使用上面的多列索引y_mult_idx是可以的,因爲排序順序是匹配的。注意

只索引掃描使用y_mult_idx ..

EXPLAIN輸出

測試用例

展開了熱烈討論,並多次更新我收集到的所有查詢到目前爲止發佈和簡要概述做了一個試驗案例後。我只使用1000行,所以SQLfiddle不會超時較慢的查詢。但前4名(Erwin 2,Clodoaldo,a_horse,Erwin 3)在我所有的本地測試中線性縮放。 更新一次,包括我的最新加入,現在改善性能的格式和順序:

Big SQL Fiddle comparing performance.

+0

你的小提琴似乎沒有'y.id'上的主鍵 – wildplasser 2013-04-07 14:50:18

+0

@wildplasser:no,對於原來的小測試案例我只是想演示它的工作原理。之後我發佈了更新之前,運行了10k和100k行(包括主鍵)的廣泛測試。我的第二個查詢進入最快,緊跟着a_horse和Clodoaldo。剛剛測試過你的,它和我的第一個版本有類似的問題:不能很好地擴展。 – 2013-04-07 14:54:29

+0

好的,對不起。感謝您的測試。我也用10K行來運行它,它感覺不好(並且在檢查時產生了一個醜陋的計劃,即使在真空分析後)奇怪的... – wildplasser 2013-04-07 14:57:31

1

使用COALESCE和子查詢的邏輯。

子查詢將檢索最後一個val值。

試試這個:

SELECT x1.id, 
     coalesce(y1.val, (SELECT val 
         FROM y 
         WHERE id = (SELECT Max(x2.id) 
            FROM x AS x2 
              JOIN y AS y2 
              ON x2.id = y2.id 
            WHERE x2.id < x1.id))) 
FROM x AS x1 
     LEFT JOIN y AS y1 
       ON x1.id = y1.id 
ORDER BY x1.id; 

sqlfiddle:http://www.sqlfiddle.com/#!12/42526/1

+0

@a_horse_with_no_name:是的,我忘了它..謝謝! – 2013-04-07 11:07:01

4
select id, 
     first_value(t.val) over (partition by group_flag order by t.id) as val 
from (
    select x.id, 
     y.val, 
     sum(case 
      when y.val is null then 0 
      else 1 
      end) over (order by x.id) as group_flag 
    from x 
    left join y on x.id=y.id 
) t  
order by id; 

SQLFiddle例如:http://sqlfiddle.com/#!12/38903/1

4

SQL Fiddle

select 
    id, 
    first_value(val) over(partition by g order by id) val 
from (
    select 
     x.id, val, count(val) over(order by x.id) g 
    from 
     x 
     left join 
     y on x.id=y.id 
) s 
order by id 
+1

好用的'count()'比我的'case'構造更優雅。但我看到我們都有同樣的想法。 – 2013-04-07 11:13:17

+0

看起來很理想。我是否正確理解count()函數是針對每一行進行評估的,在行框架上執行一個非缺少'val'的運行計數(這是從第一行到當前的所有行)? – jl6 2013-04-07 11:20:05

+1

@ jl6是的,它是一個運行計數,因爲它不計算空值,因此非常適合進行分組。 – 2013-04-07 11:44:49

2
SELECT 
    m.id, 
    y.val 
FROM (
    SELECT 
    x.id, 
    MAX(y.id) id_y 
    FROM 
    x INNER JOIN y ON x.id >= y.id 
    GROUP BY 
    x.id 
) m INNER JOIN y ON m.id_y = y.id 
ORDER BY 
    m.id 

請參閱小提琴here

0

我不知道如何使用單個存儲過程來實現這一點。邏輯類似以下內容將返回你所期望的結果

create PROCEDURE GetData 
AS 
BEGIN 
    Declare @resultTable TABLE(
    id int, 
    value varchar(10)) 

    Declare @id int 
    Declare @previousValue varchar(10) 
    Declare @currentValue varchar(10) 

    DECLARE x_cursor CURSOR 
    FOR SELECT id FROM x order by id 

    OPEN x_cursor 
    FETCH NEXT FROM x_cursor into @id; 
    WHILE (@@FETCH_STATUS = 0) 
    BEGIN 
     select @currentValue = isnull(val,@previousValue) from Y where id = @id 
     insert into @resultTable values(@id,@currentValue) 
     set @previousValue = @currentValue 
     FETCH NEXT FROM x_cursor into @id; 
    END 


    Close x_cursor 
    Deallocate x_cursor 

    select * from @resultTable 
END 
GO 
+0

不錯的嘗試,但問題是僅Postgresql和您的過程是隻有SQL Server – 2013-04-07 11:46:42

+2

而存儲過程是絕對沒有必要的。 – 2013-04-07 12:25:14

+0

@ user2254386這裏是Sql Server版本,儘管它只是在最新版本(2012)上Sql Server具有像first_value這樣的窗口函數。將無法在Sql Server 2008上工作http://www.sqlfiddle.com/#!6/7e45d/1 – 2013-04-07 12:42:37

2

我想表達的聚合函數MIN(),MAX(),或closer_to()來講(NOT)存在。

SELECT x.id, y.val 
FROM x 
JOIN y ON y.id <= x.id 
WHERE NOT EXISTS (SELECT * 
     FROM y y2 
     WHERE y2.id <= x.id -- same condition AS main query 
     AND y2.id > y.id -- but closer to x.id 
     ) 
     ; 

我的直覺是,這將產生與Erwin的答案完全相同的查詢計劃。

+1

您的方法的這種變體應該更好地執行:http://sqlfiddle.com/#!12/38903/8 – 2013-04-07 15:04:46

+0

但它很醜* ;-)注意:正如我們所說的,我正在升級到9.2.4以查看是否有任何差異。 (有傳聞說9.2慢,我不相信他們,至少不是真實世界的問題......) – wildplasser 2013-04-07 15:12:41

+0

沒有說這是一種美。 :)只是更快。如果你正在尋找優雅,請看我在答案中發佈的內容。 ;) – 2013-04-07 15:16:59