Tìm kiếm một thay thế đơn giản hơn cho một truy vấn đệ quy


7

Truy vấn thực tế có liên quan nhiều hơn, nhưng vấn đề tôi gặp phải có thể được giải quyết cho vấn đề này:

Một truy vấn để lọc một hàng các số nguyên tăng đơn điệu sao cho - trong tập kết quả cuối cùng, hàng (n + 1) .value> = row (n) .value + 5 .

Đối với vấn đề thực tế tôi cần giải quyết, số lượng hàng là trong 1000 giây.

Một vài ví dụ để làm rõ:

  • nếu các hàng là: 1,2,3,4,5: thì truy vấn sẽ trả về: 1
  • nếu các hàng là: 1,5,7,10,11,12,13: thì truy vấn sẽ trả về: 1,7,12
  • nếu các hàng là: 6,8,11,16,20,23: thì truy vấn sẽ trả về: 6,11,16,23
  • nếu các hàng là: 6,8,12,16,20,23: thì truy vấn sẽ trả về: 6,12,20

Tôi đã quản lý để có được kết quả cần thiết với truy vấn sau, nhưng có vẻ quá phức tạp. Bỏ ghi chú ".. với t (k) .." khác nhau để thử chúng.

Tôi đang tìm kiếm bất kỳ đơn giản hóa hoặc phương pháp thay thế nào để có được kết quả tương tự.

with recursive r(n, pri) as (
    with t(k) as (values (1),(2),(3),(4),(5))   -- the data we want to filter
    -- with t(k) as (values (1),(5),(7),(10),(11),(12),(13))
    -- with t(k) as (values (6),(8),(11),(16),(20),(23))
    -- with t(k) as (values (6),(8),(12),(16),(20),(23))
    select min(k), 1::bigint from t             -- bootstrap for recursive processing. 1 here represents rank().
    UNION
    select k, (rank() over(order by k)) rr      -- rank() is required just to filter out the rows we dont want from the final result set, and no other reason
    from r, t 
    where t.k >= r.n+5 and r.pri = 1            -- capture the rows we want, AND unfortunately a bunch of rows we dont want 
)
select n from r where pri = 1; 

Đối với quy tắc row(n+1).value >= row(n).value + 5 và các hàng 1,5,7,10,11,12,13bạn nói nó sẽ trả về 1,7,12nhưng điều đó không đồng ý với quy tắc. Quy tắc cũng nên nói rằng bạn luôn trả về hàng đầu tiên. Vì vậy, sau đó bắt đầu với hàng 1 đầu tiên, 5 >= 1 + 5là sai nên 5 bị loại trừ. Sau đó 7 >= 5 + 5cũng là sai vì vậy 7 nên được loại trừ theo quy tắc, nhưng nó được bao gồm. Quy tắc của bạn giống như "bắt đầu với hàng đầu tiên, tìm hàng tiếp theo ít nhất năm lần so với giá trị hiện tại, lặp lại"
Davos

Câu trả lời:


2

Điều này thật khó! Tôi không biết điều này có đơn giản hơn không , nhưng ít nhất nó không sử dụng chức năng cửa sổ cũng như không tạo ra các hàng yêu cầu được lọc ra.

with recursive r(k, n) as (
    with t(k) as (values (1),(2),(3),(4),(5))   -- the data we want to filter
    -- with t(k) as (values (1),(5),(7),(10),(11),(12),(13))
    -- with t(k) as (values (6),(8),(11),(16),(20),(23))
    -- with t(k) as (values (6),(8),(12),(16),(20),(23))
         ,t2(k,n) AS (select k, (select min(k) from t tt where k >= t.k+5) from t) -- precalculate what's next
    select * from (select * from t2 limit 1) x   -- limit 1 directly fails in a union!
    UNION ALL
    select t2.* from r, t2 where t2.k = r.n      -- on each iteration, keep only the value that matches the previous precalculated next one
)
select k from r

Kiểm tra

Sự thay thế này dường như kém hiệu quả hơn đối với các bộ rất nhỏ, nhưng hiệu suất tuyến tính nhiều hay ít, trong khi bản gốc dường như chậm hơn theo cấp số nhân.

drop table if exists t;
create temp table t(k) AS
with recursive r(n) as (
  select floor(random()*10)::int + 1
  UNION ALL
  select n + floor(random()*10)::int + 1
  from r
  where n < 100000)        -- change to increase or reduce set
select * from r;           -- surprisingly fast! Go PG!
create index on t(k);

with recursive r(n, pri) as (
    select min(k), 1::bigint from t
    UNION
    select k, (rank() over(order by k)) rr
    from r, t 
    where t.k >= r.n+5 and r.pri = 1
)
select count(*) from r where pri = 1; -- I aborted it after waiting for a minute

with recursive r(k, n) as (
    with t2(k,n) AS (select k, (select min(k) from t tt where k >= t.k+5) from t)
    select * from (select * from t2 limit 1) x
    UNION ALL
    select t2.* from r, t2 where t2.k = r.n
)
select count(*) from r -- 26" in my server

2

Thông thường, bạn có thể sử dụng các hàm tổng hợp có cửa sổ khi bạn cần truy cập dữ liệu của hàng trước , nhưng ở đây tính toán của hàng hiện tại dựa trên kết quả trước đó , không phải hàng. Đối với điều này một loại câu hỏi tôi không bao giờ tìm thấy một giải pháp dựa trên tập hợp với hiệu suất chấp nhận được.

Nhưng thật dễ dàng để viết bằng logic hàng. Tôi thích cụ thể hóa một ROW_NUMBERtrong một bảng tạm thời, sau đó tìm hàng tiếp theo chỉ đơn giản là n+1:

CREATE TABLE temp AS
 ( SELECT k, 
      ROW_NUMBER() OVER (ORDER BY k) AS rn 
   FROM t
 )
;
-- I don't know enough about PostgreSQL, but this is probably needed for performance
create unique index on temp(rn)
;

WITH RECURSIVE cte(k,rn, k1) AS 
 (
   SELECT k, rn, k AS k1
   FROM tmp 
   WHERE rn = 1  -- start with the minimum value
   UNION ALL
   SELECT tmp.k, tmp.rn,      
      CASE 
        WHEN tmp.k >= cte.k1 + 5 
        THEN tmp.k   -- use the new value
        ELSE cte.k1  -- keep the old value
      END 
   FROM tmp JOIN cte
     ON tmp.rn = cte.rn+1 -- next row
)
SELECT count(k) 
FROM cte
WHERE k = k1

Thực hiện điều này khá nhanh, tôi bị bắt cóc @ ZiggyCrueltyfreeZeitgeister của fiddle .

Trong thực tế, đây là xử lý logic con trỏ kiểu cũ và con trỏ thực sự có thể nhanh hơn (bạn không cần bảng tạm thời cộng ROW_NUMBERvà đệ quy). Kiến thức PostgreSQL của tôi rất hạn chế và tôi không biết những gì được ưa thích trong PG-đất :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.