Tìm các hàng trong đó chuỗi số nguyên chứa một chuỗi con đã cho


9

Vấn đề

Lưu ý: Tôi đề cập đến các trình tự toán học , không phải cơ chế trình tự của PostgreSQL .

Tôi có một bảng biểu diễn các chuỗi số nguyên. Định nghĩa là:

CREATE TABLE sequences
(
  id serial NOT NULL,
  title character varying(255) NOT NULL,
  date date NOT NULL,
  sequence integer[] NOT NULL,
  CONSTRAINT "PRIM_KEY_SEQUENCES" PRIMARY KEY (id)
);

Mục tiêu của tôi là tìm các hàng bằng cách sử dụng một chuỗi con cho trước. Điều đó có nghĩa là, các hàng trong đó sequencetrường là một chuỗi chứa chuỗi con đã cho (trong trường hợp của tôi, chuỗi được sắp xếp).

Thí dụ

Giả sử bảng chứa dữ liệu sau:

+----+-------+------------+-------------------------------+
| id | title |    date    |           sequence            |
+----+-------+------------+-------------------------------+
|  1 | BG703 | 2004-12-24 | {1,3,17,25,377,424,242,1234}  |
|  2 | BG256 | 2005-05-11 | {5,7,12,742,225,547,2142,223} |
|  3 | BD404 | 2004-10-13 | {3,4,12,5698,526}             |
|  4 | BK956 | 2004-08-17 | {12,4,3,17,25,377,456,25}     |
+----+-------+------------+-------------------------------+

Vì vậy, nếu thứ tự đã cho là {12, 742, 225, 547}, tôi muốn tìm hàng 2.

Tương tự, nếu thứ tự đã cho là {3, 17, 25, 377}, tôi muốn tìm hàng 1 và hàng 4.

Cuối cùng, nếu thứ tự đã cho là {12, 4, 3, 25, 377}, thì không có hàng nào được trả về.

Điều tra

Đầu tiên, tôi không hoàn toàn chắc chắn rằng đại diện cho chuỗi với một kiểu dữ liệu mảng là khôn ngoan. Mặc dù điều này có vẻ phù hợp với tình hình; Tôi sợ nó làm cho xử lý phức tạp hơn. Có lẽ tốt hơn là biểu diễn các chuỗi khác nhau, sử dụng mô hình quan hệ với một bảng khác.

Theo cách tương tự, tôi nghĩ về việc mở rộng các chuỗi bằng cách sử dụng unnesthàm mảng và sau đó thêm tiêu chí tìm kiếm của tôi. Tuy nhiên, số lượng thuật ngữ trong chuỗi là biến tôi không thấy làm thế nào để làm điều đó.

Tôi biết cũng có thể cắt chuỗi của mình sau đó bằng cách sử dụng subarraychức năng của mô-đun intarray nhưng tôi không thấy nó mang lại lợi ích gì cho tìm kiếm của tôi.

Những ràng buộc

Ngay cả khi tại thời điểm mô hình của tôi vẫn đang được phát triển, bảng dự định sẽ bao gồm nhiều chuỗi, từ 50.000 đến 300.000 hàng. Vì vậy, tôi có một hạn chế hiệu suất mạnh mẽ.

Trong ví dụ của tôi, tôi đã sử dụng các số nguyên tương đối nhỏ. Trong thực tế, có thể các số nguyên này trở nên lớn hơn nhiều, lên đến mức tràn bigint. Trong tình huống như vậy, tôi nghĩ tốt nhất là lưu trữ các số dưới dạng chuỗi (vì không cần thiết phải thực hiện các chuỗi hoạt động toán học này). Tuy nhiên, lựa chọn giải pháp này, điều này khiến cho không thể sử dụng mô-đun intarray , được đề cập ở trên.


Nếu chúng có thể tràn, bigintbạn nên sử dụng numericlàm loại để lưu trữ chúng. Nó chậm hơn rất nhiều và chiếm nhiều phòng hơn.
Craig Ringer

@CraigRinger Tại sao sử dụng numericvà không phải là một chuỗi ( textví dụ)? Tôi không cần phải thực hiện các phép toán trên chuỗi của mình.
mlpo

2
Bởi vì nó nhỏ gọn hơn và theo nhiều cách nhanh hơn textvà ngăn bạn lưu trữ dữ liệu không phải là số. Tùy thuộc, nếu bạn chỉ thực hiện I / O, bạn có thể muốn văn bản giảm xử lý I / O.
Craig Ringer

