2

Here is my SQL query:


SELECT (CASE (elapsed_time_from_first_login IS NULL) 
                        WHEN true THEN 0 
                        ELSE elapsed_time_from_first_login END) 
FROM (
   SELECT (now()::ABSTIME::INT4 - min(AcctStartTime)::ABSTIME::INT4) 
   FROM radacct
   WHERE UserName = 'test156') AS elapsed_time_from_first_login;

When I execute the above query, I get this error:

ERROR: CASE types record and integer cannot be matched

From the error message I understand that PostgreSQL take the second select, respectively elapsed_time_from_first_login as a row, even if it will always be a single value (because of the min() function).

Question: do you have some suggestions on how to deal with this query?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
artaxerxe
  • 6,281
  • 21
  • 68
  • 106

4 Answers4

3

I suppose, what you are actually trying to do should look like this:

SELECT COALESCE((SELECT now() - min(acct_start_time)
                 FROM   radacct
                 WHERE  user_name = 'test156')
              , interval '0s')

While there is an aggregate function in the top SELECT list of the subselect, it cannot return "no row". The aggregate function min() converts "no row" to NULL, and the simple form below also does the trick.

db<>fiddle here
Oldsqlfiddle

Other problems with your query have already been pointed out. But this is the much simpler solution. It returns an interval rather than an integer.

Convert to integer

Simplified with input from artaxerxe.
Simple form does the job without check for "no row":

SELECT COALESCE(EXTRACT(epoch FROM now() - min(acct_start_time))::int, 0)
FROM   radacct
WHERE  user_name = 'test156';

Details about EXTRACT(epoch FROM INTERVAL) in the manual.

Aggregate functions and NULL

If you had used the aggregate function count() instead of sum() as you had initially, the outcome would be different. count() is a special case among standard aggregate functions in that it never returns NULL. If no value (or row) is found, it returns 0 instead.

The manual on aggregate functions:

It should be noted that except for count, these functions return a null value when no rows are selected. In particular, sum of no rows returns null, not zero as one might expect, and array_agg returns null rather than an empty array when there are no input rows. The coalesce function can be used to substitute zero or an empty array for null when necessary.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • That's the shortest one, but it does not convert time to seconds. I need it as an amount of seconds. Do you have any better idea, assuming that `ABSTIME` is depracated? Thanks – artaxerxe Aug 09 '12 at 07:55
  • @artaxerxe: I assumed that an `interval` would be the more appropriate type to measure time to begin with. I added a variant to get the count of seconds as `integer` instead. – Erwin Brandstetter Aug 09 '12 at 08:05
  • @artaxerxe: I added a bit about aggregate functions and `NULL` that should be of interest in this context. – Erwin Brandstetter Aug 09 '12 at 08:42
2

Postgres is complaining that 0 and elapsed_time_from_first_login are not the same type.

Try this (also simplifying your select):

select
    coalesce(elapsed_time_from_first_login::INT4, 0)
from ...
Bohemian
  • 412,405
  • 93
  • 575
  • 722
1

Here is how I formatted the SQL and now is working:

SELECT coalesce(result, 0) 
     FROM (SELECT (now()::ABSTIME::INT4 - min(AcctStartTime)::ABSTIME::INT4) as result 
                FROM radacct WHERE UserName = 'test156') as elapsed_time_from_first_login;
artaxerxe
  • 6,281
  • 21
  • 68
  • 106
  • 1
    [Manual says](http://www.postgresql.org/docs/9.1/static/datatype-datetime.html): _The types abstime and reltime are lower precision types which are used internally. You are discouraged from using these types in applications; these internal types might disappear in a future release._ – LisMorski Aug 09 '12 at 07:27
1

The second SELECT is returning a table, named elapsed_time_from_first_login with one column and one row. You have to alias that column and use it in the CASE clause. You can't put a whole table (even if it is one column, one row only) where a value is expected.

SELECT (CASE (elapsed_time IS NULL) 
                        WHEN true THEN 0 
                        ELSE elapsed_time end) 
   FROM (SELECT (now()::ABSTIME::INT4 - min(AcctStartTime)::ABSTIME::INT4) 
                AS elapsed_time                -- column alias
         FROM radacct 
         WHERE UserName = 'test156'
        ) as elapsed_time_from_first_login;    -- table alias

and you can shorten the CASE by using the COALESCE() function (and optionally add an alias for that column to be shown in the results):

SELECT COALESCE(elapsed_time, 0) 
         AS elapsed_time 
   FROM (SELECT (now()::ABSTIME::INT4 - min(AcctStartTime)::ABSTIME::INT4) 
                AS elapsed_time            
         FROM radacct 
         WHERE UserName = 'test156'
        ) as elapsed_time_from_first_login;    -- table alias
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
  • `COALESCE` will catch the case where `AcctStartTime IS NULL`, but will fail when there is no row in table `radacct` with UserName = 'test156'. I provided a solution for both cases. Then again, maybe the OP wants it that way. The question does not give any indication, really. – Erwin Brandstetter Aug 09 '12 at 08:22
  • 1
    @ErwinBrandstetter: Actually it catches both situations. – artaxerxe Aug 09 '12 at 08:36
  • 2
    @artaxerxe: Ah you are right! I overlooked the aggregate function, which turns "no row" into `NULL`. If you remove `min()` from the expression, the row goes away. [Quick test case on sqlfiddle to play with.](http://sqlfiddle.com/#!1/918ec/3) – Erwin Brandstetter Aug 09 '12 at 08:52