Tôi không đồng ý với một số lời khuyên trong các câu trả lời khác. Điều này có thể được thực hiện với PL / pgSQL và tôi nghĩ rằng nó hầu hết vượt trội hơn nhiều để tập hợp các truy vấn trong một ứng dụng khách. Nó nhanh hơn và sạch hơn và ứng dụng chỉ gửi mức tối thiểu qua đường dây trong các yêu cầu. Các câu lệnh SQL được lưu bên trong cơ sở dữ liệu, giúp dễ bảo trì hơn - trừ khi bạn muốn thu thập tất cả logic nghiệp vụ trong ứng dụng khách, điều này phụ thuộc vào kiến trúc chung.
Hàm PL / pgSQL với SQL động
CREATE OR REPLACE FUNCTION func(
_ad_nr int = NULL
, _ad_nr_extra text = NULL
, _ad_info text = NULL
, _ad_postcode text = NULL
, _sname text = NULL
, _pname text = NULL
, _cname text = NULL)
RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
, info text, postcode text, street text, place text
, country text, the_geom geometry)
LANGUAGE plpgsql AS
$func$
BEGIN
-- RAISE NOTICE '%', -- for debugging
RETURN QUERY EXECUTE concat(
$$SELECT a.id, 'address'::text, 1 AS score, a.ad_nr, a.ad_nr_extra
, a.ad_info, a.ad_postcode$$
, CASE WHEN (_sname, _pname, _cname) IS NULL THEN ', NULL::text' ELSE ', s.name' END -- street
, CASE WHEN (_pname, _cname) IS NULL THEN ', NULL::text' ELSE ', p.name' END -- place
, CASE WHEN _cname IS NULL THEN ', NULL::text' ELSE ', c.name' END -- country
, ', a.wkb_geometry'
, concat_ws('
JOIN '
, '
FROM "Addresses" a'
, CASE WHEN NOT (_sname, _pname, _cname) IS NULL THEN '"Streets" s ON s.id = a.street_id' END
, CASE WHEN NOT (_pname, _cname) IS NULL THEN '"Places" p ON p.id = s.place_id' END
, CASE WHEN _cname IS NOT NULL THEN '"Countries" c ON c.id = p.country_id' END
)
, concat_ws('
AND '
, '
WHERE TRUE'
, CASE WHEN $1 IS NOT NULL THEN 'a.ad_nr = $1' END
, CASE WHEN $2 IS NOT NULL THEN 'a.ad_nr_extra = $2' END
, CASE WHEN $3 IS NOT NULL THEN 'a.ad_info = $3' END
, CASE WHEN $4 IS NOT NULL THEN 'a.ad_postcode = $4' END
, CASE WHEN $5 IS NOT NULL THEN 's.name = $5' END
, CASE WHEN $6 IS NOT NULL THEN 'p.name = $6' END
, CASE WHEN $7 IS NOT NULL THEN 'c.name = $7' END
)
)
USING $1, $2, $3, $4, $5, $6, $7;
END
$func$;
Gọi:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Vì tất cả các tham số hàm đều có giá trị mặc định, bạn có thể sử dụng vị trí ký hiệu, được đặt tên ký hiệu hoặc hỗn hợp ký hiệu bạn chọn trong lệnh gọi hàm. Xem:
- Các hàm có số lượng tham số đầu vào thay đổi
Giải thích thêm về những điều cơ bản về SQL động:
- 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
concat()
chức năng là công cụ để xây dựng chuỗi. Nó được giới thiệu với Postgres 9.1.
ELSE
nhánh của CASE
câu lệnh mặc định là NULL
khi không có mặt. Đơn giản hóa mã.
USING
mệnh đề cho EXECUTE
làm cho việc đưa vào SQL không thể thực hiện được vì các giá trị được truyền dưới dạng giá trị và cho phép sử dụng trực tiếp các giá trị tham số, giống như trong các câu lệnh đã chuẩn bị.
NULL
giá trị được sử dụng để bỏ qua các tham số ở đây. Chúng không thực sự được sử dụng để tìm kiếm.
Bạn không cần dấu ngoặc đơn xung quanh SELECT
với RETURN QUERY
.
Hàm SQL đơn giản
Bạn có thể làm điều đó với một hàm SQL thuần túy và tránh SQL động. Đối với một số trường hợp, điều này có thể nhanh hơn. Nhưng tôi sẽ không mong đợi điều đó trong trường hợp này . Lập kế hoạch truy vấn mà không có các phép nối và vị từ không cần thiết thường tạo ra kết quả tốt nhất. Lập kế hoạch chi phí cho một truy vấn đơn giản như thế này hầu như không đáng kể.
CREATE OR REPLACE FUNCTION func_sql(
_ad_nr int = NULL
, _ad_nr_extra text = NULL
, _ad_info text = NULL
, _ad_postcode text = NULL
, _sname text = NULL
, _pname text = NULL
, _cname text = NULL)
RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
, info text, postcode text, street text, place text
, country text, the_geom geometry)
LANGUAGE sql AS
$func$
SELECT a.id, 'address' AS match, 1 AS score, a.ad_nr, a.ad_nr_extra
, a.ad_info, a.ad_postcode
, s.name AS street, p.name AS place
, c.name AS country, a.wkb_geometry
FROM "Addresses" a
LEFT JOIN "Streets" s ON s.id = a.street_id
LEFT JOIN "Places" p ON p.id = s.place_id
LEFT JOIN "Countries" c ON c.id = p.country_id
WHERE ($1 IS NULL OR a.ad_nr = $1)
AND ($2 IS NULL OR a.ad_nr_extra = $2)
AND ($3 IS NULL OR a.ad_info = $3)
AND ($4 IS NULL OR a.ad_postcode = $4)
AND ($5 IS NULL OR s.name = $5)
AND ($6 IS NULL OR p.name = $6)
AND ($7 IS NULL OR c.name = $7)
$func$;
Cuộc gọi giống hệt nhau.
Để bỏ qua các tham số một cách hiệu quả với NULL
giá trị :
($1 IS NULL OR a.ad_nr = $1)
Để thực sự sử dụng giá trị NULL làm tham số , hãy sử dụng cấu trúc này thay thế:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Điều này cũng cho phép lập chỉ mục sẽ được sử dụng.
Đối với trường hợp hiện tại, hãy thay thế tất cả các bản sao của LEFT JOIN
với JOIN
.
db <> fiddle here - với bản trình diễn đơn giản cho tất cả các biến thể.
sqlfiddle cũ
Bên cạnh
-
Không sử dụng
name
vàid
dưới dạng tên cột. Chúng không mang tính mô tả và khi bạn tham gia nhiều bảng (giống như bạn làm vớia lot
trong cơ sở dữ liệu quan hệ), bạn kết thúc với một số cột có tên làname
hoặcid
và phải đính kèm các bí danh để sắp xếp lộn xộn. -
Vui lòng định dạng SQL của bạn đúng cách, ít nhất là khi đặt câu hỏi công khai. Nhưng hãy làm điều đó một cách riêng tư, vì lợi ích của riêng bạn.