@CraigRinger Thật vậy, loại này phù hợp hơn. Về hiệu suất, tôi sẽ kiểm tra khi tôi tìm được cách thực hiện tìm kiếm của mình.
mlpo

2
@CraigRinger Nó có thể hoạt động nếu thứ tự không quan trọng. Nhưng ở đây, các trình tự được ra lệnh. Ví dụ: SELECT ARRAY[12, 4, 3, 17, 25, 377, 456, 25] @> ARRAY[12, 4, 3, 25, 377];sẽ trả về true, bởi vì lệnh này không được xem xét bởi toán tử này.
mlpo

Câu trả lời:


3

Nếu bạn đang tìm kiếm các cải tiến hiệu suất đáng kể cho câu trả lời của dnoeth , hãy xem xét sử dụng hàm C gốc và tạo toán tử thích hợp.

Dưới đây là một ví dụ cho mảng int4. ( Một biến thể mảng chungtập lệnh SQL tương ứng ).

Datum
_int_sequence_contained(PG_FUNCTION_ARGS)
{
    return DirectFunctionCall2(_int_contains_sequence,
                               PG_GETARG_DATUM(1),
                               PG_GETARG_DATUM(0));
}

Datum
_int_contains_sequence(PG_FUNCTION_ARGS)
{
    ArrayType  *a = PG_GETARG_ARRAYTYPE_P(0);
    ArrayType  *b = PG_GETARG_ARRAYTYPE_P(1);
    int         na, nb;
    int32      *pa, *pb;
    int         i, j;

    na = ArrayGetNItems(ARR_NDIM(a), ARR_DIMS(a));
    nb = ArrayGetNItems(ARR_NDIM(b), ARR_DIMS(b));
    pa = (int32 *) ARR_DATA_PTR(a);
    pb = (int32 *) ARR_DATA_PTR(b);

    /* The naive searching algorithm. Replace it with a better one if your arrays are quite large. */
    for (i = 0; i <= na - nb; ++i)
    {
        for (j = 0; j < nb; ++j)
            if (pa[i + j] != pb[j])
                break;

        if (j == nb)
            PG_RETURN_BOOL(true);
    }

    PG_RETURN_BOOL(false);
}
CREATE FUNCTION _int_contains_sequence(_int4, _int4)
RETURNS bool
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT IMMUTABLE;

CREATE FUNCTION _int_sequence_contained(_int4, _int4)
RETURNS bool
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT IMMUTABLE;

CREATE OPERATOR @@> (
  LEFTARG = _int4,
  RIGHTARG = _int4,
  PROCEDURE = _int_contains_sequence,
  COMMUTATOR = '<@@',
  RESTRICT = contsel,
  JOIN = contjoinsel
);

CREATE OPERATOR <@@ (
  LEFTARG = _int4,
  RIGHTARG = _int4,
  PROCEDURE = _int_sequence_contained,
  COMMUTATOR = '@@>',
  RESTRICT = contsel,
  JOIN = contjoinsel
);

Bây giờ bạn có thể lọc các hàng như thế này.

SELECT * FROM sequences WHERE sequence @@> '{12, 742, 225, 547}'

Tôi đã tiến hành một thí nghiệm nhỏ để tìm ra giải pháp này nhanh hơn bao nhiêu.

CREATE TEMPORARY TABLE sequences AS
SELECT array_agg((random() * 10)::int4) AS sequence, g1 AS id
FROM generate_series(1, 100000) g1
  CROSS JOIN generate_series(1, 30) g2
GROUP BY g1;
EXPLAIN ANALYZE SELECT * FROM sequences
WHERE        translate(cast(sequence as text), '{}',',,')
 LIKE '%' || translate(cast('{1,2,3,4}'as text), '{}',',,') || '%'

"Seq Scan on sequences  (cost=0.00..7869.42 rows=28 width=36) (actual time=2.487..334.318 rows=251 loops=1)"
"  Filter: (translate((sequence)::text, '{}'::text, ',,'::text) ~~ '%,1,2,3,4,%'::text)"
"  Rows Removed by Filter: 99749"
"Planning time: 0.104 ms"
"Execution time: 334.365 ms"
EXPLAIN ANALYZE SELECT * FROM sequences WHERE sequence @@> '{1,2,3,4}'

"Seq Scan on sequences  (cost=0.00..5752.01 rows=282 width=36) (actual time=0.178..20.792 rows=251 loops=1)"
"  Filter: (sequence @@> '{1,2,3,4}'::integer[])"
"  Rows Removed by Filter: 99749"
"Planning time: 0.091 ms"
"Execution time: 20.859 ms"

