2013-09-24 72 views
22

Tôi có một bảng trong Postgres trông như thế này:Làm cách nào để chọn hiệu quả giá trị không null trước đó?

# select * from p; 
id | value 
----+------- 
    1 | 100 
    2 |  
    3 |  
    4 |  
    5 |  
    6 |  
    7 |  
    8 | 200 
    9 |   
(9 rows) 

Và tôi muốn truy vấn để làm cho nó trông như thế này:

# select * from p; 
id | value | new_value 
----+-------+---------- 
    1 | 100 |  
    2 |  | 100 
    3 |  | 100 
    4 |  | 100 
    5 |  | 100 
    6 |  | 100 
    7 |  | 100 
    8 | 200 | 100 
    9 |  | 200 
(9 rows) 

tôi đã có thể làm điều này với một subquery trong lựa chọn, nhưng trong dữ liệu thực tế của tôi, tôi có 20k hàng trở lên và nó được khá chậm.

Điều này có thể thực hiện được trong chức năng cửa sổ không? Tôi rất thích sử dụng lag(), nhưng nó dường như không hỗ trợ tùy chọn IGNORE NULLS.

select id, value, lag(value, 1) over (order by id) as new_value from p; 
id | value | new_value 
----+-------+----------- 
    1 | 100 |  
    2 |  |  100 
    3 |  |  
    4 |  | 
    5 |  | 
    6 |  | 
    7 |  | 
    8 | 200 | 
    9 |  |  200 
(9 rows) 

Trả lời

48

Tôi đã tìm thấy this answer cho SQL Server cũng hoạt động trong Postgres. Chưa từng làm điều đó trước đây, tôi nghĩ kỹ thuật này khá thông minh. Về cơ bản, anh ta tạo một phân vùng tùy chỉnh cho chức năng cửa sổ bằng cách sử dụng một câu lệnh case bên trong một truy vấn lồng nhau tăng một tổng khi giá trị không phải là null và để lại nó một mình. Điều này cho phép một để phân định mọi phần rỗng với cùng số với giá trị không null trước đó. Dưới đây là các truy vấn:

SELECT 
    id, value, value_partition, first_value(value) over (partition by value_partition order by id) 
FROM (
    SELECT 
    id, 
    value, 
    sum(case when value is null then 0 else 1 end) over (order by id) as value_partition 

    FROM p 
    ORDER BY id ASC 
) as q 

Và kết quả:

id | value | value_partition | first_value 
----+-------+-----------------+------------- 
    1 | 100 |    1 |   100 
    2 |  |    1 |   100 
    3 |  |    1 |   100 
    4 |  |    1 |   100 
    5 |  |    1 |   100 
    6 |  |    1 |   100 
    7 |  |    1 |   100 
    8 | 200 |    2 |   200 
    9 |  |    2 |   200 
(9 rows) 
+0

+1 ... Đây là giải pháp thực sự thông minh, cũng là chỉ mục trên 'id' và' value' sẽ cải thiện hiệu suất. – MatheusOl

+0

Giải pháp tuyệt vời cho tôi trong năm 2015! – Chris

+4

bạn có thể sử dụng 'count (value)' thay vì 'sum (case ...)' như đếm bỏ qua null. – shaunc

2

Vâng, tôi không thể đảm bảo đây là cách hiệu quả nhất, nhưng hoạt động:

SELECT id, value, (
    SELECT p2.value 
    FROM p p2 
    WHERE p2.value IS NOT NULL AND p2.id <= p1.id 
    ORDER BY p2.id DESC 
    LIMIT 1 
) AS new_value 
FROM p p1 ORDER BY id; 

Chỉ số sau đây có thể cải thiện phụ truy vấn cho các tập dữ liệu lớn:

CREATE INDEX idx_p_idvalue_nonnull ON p (id, value) WHERE value IS NOT NULL; 

Giả sử các value là thưa thớt (ví dụ như có rất nhiều nulls) nó sẽ chạy tốt.

+0

Cảm ơn! Điều này là có hiệu quả những gì tôi đã có về một truy vấn phụ. Chắc chắn hoạt động. Tôi đã không nhận ra Postgres cho phép bạn tạo một chỉ mục và kết hợp nó với một điều kiện. Thats khá tuyệt vời. – adamlamar

2

Bạn có thể tạo một chức năng tùy chỉnh tổng hợp trong Postgres. Dưới đây là ví dụ cho loại int:

CREATE FUNCTION coalesce_agg_sfunc(state int, value int) RETURNS int AS 
$$ 
    SELECT coalesce(value, state); 
$$ LANGUAGE SQL; 

CREATE AGGREGATE coalesce_agg(int) (
    SFUNC = coalesce_agg_sfunc, 
    STYPE = int); 

Sau đó truy vấn như bình thường.

SELECT *, coalesce_agg(b) over w, sum(b) over w FROM y 
    WINDOW w AS (ORDER BY a); 

a b coalesce_agg sum 
- - ------------ --- 
a 0   0 0 
b ∅   0 0 
c 2   2 2 
d 3   3 5 
e ∅   3 5 
f 5   5 10 
(6 rows) 
+0

Bạn có thể sử dụng để giả loại 'anyelement' thay vì' int' để làm cho hàm và hàm tổng hợp chung. –

+0

Gọi nó là 'coalesce_agg' có vẻ trực quan hơn. – ddrscott

+0

'coalesce_agg' có vẻ tốt hơn. Mặc dù để nitpick, 'coalesce' thích đối số đầu tiên trong khi hàm tổng hợp này thích giá trị cuối cùng. –

0

Bạn có thể sử dụng LAST_VALUE với LỌC để đạt được những gì bạn cần (ít nhất là trong PG 9,4)

WITH base AS (
SELECT 1 AS id , 100 AS val 
UNION ALL 
SELECT 2 AS id , null AS val 
UNION ALL 
SELECT 3 AS id , null AS val 
UNION ALL 
SELECT 4 AS id , null AS val 
UNION ALL 
SELECT 5 AS id , 200 AS val 
UNION ALL 
SELECT 6 AS id , null AS val 
UNION ALL 
SELECT 7 AS id , null AS val 
) 
SELECT id, val, last(val) FILTER (WHERE val IS NOT NULL) over(ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) new_val 
    FROM base 
+0

FILTER với chức năng cửa sổ không tổng hợp chưa được triển khai trong Postgres. – pensnarik