Search code examples
sqlpostgresqlplpgsqlreturn-typeset-returning-functions

Create function with SELECT CASE


I want create a function with 2 parameters. This is my SELECT statement:

SELECT CASE 
   WHEN duration <=  10000000 THEN '00-01 sec'
           WHEN duration <=  40000000 THEN '01-04 sec' 
           WHEN duration <= 100000000 THEN '04-10 sec' 
           WHEN duration <= 300000000 THEN '10-30 sec' 
           WHEN duration <= 600000000 THEN '30-60 sec' 
           ELSE 'more than 60 sec' END AS "Kategorien", 
   COUNT(*) AS Requestcounter
FROM tablename
WHERE starttime BETWEEN '2016-03-01 00:00:00' AND '2016-03-08 14:00:00'
GROUP BY
  (CASE WHEN duration <=  10000000 THEN '00-01 sec'
   WHEN duration <=  40000000 THEN '01-04 sec' 
   ELSE 'more than 60 sec' END); 

Result:

 Kategorien | requestcounter
------------+----------------
 00-01 sec  |           2073
 01-04 sec  |              2
(2 rows)

I want this result from my function. But I get the error:

query has no destination for result data

This is my function:

CREATE OR REPLACE FUNCTION requestcounter(mindate timestamptz,maxdate timestamptz) 
RETURNS SETOF integer AS $$ 

BEGIN
SELECT CASE WHEN duration <=  10000000 THEN '00-01 sec'
            WHEN duration <=  40000000 THEN '01-04 sec'  
            ELSE 'more than 60 sec' END AS "Kategorien", 
COUNT(*) AS Requestcounter from tablename where starttime BETWEEN mindate and maxdate
GROUP BY
  (CASE WHEN duration <=  10000000 THEN '00-01 sec' 
        WHEN duration <=  40000000 THEN '01-04 sec'  
        ELSE 'more than 60 sec' END);
Return; 
END;
$$ LANGUAGE plpgsql;

I know that RETURNS SETOF integer is not right but I have no idea how to get this to work?


Solution

  • @Travis already provided that you need RETURN QUERY to actually return the results of a query from a plpgsql function.

    But you don't need plpgsql for a simple SELECT. Use a plain SQL function instead. You need to define the return type either way, best use RETURNS TABLE like @Travis also provided, but the data types actually returned have to match precisely:

    CREATE OR REPLACE FUNCTION requestcounter(_mindate timestamptz, _maxdate timestamptz) 
      RETURNS TABLE (kategorien text, requestcounter int) AS
    $func$ 
    SELECT CASE WHEN duration <= 10000000 THEN '00-01 sec'::text
                WHEN duration <= 40000000 THEN '01-04 sec'
                -- more cases ...
                ELSE 'more than 60 sec' END  -- AS kategorien -- not visible outside function
         , count(*)::int                     -- AS requestcounter
    FROM   tablename
    WHERE  starttime BETWEEN _mindate AND _maxdate
    GROUP  BY 1                              -- simpler with positional reference
    ORDER  BY 1                              -- see below   
    $func$  LANGUAGE sql;                    -- never quote the language name
    
    • count() returns bigint. If you define the returned column requestcounter as int, you have to cast: count(*)::int (Or return bigint to begin with.)

    • In an SQL function you can use a plain SELECT to return rows.

    • Avoid naming conflicts between parameters and column name. A common convention is to prefix parameters like I did.

    • A positional reference in GROUP BY is a very convenient syntax shortcut for long expressions in the SELECT list. This also makes sure that GROUP BY items match SELECT items (which is not the case in your first query).

    • You probably want to add ORDER BY 1 or you get an arbitrary order. Your demonstrated text values happen to sort in the right order.

    Call:

    SELECT * FROM requestcounter('2015-01-01 00:00+01', '2015-03-01 00:00+01')