0

I am trying to call/convert a numeric variable into string inside a user-defined function. I was thinking about using to_char, but it didn't pass.

My function is like this:

create or replace function ntile_loop(x numeric)
returns setof numeric as 
$$
  select

  max("billed") as _____(to_char($1,'99')||"%"???) from 
      (select "billed", "id","cm",ntile(100) 
      over (partition by "id","cm" order by "billed")
      as "percentile" from "table_all") where "percentile"=$1
      group by "id","cm","percentile";
$$
language sql;

My purpose is to define a new variable "x%" as its name, with x varying as the function input. In context, x is numeric and will be called again later in the function as a numeric (this part of code wasn't included in the sample above).

What I want to return:

I simply want to return a block of code so that every time I change the percentile number, I don't have to run this block of code again and again. I'd like to calculate 5, 10, 20, 30, ....90th percentile and display all of them in the same table for each id+cm group.

That's why I was thinking about macro or function, but didn't find any solutions I like.

Thank you for your answers. Yes, I will definitely read basics while I am learning. Today's my second day to use SQL, but have to generate some results immediately.

kuki
  • 303
  • 2
  • 6
  • 15
  • Post the full function... – Denis de Bernardy Jun 06 '13 at 20:38
  • 1
    `I also know that returns setof numeric is not correct, but have no idea what else I could use.` We also have no idea what you want to return exactly. How about a sample output? And `it didn't pass` is not a valid Postgres error message. What did you really get? – Erwin Brandstetter Jun 06 '13 at 23:21
  • 1
    What are the `_____` and `???` doing there? Are they actually part of your code, or are you trying to tell us something about it? – IMSoP Jun 06 '13 at 23:42
  • 1
    Also relevant: your version of PostgreSQL ... – Erwin Brandstetter Jun 06 '13 at 23:53
  • Hi, ___is just to "fill the blank". I am using the newest version of PostgreSQL. – kuki Jun 07 '13 at 02:07
  • 1
    The first thing to learn about SQL is that "thinking about a macro or function" should be your last resort. Your first approach should always be to build up a single query which gives the results you need. The DBMS is likely much better than you are at working out how to loop over or otherwise interrogate the data. – IMSoP Jun 07 '13 at 14:42

2 Answers2

2

Converting numeric to text is the least of your problems.

My purpose is to define a new variable "x%" as its name, with x varying as the function input.

  • First of all: there are no variables in an SQL function. SQL functions are just wrappers for valid SQL statements. Input and output parameters can be named, but names are static, not dynamic.

  • You may be thinking of a PL/pgSQL function, where you have procedural elements including variables. Parameter names are still static, though. There are no dynamic variable names in plpgsql. You can execute dynamic SQL with EXECUTE but that's something different entirely.

  • While it is possible to declare a static variable with a name like "123%" it is really exceptionally uncommon to do so. Maybe for deliberately obfuscating code? Other than that: Don't. Use proper, simple, legal, lower case variable names without the need to double-quote and without the potential to do something unexpected after a typo.

  • Since the window function ntile() returns integer and you run an equality check on the result, the input parameter should be integer, not numeric.

  • To assign a variable in plpgsql you can use the assignment operator := for a single variable or SELECT INTO for any number of variables. Either way, you want the query to return a single row or you have to loop.

  • If you want the maximum billed from the chosen percentile, you don't GROUP BY x, y. That might return multiple rows and does not do what you seem to want. Use plain max(billed) without GROUP BY to get a single row.

  • You don't need to double quote perfectly legal column names.

A valid function might look like this. It's not exactly what you were trying to do, which cannot be done. But it may get you closer to what you actually need.

CREATE OR REPLACE FUNCTION ntile_loop(x integer)
RETURNS SETOF numeric as 
$func$
DECLARE
   myvar text;
BEGIN

SELECT INTO myvar  max(billed)
FROM  (
   SELECT billed, id, cm
         ,ntile(100) OVER (PARTITION BY id, cm ORDER BY billed) AS tile
   FROM   table_all
   ) sub
WHERE  sub.tile = $1;

-- do something with myvar, depending on the value of $1 ...
END
$func$ LANGUAGE plpgsql;

Long story short, you need to study the basics before you try to create sophisticated functions.

Plain SQL

After Q update:

I'd like to calculate 5, 10, 20, 30, ....90th percentile and display all of them in the same table for each id+cm group.

This simple query should do it all:

SELECT id, cm, tile, max(billed) AS max_billed
FROM  (
   SELECT billed, id, cm
         ,ntile(100) OVER (PARTITION BY id, cm ORDER BY billed) AS tile
   FROM   table_all
   ) sub
WHERE (tile%10 = 0 OR tile = 5)
AND    tile <= 90
GROUP  BY 1,2,3
ORDER  BY 1,2,3;

% .. modulo operator
GROUP BY 1,2,3 .. positional parameter

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • 1
    This is everything I was drafting in my answer, mostly said better, including a much better attempt to understand the weird nested `GROUP BY` than I'd managed to get my head around. – IMSoP Jun 06 '13 at 23:59
  • @IMSoP: the code in the Q certainly has the potential to get your head spinning ... – Erwin Brandstetter Jun 07 '13 at 00:00
  • 1
    One thing that I did try to mention in my answer (which I've deleted): when using a procedural language to construct your SQL (e.g. PL/pgSQL using `EXECUTE`, or PHP / Ruby / whatever outside the DB) you can, theoretically, create your column names dynamically based on some previous input. However, dynamic variable/field names often turn out to introduce far more problems than what they were mistakenly introduced as a solution for. – IMSoP Jun 07 '13 at 00:03
  • 1
    @IMSoP: I could not agree more. While one *can* create a function dynamically just to get a certain variable name, chances are, there is a much simpler solution around the corner. One would need to know plpgsql very well to jump through those burning loops. But people who know it well enough probably know a simpler solution. – Erwin Brandstetter Jun 07 '13 at 00:10
  • Thank you all. I added more descriptions for my purpose and problems. If you have simpler solutions, please give me a hint so that I at least know what I should read about. – kuki Jun 07 '13 at 02:39
  • 1
    @JohnSmith: Hints? You mean, like the many links to the excellent manual in my answer? I also added a plain SQL query that should fit your Q update. No jumping through burning loops, but still pretty advanced stuff for your `second day to use SQL`. – Erwin Brandstetter Jun 07 '13 at 03:14
  • Thanks! Yes, the manuals are helpful but may take me enormous time to research and read. Your answer is even more helpful! – kuki Jun 07 '13 at 20:30
  • 1
    Also I think the last line in your code should be `order by 1,2,3`. – kuki Jun 07 '13 at 21:01
  • @JohnSmith: Exactly. Fixed it. – Erwin Brandstetter Jun 07 '13 at 21:22
  • @ErwinBrandstetter I am thinking, is it possible to also get the min(billed) directly from this code? I tried two ways: 'min(billed),'in the first line, or have tile=0, but neither works. – kuki Jun 07 '13 at 22:55
  • @JohnSmith: I suppose you start a new question with a description, what you tried and how it failed. Refer to this one for context. `min(billed)` should work. Not sure, why it fails. – Erwin Brandstetter Jun 07 '13 at 22:57
0

It looks like you're looking for return query execute, returning the result from a dynamic SQL statement:

http://www.postgresql.org/docs/current/static/plpgsql-control-structures.html

http://www.postgresql.org/docs/current/static/plpgsql-statements.html

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154