Giả sử Postgres 9.1 trở lên.
Tôi đã đơn giản hóa / tối ưu hóa truy vấn cơ bản của bạn để truy xuất các giá trị mới nhất:
SELECT DISTINCT ON (1,2)
c.unique_id, a.attname AS col, c.value
FROM pg_attribute a
LEFT JOIN changes c ON c.column_name = a.attname
AND c.table_name = 'instances'
-- AND c.unique_id = 3 -- uncomment to fetch single row
WHERE a.attrelid = 'instances'::regclass -- schema-qualify to be clear?
AND a.attnum > 0 -- no system columns
AND NOT a.attisdropped -- no deleted columns
ORDER BY 1, 2, c.updated_at DESC;
Tôi truy vấn danh mục PostgreSQL thay vì lược đồ thông tin tiêu chuẩn vì điều đó nhanh hơn. Lưu ý diễn viên đặc biệt cho ::regclass
.
Bây giờ, điều đó cung cấp cho bạn bảng . Bạn muốn tất cả các giá trị cho một unique_id
trong một hàng .
Để đạt được điều đó, về cơ bản bạn có ba lựa chọn:
-
Một lựa chọn con (hoặc tham gia) trên mỗi cột. Đắt tiền và khó sử dụng. Nhưng một tùy chọn hợp lệ chỉ cho một vài cột.
-
CASE
lớn tuyên bố. -
Một chức năng tổng hợp . PostgreSQL cung cấp
crosstab()
chức năng trong mô-đun bổ sungtablefunc
cho điều đó.
Hướng dẫn cơ bản:- Truy vấn bảng chéo PostgreSQL
Bảng tổng hợp cơ bản với crosstab()
Tôi đã viết lại hoàn toàn hàm:
SELECT *
FROM crosstab(
$x$
SELECT DISTINCT ON (1, 2)
unique_id, column_name, value
FROM changes
WHERE table_name = 'instances'
-- AND unique_id = 3 -- un-comment to fetch single row
ORDER BY 1, 2, updated_at DESC;
$x$,
$y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = 'instances'::regclass -- possibly schema-qualify table name
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$
)
AS tbl (
unique_id integer
-- !!! You have to list all columns in order here !!! --
);
Tôi đã tách tra cứu danh mục khỏi truy vấn giá trị, dưới dạng crosstab()
hàm với hai tham số cung cấp tên cột riêng biệt. Các giá trị bị thiếu (không có mục thay đổi) được thay thế bằng NULL
tự động. Một kết hợp hoàn hảo cho trường hợp sử dụng này!
Giả sử rằng attname
khớp với column_name
. Loại trừ unique_id
, đóng một vai trò đặc biệt.
Tự động hóa hoàn toàn
Giải quyết nhận xét của bạn: Có một cách để cung cấp danh sách định nghĩa cột tự động. Tuy nhiên, nó không dành cho những người yếu tim.
Tôi sử dụng một số tính năng nâng cao của Postgres tại đây:crosstab()
, hàm plpgsql với SQL động, xử lý kiểu kết hợp, trích dẫn đô la nâng cao, tra cứu danh mục, hàm tổng hợp, hàm cửa sổ, loại định danh đối tượng, ...
Môi trường thử nghiệm:
CREATE TABLE instances (
unique_id int
, col1 text
, col2 text -- two columns are enough for the demo
);
INSERT INTO instances VALUES
(1, 'foo1', 'bar1')
, (2, 'foo2', 'bar2')
, (3, 'foo3', 'bar3')
, (4, 'foo4', 'bar4');
CREATE TABLE changes (
unique_id int
, table_name text
, column_name text
, value text
, updated_at timestamp
);
INSERT INTO changes VALUES
(1, 'instances', 'col1', 'foo11', '2012-04-12 00:01')
, (1, 'instances', 'col1', 'foo12', '2012-04-12 00:02')
, (1, 'instances', 'col1', 'foo1x', '2012-04-12 00:03')
, (1, 'instances', 'col2', 'bar11', '2012-04-12 00:11')
, (1, 'instances', 'col2', 'bar17', '2012-04-12 00:12')
, (1, 'instances', 'col2', 'bar1x', '2012-04-12 00:13')
, (2, 'instances', 'col1', 'foo2x', '2012-04-12 00:01')
, (2, 'instances', 'col2', 'bar2x', '2012-04-12 00:13')
-- NO change for col1 of row 3 - to test NULLs
, (3, 'instances', 'col2', 'bar3x', '2012-04-12 00:13');
-- NO changes at all for row 4 - to test NULLs
Chức năng tự động cho một bảng
CREATE OR REPLACE FUNCTION f_curr_instance(int, OUT t public.instances) AS
$func$
BEGIN
EXECUTE $f$
SELECT *
FROM crosstab($x$
SELECT DISTINCT ON (1,2)
unique_id, column_name, value
FROM changes
WHERE table_name = 'instances'
AND unique_id = $f$ || $1 || $f$
ORDER BY 1, 2, updated_at DESC;
$x$
, $y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = 'public.instances'::regclass
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$) AS tbl ($f$
|| (SELECT string_agg(attname || ' ' || atttypid::regtype::text
, ', ' ORDER BY attnum) -- must be in order
FROM pg_catalog.pg_attribute
WHERE attrelid = 'public.instances'::regclass
AND attnum > 0
AND NOT attisdropped)
|| ')'
INTO t;
END
$func$ LANGUAGE plpgsql;
Bảng instances
được mã hóa cứng, lược đồ đủ điều kiện để rõ ràng. Lưu ý việc sử dụng kiểu bảng làm kiểu trả về. Có một loại hàng được đăng ký tự động cho mọi bảng trong PostgreSQL. Điều này được ràng buộc phải khớp với kiểu trả về của crosstab()
chức năng.
Điều này liên kết hàm với loại bảng:
- Bạn sẽ nhận được thông báo lỗi nếu bạn cố gắng
DROP
cái bàn - Chức năng của bạn sẽ không thành công sau một
ALTER TABLE
. Bạn phải tạo lại nó (không có thay đổi). Tôi coi đây là một lỗi trong 9.1.ALTER TABLE
không nên âm thầm phá vỡ chức năng mà gây ra lỗi.
Điều này hoạt động rất tốt.
Gọi:
SELECT * FROM f_curr_instance(3);
unique_id | col1 | col2
----------+-------+-----
3 |<NULL> | bar3x
Lưu ý cách làm col1
là NULL
tại đây.
Sử dụng trong truy vấn để hiển thị một phiên bản với các giá trị mới nhất của nó:
SELECT i.unique_id
, COALESCE(c.col1, i.col1)
, COALESCE(c.col2, i.col2)
FROM instances i
LEFT JOIN f_curr_instance(3) c USING (unique_id)
WHERE i.unique_id = 3;
Tự động hóa hoàn toàn cho bất kỳ bảng nào
(Đã thêm năm 2016. Đây là thuốc nổ.)
Yêu cầu Postgres 9.1 hoặc sau đó. (Có thể được tạo ra để hoạt động với trang 8,4, nhưng tôi không bận tâm đến việc vá lại.)
CREATE OR REPLACE FUNCTION f_curr_instance(_id int, INOUT _t ANYELEMENT) AS
$func$
DECLARE
_type text := pg_typeof(_t);
BEGIN
EXECUTE
(
SELECT format
($f$
SELECT *
FROM crosstab(
$x$
SELECT DISTINCT ON (1,2)
unique_id, column_name, value
FROM changes
WHERE table_name = %1$L
AND unique_id = %2$s
ORDER BY 1, 2, updated_at DESC;
$x$
, $y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = %1$L::regclass
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$) AS ct (%3$s)
$f$
, _type, _id
, string_agg(attname || ' ' || atttypid::regtype::text
, ', ' ORDER BY attnum) -- must be in order
)
FROM pg_catalog.pg_attribute
WHERE attrelid = _type::regclass
AND attnum > 0
AND NOT attisdropped
)
INTO _t;
END
$func$ LANGUAGE plpgsql;
Gọi (cung cấp loại bảng với NULL::public.instances
:
SELECT * FROM f_curr_instance(3, NULL::public.instances);
Có liên quan:
- Cấu trúc lại một hàm PL / pgSQL để trả về kết quả đầu ra của các truy vấn SELECT khác nhau
- Cách đặt giá trị của trường biến tổng hợp bằng SQL động