Postgresql - 使用函数动态创建多级排序

问题描述 投票:0回答:1

我正在创建一个将构建和执行动态sql语句的函数。基本上它正在构建ORDER BY子句,它可以根据用户传入的VARCHAR数组对多个级别进行深度ORDER。

例如,用户的输入将是一个数组:

{{orderColumn,orderDirection},{orderColumn,orderDirection},...}

显然,我理解声明中的问题所在,因为postgres相当清楚地将其打印出来。问题是在这种特殊情况下我无法弄清楚正确的语法。也许我做错了,而且我有更好的选择,我不会采用这种方法。如果我能在没有动态sql的情况下做到这一点,那就更好了。帮助或见解将不胜感激。

作为参考我使用的是PostgreSQL 9.6.9

我的代码到目前为止......

CREATE OR REPLACE FUNCTION profile.sp_rider_search(
    _limit integer,
    _offset integer,
    _term text,
    _orderBy VARCHAR[],
    _registration_date timestamp with time zone DEFAULT 
    to_timestamp((0)::double precision))
    RETURNS TABLE(customer_id integer
              , first_name character varying
              , last_name character varying
              , dob date
              , cell_phone character varying
              , email character varying
              , registration_date timestamp with time zone
              , full_count integer) 

AS $func$
DECLARE
sql TEXT := 'SELECT pp.user_id AS customer_id,
    pp.first_name,
    pp.last_name,        
    pp.date_of_birth AS dob,
    pp.cell_phone,
    aau.email,
    pp.created AS registration_date,
    count(*) OVER()::integer AS full_count
   FROM profile.profile pp
     LEFT JOIN authsvc.all_users aau ON aau.id = pp.user_id         
   WHERE pp.created > $5 AND (      
      pp.last_name ILIKE % || $3 || %
      OR pp.first_name ILIKE % || $3 || %
      OR pp.cell_phone ILIKE % || $3 || %
      OR CAST(pp.user_id AS TEXT) ILIKE % || $3 || %
      OR aau.email ILIKE % || $3 || %
      OR CONCAT(pp.first_name,'' '',pp.last_name) ILIKE % || $3 || %
      )
      ORDER BY';
_item TEXT;

BEGIN    
   FOREACH _item IN ARRAY $4
   LOOP
        CASE WHEN _item[2] = 1 THEN
            CASE WHEN _item[1] = 'last_name' THEN 
                    sql :=  sql || ' pp.last_name, ';
                 WHEN _item[1] = 'first_name' THEN 
                    sql := sql || ' pp.first_name, ';
                 WHEN _item[1] = 'email' THEN 
                    sql := sql || ' aau.email, ';
                 WHEN _item[1] = 'phone' THEN 
                    sql := sql || ' pp.cell_phone, ';
                 WHEN _item[1] = 'customer_id' THEN 
                    sql := sql || ' pp.user_id::TEXT, ';
                 WHEN _item[1] = 'registration_date' THEN 
                    sql := sql || ' pp.created::TEXT, ';
            END
        END
        CASE WHEN _item[2] = -1 THEN
            CASE WHEN _item[1] = 'last_name' THEN 
                    sql :=  sql || ' pp.last_name DESC, ';
                 WHEN _item[1] = 'first_name' THEN 
                    sql := sql || ' pp.first_name DESC, ';
                 WHEN _item[1] = 'email' THEN 
                    sql := sql || ' aau.email DESC, ';
                 WHEN _item[1] = 'phone' THEN 
                    sql := sql || ' pp.cell_phone DESC, ';
                 WHEN _item[1] = 'customer_id' THEN 
                    sql := sql || ' pp.user_id::TEXT DESC, ';
                 WHEN _item[1] = 'registration_date' THEN 
                    sql := sql || ' pp.created::TEXT DESC, ';
            END
         END 
END LOOP;

sql :=  sql || 'LIMIT  $1
                OFFSET $2;' 
RETURN QUERY EXECUTE sql
USING _limit,
    _offset,
    _term,
    _orderBy,
    _registration_date;

