PostgreSQL 12 đi kèm với một tính năng mới tuyệt vời, Cột đã tạo. Chức năng hoàn toàn không phải là bất cứ điều gì mới, nhưng tiêu chuẩn hóa, dễ sử dụng, khả năng truy cập và hiệu suất đã được cải thiện trong phiên bản mới này.
Cột được Tạo là một cột đặc biệt trong bảng chứa dữ liệu được tạo tự động từ dữ liệu khác trong hàng. Nội dung của cột đã tạo được tự động điền và cập nhật bất cứ khi nào dữ liệu nguồn, chẳng hạn như bất kỳ cột nào khác trong hàng, được tự thay đổi.
Các cột đã tạo trong PostgreSQL 12+
Trong các phiên bản gần đây của PostgreSQL, các cột được tạo là một tính năng được tích hợp sẵn cho phép các câu lệnh CREATE TABLE hoặc ALTER TABLE thêm một cột trong đó nội dung được tự động ‘tạo’ do một biểu thức. Các biểu thức này có thể là các phép toán đơn giản từ các cột khác hoặc một hàm bất biến nâng cao hơn. Một số lợi ích của việc triển khai cột được tạo vào thiết kế cơ sở dữ liệu bao gồm:
- Khả năng thêm cột vào bảng chứa dữ liệu được tính toán mà không cần cập nhật mã ứng dụng để tạo dữ liệu sau đó đưa nó vào trong các thao tác INSERT và UPDATE.
- Giảm thời gian xử lý đối với các câu lệnh SELECT cực kỳ thường xuyên sẽ xử lý dữ liệu một cách nhanh chóng. Vì quá trình xử lý dữ liệu được thực hiện tại thời điểm INSERT hoặc UPDATE, dữ liệu được tạo ra một lần và các câu lệnh SELECT chỉ cần lấy dữ liệu. Trong môi trường đọc nhiều, điều này có thể thích hợp hơn, miễn là có thể chấp nhận được dung lượng lưu trữ dữ liệu bổ sung được sử dụng.
- Vì các cột đã tạo được cập nhật tự động khi bản thân dữ liệu nguồn được cập nhật, nên việc thêm một cột được tạo sẽ thêm một đảm bảo giả định rằng dữ liệu trong cột được tạo luôn chính xác.
Trong PostgreSQL 12, chỉ có loại cột được tạo là ‘ĐƯỢC LƯU TRỮ’. Trong các hệ thống cơ sở dữ liệu khác, có sẵn một cột được tạo với kiểu ‘VIRTUAL’, hoạt động giống như một dạng xem trong đó kết quả được tính toán nhanh chóng khi dữ liệu được truy xuất. Vì chức năng rất giống với các chế độ xem và chỉ cần viết thao tác vào một câu lệnh đã chọn, chức năng này không mang lại lợi ích như chức năng 'ĐÃ ĐƯỢC LƯU TRỮ' được thảo luận ở đây, nhưng có khả năng các phiên bản trong tương lai sẽ bao gồm tính năng này.
Tạo bảng với một cột đã tạo được thực hiện khi xác định chính cột đó. Trong ví dụ này, cột được tạo là "lợi nhuận" và được tạo tự động bằng cách lấy các cột giá_bán trừ đi_giá_mãi, sau đó nhân với cột_số_lượng_bán.
CREATE TABLE public.transactions (
transactions_sid serial primary key,
transaction_date timestamp with time zone DEFAULT now() NOT NULL,
product_name character varying NOT NULL,
purchase_price double precision NOT NULL,
sale_price double precision NOT NULL,
quantity_sold integer NOT NULL,
profit double precision NOT NULL GENERATED ALWAYS AS ((sale_price - purchase_price) * quantity_sold) STORED
);
Trong ví dụ này, bảng 'giao dịch' được tạo để theo dõi một số giao dịch cơ bản và lợi nhuận của một cửa hàng cà phê tưởng tượng. Chèn dữ liệu vào bảng này sẽ hiển thị một số kết quả ngay lập tức.
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('House Blend Coffee', 5, 11.99, 1);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('French Roast Coffee', 6, 12.99, 4);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('BULK: House Blend Coffee, 10LB', 40, 100, 6);
severalnines=# SELECT * FROM public.transactions;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
1 | 2020-02-28 04:50:06.626371+00 | House Blend Coffee | 5 | 11.99 | 1 | 6.99
2 | 2020-02-28 04:50:53.313572+00 | French Roast Coffee | 6 | 12.99 | 4 | 27.96
3 | 2020-02-28 04:51:08.531875+00 | BULK: House Blend Coffee, 10LB | 40 | 100 | 6 | 360
Khi cập nhật hàng, cột được tạo sẽ tự động cập nhật:
severalnines=# UPDATE public.transactions SET sale_price = 95 WHERE transactions_sid = 3;
UPDATE 1
severalnines=# SELECT * FROM public.transactions WHERE transactions_sid = 3;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
3 | 2020-02-28 05:55:11.233077+00 | BULK: House Blend Coffee, 10LB | 40 | 95 | 6 | 330
Điều này sẽ đảm bảo rằng cột được tạo luôn chính xác, không cần thêm logic nào về phía ứng dụng.
LƯU Ý:Không thể CHÈN hoặc CẬP NHẬT trực tiếp các cột đã tạo và bất kỳ nỗ lực nào để làm như vậy sẽ trả về LỖI:
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold, profit) VALUES ('BULK: House Blend Coffee, 10LB', 40, 95, 1, 95);
ERROR: cannot insert into column "profit"
DETAIL: Column "profit" is a generated column.
severalnines=# UPDATE public.transactions SET profit = 330 WHERE transactions_sid = 3;
ERROR: column "profit" can only be updated to DEFAULT
DETAIL: Column "profit" is a generated column.
Các cột được tạo trên PostgreSQL 11 trở về trước
Mặc dù các cột được tạo tích hợp là mới đối với phiên bản 12 của PostgreSQL, về mặt chức năng vẫn có thể đạt được trong các phiên bản trước đó, nó chỉ cần thiết lập thêm một chút với các thủ tục và trình kích hoạt được lưu trữ. Tuy nhiên, ngay cả với khả năng triển khai nó trên các phiên bản cũ, ngoài chức năng được bổ sung có thể có lợi, việc tuân thủ nghiêm ngặt đầu vào dữ liệu khó đạt được hơn và phụ thuộc vào các tính năng PL / pgSQL và sự khéo léo trong lập trình.
THƯỞNG:Ví dụ dưới đây cũng sẽ hoạt động trên PostgreSQL 12+, vì vậy nếu chức năng được bổ sung với tổ hợp hàm / trình kích hoạt là cần thiết hoặc mong muốn trong các phiên bản mới hơn, tùy chọn này là một dự phòng hợp lệ và không bị giới hạn chỉ là các phiên bản cũ hơn 12.
Mặc dù đây là một cách để thực hiện trên các phiên bản trước của PostgreSQL, nhưng có một số lợi ích bổ sung của phương pháp này:
- Vì việc bắt chước cột đã tạo sử dụng một hàm nên có thể sử dụng các phép tính phức tạp hơn. Các cột đã tạo trong phiên bản 12 yêu cầu hoạt động NGAY LẬP TỨC, nhưng tùy chọn kích hoạt / chức năng có thể sử dụng loại chức năng ỔN ĐỊNH hoặc VOLATILE với nhiều khả năng hơn và hiệu suất tương ứng có thể thấp hơn.
- Sử dụng một hàm có tùy chọn là ỔN ĐỊNH hoặc VOLATILE cũng mở ra khả năng CẬP NHẬT các cột bổ sung, CẬP NHẬT các bảng khác hoặc thậm chí tạo dữ liệu mới thông qua INSERTS vào các bảng khác. (Tuy nhiên, mặc dù các tùy chọn kích hoạt / chức năng này linh hoạt hơn nhiều, nhưng điều đó không có nghĩa là thiếu "Cột được tạo" thực tế, vì nó thực hiện những gì được quảng cáo với hiệu suất và hiệu quả cao hơn.)
Trong ví dụ này, một trình kích hoạt / chức năng được thiết lập để bắt chước chức năng của cột được tạo từ PostgreSQL 12+, cùng với hai phần tạo ra một ngoại lệ nếu một INSERT hoặc UPDATE cố gắng thay đổi cột đã tạo . Những điều này có thể được bỏ qua, nhưng nếu chúng bị bỏ qua, các ngoại lệ sẽ không được đưa ra và dữ liệu thực tế được INSERTed hoặc UPDATEd sẽ bị loại bỏ một cách lặng lẽ, điều này thường không được khuyến nghị.
Bản thân trình kích hoạt được đặt để chạy TRƯỚC, có nghĩa là quá trình xử lý xảy ra trước khi việc chèn thực sự xảy ra và yêu cầu TRỞ LẠI MỚI, là BẢN GHI được sửa đổi để chứa giá trị cột được tạo mới. Ví dụ cụ thể này được viết để chạy trên PostgreSQL phiên bản 11.
CREATE TABLE public.transactions (
transactions_sid serial primary key,
transaction_date timestamp with time zone DEFAULT now() NOT NULL,
product_name character varying NOT NULL,
purchase_price double precision NOT NULL,
sale_price double precision NOT NULL,
quantity_sold integer NOT NULL,
profit double precision NOT NULL
);
CREATE OR REPLACE FUNCTION public.generated_column_function()
RETURNS trigger
LANGUAGE plpgsql
IMMUTABLE
AS $function$
BEGIN
-- This statement mimics the ERROR on built in generated columns to refuse INSERTS on the column and return an ERROR.
IF (TG_OP = 'INSERT') THEN
IF (NEW.profit IS NOT NULL) THEN
RAISE EXCEPTION 'ERROR: cannot insert into column "profit"' USING DETAIL = 'Column "profit" is a generated column.';
END IF;
END IF;
-- This statement mimics the ERROR on built in generated columns to refuse UPDATES on the column and return an ERROR.
IF (TG_OP = 'UPDATE') THEN
-- Below, IS DISTINCT FROM is used because it treats nulls like an ordinary value.
IF (NEW.profit::VARCHAR IS DISTINCT FROM OLD.profit::VARCHAR) THEN
RAISE EXCEPTION 'ERROR: cannot update column "profit"' USING DETAIL = 'Column "profit" is a generated column.';
END IF;
END IF;
NEW.profit := ((NEW.sale_price - NEW.purchase_price) * NEW.quantity_sold);
RETURN NEW;
END;
$function$;
CREATE TRIGGER generated_column_trigger BEFORE INSERT OR UPDATE ON public.transactions FOR EACH ROW EXECUTE PROCEDURE public.generated_column_function();
LƯU Ý:Đảm bảo hàm có quyền / quyền sở hữu chính xác để (các) người dùng ứng dụng mong muốn thực thi.
Như đã thấy trong ví dụ trước, kết quả giống nhau trong các phiên bản trước với giải pháp hàm / trình kích hoạt:
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('House Blend Coffee', 5, 11.99, 1);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('French Roast Coffee', 6, 12.99, 4);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('BULK: House Blend Coffee, 10LB', 40, 100, 6);
severalnines=# SELECT * FROM public.transactions;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
1 | 2020-02-28 00:35:14.855511-07 | House Blend Coffee | 5 | 11.99 | 1 | 6.99
2 | 2020-02-28 00:35:21.764449-07 | French Roast Coffee | 6 | 12.99 | 4 | 27.96
3 | 2020-02-28 00:35:27.708761-07 | BULK: House Blend Coffee, 10LB | 40 | 100 | 6 | 360
Cập nhật dữ liệu cũng tương tự.
severalnines=# UPDATE public.transactions SET sale_price = 95 WHERE transactions_sid = 3;
UPDATE 1
severalnines=# SELECT * FROM public.transactions WHERE transactions_sid = 3;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
3 | 2020-02-28 00:48:52.464344-07 | BULK: House Blend Coffee, 10LB | 40 | 95 | 6 | 330
Cuối cùng, cố gắng CHÈN vào hoặc CẬP NHẬT chính cột đặc biệt sẽ dẫn đến LỖI:
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold, profit) VALUES ('BULK: House Blend Coffee, 10LB', 40, 95, 1, 95);
ERROR: ERROR: cannot insert into column "profit"
DETAIL: Column "profit" is a generated column.
CONTEXT: PL/pgSQL function generated_column_function() line 7 at RAISE
severalnines=# UPDATE public.transactions SET profit = 3030 WHERE transactions_sid = 3;
ERROR: ERROR: cannot update column "profit"
DETAIL: Column "profit" is a generated column.
CONTEXT: PL/pgSQL function generated_column_function() line 15 at RAISE
Trong ví dụ này, nó hoạt động khác với thiết lập cột được tạo đầu tiên theo một số cách cần lưu ý:
- Nếu cố gắng cập nhật 'cột đã tạo' nhưng không tìm thấy hàng nào được cập nhật, nó sẽ trả về thành công với kết quả “CẬP NHẬT 0”, trong khi Cột được tạo thực tế trong phiên bản 12 vẫn sẽ trả về LỖI, ngay cả khi không tìm thấy hàng nào để CẬP NHẬT.
- Khi cố gắng cập nhật cột lợi nhuận, cột 'phải' luôn trả về LỖI, nếu giá trị được chỉ định giống với giá trị được 'tạo' chính xác thì cột đó sẽ thành công. Tuy nhiên, cuối cùng thì dữ liệu là chính xác, nếu bạn muốn trả về một LỖI nếu cột được chỉ định.
Tài liệu và Cộng đồng PostgreSQL
Tài liệu chính thức cho Cột được tạo PostgreSQL có tại Trang web PostgreSQL chính thức. Kiểm tra lại thời điểm các phiên bản chính mới của PostgreSQL được phát hành để khám phá các tính năng mới khi chúng xuất hiện.
Mặc dù các cột được tạo trong PostgreSQL 12 khá dễ hiểu, nhưng việc triển khai chức năng tương tự trong các phiên bản trước có khả năng phức tạp hơn nhiều. Cộng đồng PostgreSQL là một cộng đồng rất tích cực, lớn, rộng khắp trên toàn thế giới và đa ngôn ngữ dành riêng để giúp mọi người ở bất kỳ cấp độ nào về trải nghiệm PostgreSQL giải quyết các vấn đề và tạo ra các giải pháp mới như thế này.
- IRC :Freenode có một kênh rất tích cực gọi là #postgres, nơi người dùng giúp nhau hiểu các khái niệm, sửa lỗi hoặc tìm các tài nguyên khác. Bạn có thể tìm thấy danh sách đầy đủ các kênh freenode sẵn có về PostgreSQL trên trang web PostgreSQL.org.
- Danh sách gửi thư :PostgreSQL có một số danh sách gửi thư có thể được tham gia. Các câu hỏi / vấn đề dạng dài hơn có thể được gửi tại đây và có thể tiếp cận nhiều người hơn IRC tại bất kỳ thời điểm nào. Bạn có thể tìm thấy danh sách trên Trang web PostgreSQL và danh sách pgsql-general hoặc pgsql-admin là những tài nguyên tốt.
- Slack :Cộng đồng PostgreSQL cũng đã phát triển mạnh trên Slack và có thể tham gia tại postgresteam.slack.com. Giống như IRC, một cộng đồng năng động luôn sẵn sàng trả lời các câu hỏi và tham gia vào tất cả mọi thứ về PostgreSQL.