Vì vậy, nó nhanh hơn khoảng 16 lần. Nếu nó không đủ, bạn có thể thêm hỗ trợ cho các chỉ mục GIN hoặc GiST, nhưng đây sẽ là nhiệm vụ khó khăn hơn nhiều.


Nghe có vẻ thú vị, tuy nhiên tôi sử dụng một trong hai chuỗi hoặc loại numericđể thể hiện dữ liệu của mình vì chúng có thể bị tràn bigint. Có thể tốt để chỉnh sửa câu trả lời của bạn để phù hợp với các ràng buộc của câu hỏi. Dù sao, tôi sẽ làm một hiệu suất so sánh mà tôi sẽ đăng ở đây.
mlpo

Tôi không chắc chắn liệu có phải là một cách thực hành tốt để dán các khối mã lớn vào câu trả lời hay không vì chúng được cho là tối thiểu và có thể kiểm chứng được. Một phiên bản mảng chung của chức năng này dài hơn bốn lần và khá cồng kềnh. Tôi cũng đã thử nghiệm nó numerictextvà sự cải thiện dao động từ 20 đến 50 lần tùy thuộc vào độ dài của mảng.
Slonopotamus

Có, tuy nhiên điều cần thiết là các câu trả lời đã trả lời các câu hỏi :-). Ở đây, dường như với tôi rằng một câu trả lời tuân theo các ràng buộc là thú vị (bởi vì khía cạnh này là một phần của câu hỏi). Tuy nhiên, có thể không cần thiết phải đề xuất một phiên bản chung. Chỉ là một phiên bản có chuỗi hoặc numeric.
mlpo

Dù sao, tôi đã thêm phiên bản cho mảng chung vì nó sẽ gần giống nhau cho bất kỳ loại dữ liệu có độ dài thay đổi. Nhưng nếu bạn thực sự lo lắng về hiệu suất, bạn nên gắn bó với các loại dữ liệu có kích thước cố định như thế nào bigint.
Slonopotamus

Tôi rất thích làm điều đó. Vấn đề là một số trình tự của tôi vượt xa bigint, vì vậy có vẻ như tôi không có lựa chọn nào khác. Nhưng nếu bạn có một ý tưởng, tôi quan tâm :).
mlpo

1

Bạn có thể dễ dàng tìm thấy chuỗi con khi bạn truyền các mảng thành chuỗi và thay thế dấu ngoặc nhọn bằng dấu phẩy:

translate(cast(sequence as varchar(10000)), '{}',',,')

{1,3,17,25,377,424,242,1234} -> ',1,3,17,25,377,424,242,1234,'

Thực hiện tương tự cho mảng bạn đang tìm kiếm và thêm một hàng đầu và dấu %:

'%' || translate(cast(searchedarray as varchar(10000)), '{}',',,') || '%'

{3, 17, 25, 377} -> '%,3,17,25,377,%'

Bây giờ bạn so sánh nó bằng cách sử dụng LIKE:

WHERE        translate(cast(sequence      as varchar(10000)), '{}',',,')
 LIKE '%' || translate(cast(searchedarray as varchar(10000)), '{}',',,') || '%'

Biên tập:

Vĩ cầm đang hoạt động trở lại.

Nếu các mảng được chuẩn hóa thành một hàng cho mỗi giá trị, bạn có thể áp dụng logic dựa trên tập hợp:

CREATE TABLE sequences
( id int NOT NULL,
  n int not null,
  val numeric not null
);

insert into sequences values(  1, 1,1     );
insert into sequences values(  1, 2,3     );
insert into sequences values(  1, 3,17    );
insert into sequences values(  1, 4,25    );
insert into sequences values(  1, 5,377   );
insert into sequences values(  1, 6,424   );
insert into sequences values(  1, 7,242   );
insert into sequences values(  1, 8,1234  );
insert into sequences values(  2, 1,5     );
insert into sequences values(  2, 2,7     );
insert into sequences values(  2, 3,12    );
insert into sequences values(  2, 4,742   );
insert into sequences values(  2, 5,225   );
insert into sequences values(  2, 6,547   );
insert into sequences values(  2, 7,2142  );
insert into sequences values(  2, 8,223   );
insert into sequences values(  3, 1,3     );
insert into sequences values(  3, 2,4     );
insert into sequences values(  3, 3,12    );
insert into sequences values(  3, 4,5698  );
insert into sequences values(  3, 5,526   );          
insert into sequences values(  4, 1,12    );
insert into sequences values(  4, 2,4     );
insert into sequences values(  4, 3,3     );
insert into sequences values(  4, 4,17    );
insert into sequences values(  4, 5,25    );
insert into sequences values(  4, 6,377   );
insert into sequences values(  4, 7,456   );
insert into sequences values(  4, 8,25    );
insert into sequences values(  5, 1,12    );
insert into sequences values(  5, 2,4     );
insert into sequences values(  5, 3,3     );
insert into sequences values(  5, 4,17    );
insert into sequences values(  5, 5,17    );
insert into sequences values(  5, 6,25    );
insert into sequences values(  5, 7,377   );
insert into sequences values(  5, 8,456   );
insert into sequences values(  5, 9,25    );

