Làm thế nào để trả về kết quả của một SELECT bên trong một hàm trong PostgreSQL?


106

Tôi có hàm này trong PostgreSQL, nhưng tôi không biết cách trả về kết quả của truy vấn:

CREATE OR REPLACE FUNCTION wordFrequency(maxTokens INTEGER)
  RETURNS SETOF RECORD AS
$$
BEGIN
    SELECT text, count(*), 100 / maxTokens * count(*)
    FROM (
        SELECT text
    FROM token
    WHERE chartype = 'ALPHABETIC'
    LIMIT maxTokens
    ) as tokens
    GROUP BY text
    ORDER BY count DESC
END
$$
LANGUAGE plpgsql;

Nhưng tôi không biết cách trả về kết quả của truy vấn bên trong hàm PostgreSQL.

Tôi thấy rằng kiểu trả về phải là SETOF RECORD, phải không? Nhưng lệnh return không đúng.

Cách đúng đắn để làm điều này là gì?


Tại sao bạn đếm chúng; bạn có mã thông báo trùng lặp trong BẢNG mã thông báo của bạn không? Ngoài ra: vui lòng thêm định nghĩa bảng vào câu hỏi của bạn.
wildplasser

1
Đây có phải là toàn bộ chức năng của bạn? Nếu bạn không có bất kỳ câu lệnh nào khác trong hàm, bạn chỉ nên tạo nó LANGUAGE SQL.
jpmc26

Câu trả lời:


134

Sử dụng RETURN QUERY:

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt   text   -- also visible as OUT parameter inside function
               , cnt   bigint
               , ratio bigint) AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt
        , count(*) AS cnt                 -- column alias only visible inside
        , (count(*) * 100) / _max_tokens  -- I added brackets
   FROM  (
      SELECT t.txt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      LIMIT  _max_tokens
      ) t
   GROUP  BY t.txt
   ORDER  BY cnt DESC;                    -- potential ambiguity 
END
$func$  LANGUAGE plpgsql;

Gọi:

SELECT * FROM word_frequency(123);

Giải trình:

  • Thực tế hơn nhiều là xác định rõ ràng kiểu trả về hơn là chỉ khai báo nó dưới dạng bản ghi. Bằng cách này, bạn không phải cung cấp danh sách định nghĩa cột với mọi lệnh gọi hàm. RETURNS TABLElà một cách để làm điều đó. Co nhung nguoi khac. Các kiểu dữ liệu của OUTtham số phải khớp chính xác với những gì được trả về bởi truy vấn.

  • Chọn tên cho OUTcác thông số một cách cẩn thận. Chúng có thể nhìn thấy trong cơ quan chức năng ở hầu hết mọi nơi. Bảng phân loại các cột có cùng tên để tránh xung đột hoặc kết quả không mong muốn. Tôi đã làm điều đó cho tất cả các cột trong ví dụ của tôi.

    Nhưng lưu ý rằng xung đột đặt tên tiềm ẩn giữa OUTtham số cntvà bí danh cột cùng tên. Trong trường hợp cụ thể này ( RETURN QUERY SELECT ...) Postgres sử dụng bí danh cột trên OUTtham số theo cả hai cách. Tuy nhiên, điều này có thể không rõ ràng trong các ngữ cảnh khác. Có nhiều cách khác nhau để tránh bất kỳ sự nhầm lẫn nào:

    1. Sử dụng vị trí thứ tự của các mục trong danh sách SELECT: ORDER BY 2 DESC. Thí dụ:
    2. Lặp lại biểu thức ORDER BY count(*).
    3. (Không áp dụng ở đây.) Đặt tham số cấu hình plpgsql.variable_conflicthoặc sử dụng lệnh đặc biệt #variable_conflict error | use_variable | use_columntrong hàm. Xem:
  • Không sử dụng "văn bản" hoặc "đếm" làm tên cột. Cả hai đều hợp pháp để sử dụng trong Postgres, nhưng "count" là một từ dành riêng trong SQL tiêu chuẩn và là tên hàm cơ bản và "text" là kiểu dữ liệu cơ bản. Có thể dẫn đến những lỗi khó hiểu. Tôi sử dụng txtcnttrong các ví dụ của tôi.

  • Đã thêm lỗi bị thiếu ;và sửa lỗi cú pháp trong tiêu đề. (_max_tokens int), không phải (int maxTokens)- nhập sau tên .

  • Trong khi làm việc với phép chia số nguyên, tốt hơn nên nhân trước và chia sau, để giảm thiểu lỗi làm tròn. Tốt hơn nữa: làm việc với numeric(hoặc một loại dấu phẩy động). Xem bên dưới.

Thay thế

Đây là những gì tôi nghĩ rằng truy vấn của bạn sẽ thực sự trông như thế nào (tính toán một phần tương đối cho mỗi mã thông báo ):

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt            text
               , abs_cnt        bigint
               , relative_share numeric) AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt, t.cnt
        , round((t.cnt * 100) / (sum(t.cnt) OVER ()), 2)  -- AS relative_share
   FROM  (
      SELECT t.txt, count(*) AS cnt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      GROUP  BY t.txt
      ORDER  BY cnt DESC
      LIMIT  _max_tokens
      ) t
   ORDER  BY t.cnt DESC;
END
$func$  LANGUAGE plpgsql;

Biểu thức sum(t.cnt) OVER ()là một hàm cửa sổ . Bạn có thể sử dụng CTE thay vì truy vấn con - khá, nhưng truy vấn con thường rẻ hơn trong những trường hợp đơn giản như thế này.

Một câu lệnh rõ ràngRETURN cuối cùng không bắt buộc (nhưng được phép) khi làm việc với OUTcác tham số hoặc RETURNS TABLE(sử dụng ngầm định các OUTtham số).

round()với hai tham số chỉ hoạt động cho numericcác loại. count()trong truy vấn con tạo ra một bigintkết quả và sum()hơn điều này biginttạo ra một numerickết quả, do đó chúng tôi xử lý một numericsố tự động và mọi thứ chỉ rơi vào vị trí.


Rất cảm ơn câu trả lời của bạn và sửa chữa. Bây giờ đang hoạt động tốt (tôi chỉ thay đổi kiểu tỷ lệ thành số).
Renato Dinhani

@ RenatoDinhaniConceição Tuyệt! Tôi đã thêm một phiên bản có thể trả lời hoặc không trả lời một câu hỏi bổ sung mà bạn chưa thực sự hỏi. ;)
Erwin Brandstetter

Thật tuyệt, điều duy nhất là tôi nghĩ bạn cần một cái RETURN;trước đó END;, ít nhất là tôi đã làm - nhưng tôi đang làm một ĐOÀN KẾT nên tôi không chắc liệu điều đó có làm cho nó khác hay không.
yekta

@yekta: Tôi đã thêm một số thông tin liên quan đến vai trò của RETURN. Đã sửa lỗi không liên quan và thêm một số cải tiến khi đang ở đó.
Erwin Brandstetter

1
Cách thực hiện điều này là gì khi bạn không muốn giới hạn những gì có trong Return TABLE (). BẢNG QUAY LẠI IE (*)?
Nick

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.