END; 
$func$ LANGUAGE plpgsql;

在执行此语句时,我收到错误:

ERROR:  syntax error at or near "END" 
LINE 57:             END                      
                     ^ 
SQL state: 42601 
Character: 998
sql postgresql plpgsql
1个回答
0
投票

我的陈述中有一些语法错误,我终于解决了,希望这会有助于其他人。我需要添加END CASE;对于每个case语句,我还必须在OFFSET $ 2之后添加一个分号;以及从语句中删除一个尾随逗号。

最终工作代码 - >

CREATE OR REPLACE FUNCTION profile.sp_rider_search(
    _limit integer,
    _offset integer,
    _term text,
    _orderBy VARCHAR[],
    _registration_date timestamp with time zone DEFAULT 
    to_timestamp((0)::double precision),
    _preferred_column TEXT DEFAULT '')
    RETURNS TABLE(customer_id integer
              , first_name character varying
              , last_name character varying
              , dob date
              , cell_phone character varying
              , email character varying
              , registration_date timestamp with time zone
              , full_count integer) 

AS $func$
DECLARE
sql TEXT := 'SELECT pp.user_id AS customer_id,
    pp.first_name,
    pp.last_name,        
    pp.date_of_birth AS dob,
    pp.cell_phone,
    aau.email,
    pp.created AS registration_date,
    count(*) OVER()::integer AS full_count
   FROM profile.profile pp
     LEFT JOIN authsvc.all_users aau ON aau.id = pp.user_id         
   WHERE pp.created > $5 AND (      
      pp.last_name ILIKE % || $3 || %
      OR pp.first_name ILIKE % || $3 || %
      OR pp.cell_phone ILIKE % || $3 || %
      OR CAST(pp.user_id AS TEXT) ILIKE % || $3 || %
      OR aau.email ILIKE % || $3 || %
      OR CONCAT(pp.first_name,'' '',pp.last_name) ILIKE % || $3 || %
      )
      ORDER BY';
_item TEXT;

BEGIN    
   FOREACH _item IN ARRAY $4
   LOOP
        CASE WHEN _item[2] = 1 THEN
            CASE WHEN _item[1] = 'last_name' THEN 
                    sql :=  sql || ' pp.last_name, ';
                 WHEN _item[1] = 'first_name' THEN 
                    sql := sql || 'pp.first_name, ';
                 WHEN _item[1] = 'email' THEN 
                    sql := sql || 'aau.email, ';
                 WHEN _item[1] = 'phone' THEN 
                    sql := sql || 'pp.cell_phone, ';
                 WHEN _item[1] = 'customer_id' THEN 
                    sql := sql || 'pp.user_id::TEXT, ';
                 WHEN _item[1] = 'registration_date' THEN 
                    sql := sql || 'pp.created::TEXT, ';
            END CASE;          


       WHEN _item[2] = -1 THEN
          CASE WHEN _item[1] = 'last_name' THEN 
                  sql :=  sql || ' pp.last_name DESC, ';
               WHEN _item[1] = 'first_name' THEN 
                  sql := sql || 'pp.first_name DESC, ';
               WHEN _item[1] = 'email' THEN 
                  sql := sql || 'aau.email DESC, ';
               WHEN _item[1] = 'phone' THEN 
                  sql := sql || 'pp.cell_phone DESC, ';
               WHEN _item[1] = 'customer_id' THEN 
                  sql := sql || 'pp.user_id::TEXT DESC, ';
               WHEN _item[1] = 'registration_date' THEN 
                  sql := sql || 'pp.created::TEXT DESC, ';
          END CASE;
        END CASE;
END LOOP;

sql := TRIM(TRAILING ',' FROM sql);

sql :=  sql || 'LIMIT  $1
                OFFSET $2;'; 

RETURN QUERY EXECUTE sql
USING _limit,
    _offset,
    _term,
    _orderBy,
    _registration_date;

END; 
$func$ LANGUAGE plpgsql;
© www.soinside.com 2019 - 2024. All rights reserved.