nphải tuần tự, không trùng lặp, không có khoảng trống. Bây giờ hãy tham gia vào các giá trị chung và khai thác thực tế là các chuỗi là tuần tự :-)

with searched (n,val) as (
  VALUES
   ( 1,3  ),
   ( 2,17 ),
   ( 3,25 ),
   ( 4,377)
)
select seq.id, 
   -- this will return the same result if the values from both tables are in the same order
   -- it's a meaningless dummy, but the same meaningless value for sequential rows 
   seq.n - s.n as dummy,
   seq.val,
   seq.n,
   s.n 
from sequences as seq join searched as s
on seq.val = s.val
order by seq.id, dummy, seq.n;

Cuối cùng đếm số hàng có cùng hình nộm và kiểm tra xem đó có phải là số chính xác không:

with searched (n,val) as (
  VALUES
   ( 1,3  ),
   ( 2,17 ),
   ( 3,25 ),
   ( 4,377)
)
select distinct seq.id
from sequences as seq join searched as s
on seq.val = s.val
group by 
   seq.id,
   seq.n - s.n
having count(*) = (select count(*) from searched)
;

Hãy thử một chỉ mục trên các chuỗi (val, id, n).


Tôi cũng đã xem xét giải pháp này sau đó. Nhưng tôi thấy một số vấn đề có vẻ khá phiền phức: trước hết tôi sợ rằng giải pháp này rất kém hiệu quả, chúng ta phải bỏ từng mảng của mỗi hàng trước khi tạo mẫu tìm kiếm. Có thể xem xét việc lưu trữ các chuỗi trong một TEXTtrường ( varchartheo ý kiến ​​của tôi là ý tưởng tồi, các chuỗi có thể dài, vì các con số, vì vậy kích thước khá khó đoán), để tránh bỏ qua; nhưng vẫn không thể sử dụng các chỉ mục để cải thiện hiệu suất (hơn nữa sử dụng trường chuỗi có vẻ không nhất thiết phải hợp lý, xem bình luận của @CraigRinger ở trên).
mlpo

@mlpo: Kỳ vọng về hiệu suất của bạn là gì? Để có thể sử dụng một chỉ mục, bạn phải chuẩn hóa chuỗi thành một hàng trên mỗi giá trị, áp dụng Bộ phận quan hệ và cuối cùng kiểm tra xem thứ tự có đúng không. Trong ví dụ của bạn 25tồn tại hai lần trong id=4, điều này thực sự có thể? Có bao nhiêu trận đấu tồn tại ở mức trung bình / tối đa cho một chuỗi tìm kiếm?
vào

Một chuỗi có thể chứa nhiều lần cùng một số. Ví dụ {1, 1, 1, 1, 12, 2, 2, 12, 12, 1, 1, 5, 4}là hoàn toàn có thể. Về số lượng trận đấu, các chuỗi được sử dụng thường được cho là giới hạn số lượng kết quả. Tuy nhiên, một số trình tự rất giống nhau và đôi khi có thể thú vị khi sử dụng đoạn tiếp theo ngắn hơn để có được nhiều kết quả hơn. Tôi ước tính rằng số lượng các trận đấu cho phần lớn các trường hợp là từ 0 đến 100. Luôn có khả năng đôi khi kết quả khớp với rất nhiều chuỗi khi nó ngắn hoặc rất phổ biến.
mlpo

@mlpo: Tôi đã thêm một giải pháp dựa trên tập hợp và tôi sẽ rất quan tâm đến một số so sánh hiệu suất :-)
dnoeth 29/07/2015

@ypercube: Đây chỉ là một bổ sung nhanh để trả về một kết quả có ý nghĩa hơn :-) Ok, thật kinh khủng, tôi sẽ thay đổi nó
dnoeth 29/07/2015
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.