Problems with Postgresql CASE syntax - sql

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?

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.

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 ...

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;

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

Related

If output comes in negative value, how to make it null?

Hi I am working on one query, what I want to do is, whenever output comes in negative value,
I want to make it null, following is my query, can anyone help me with this ?
select (to_char((coalesce(14515200/3600000,0) - (coalesce(30512.65/3600,0) - (coalesce(1800/3600,0) + (coalesce(1800/3600,0))))),'FM99,999,999,999'))::character varying as test
If you want a result of NULL for negative values, use
nullif(greatest(/* your expression */, 0), 0)
The only drawback here is that a result of exactly 0 will also become NULL. If you want to avoid that, you could use a user defined function:
CREATE FUNCTION neg_to_null(double precision) RETURNS double precision
LANGUAGE plpgsql AS
'BEGIN; RETURN CASE WHEN $1 < 0.0 THEN NULL ELSE $1; END;';
I use PL/pgSQL to avoid function inlining.
Move your logic to the from clause and use case:
select (case when test_n >= 0 then test_n::character varying end)
from (select . . . as test_n -- your expression as number) x
If your expression actually uses values from a table, you can use a subquery, CTE, or lateral join to define test_n.

Select sub queries within a select

insert into time_test(Difference) values
(select ((select actual from time_test where id = :p13_id) -
(select
(monday+tuesday+wednesday+thursday+friday) from time_test where id=
:p13_id
)) from time_test where id= :p13_id)
Difference is a column in time_test which is null, and :p13_id is a page item for Oracle Apex.
I know I need to wrap it in nvl or some function like that but I don't know how.
It looks like you're actually trying to do an update, not an insert:
update time_test
set difference = actual - (monday+tuesday+wednesday+thursday+friday)
where id = :p13_id
If any of the 'day' columns might be null then you can use nvl() or coalesce() to default them to zero so they don't break the calculation:
update time_test
set difference = actual - coalesce(monday, 0) - coalesce(tuesday, 0)
- coalesce(wednesday, 0) - coalesce(thursday, 0) - coalesce(friday, 0)
where id = :p13_id
You could also do coalesce(actual, 0) but it might make more sense to leave the difference null if that is not set. It depends what you want to see in that case.
In this case the nvl() and coalesce() functions are equivalent. If the first argument - e.g. monday - is null then the second argument is substituted. So nvl(monday, 0) will give you the actual value of monday if it is not null, but will give you zero if it is null. You will get the same effect from coalesce(), but that allows a list of multiple expressions to be evaluated and will return the first non-null value from the list.
Another approach to this is to make difference a virtual column that is calculated on the fly, or calculate it in a view over the table; either would remove the duplicate data storage and the need to maintain the value yourself. And if you did definitely want a physical column you could set it from a trigger so automate the maintenance in case any of the other columns are updated outside your Apex application. But a virtual column is probably simpler and neater.

Avoid division by zero in PostgreSQL

I'd like to perform division in a SELECT clause. When I join some tables and use aggregate function I often have either null or zero values as the dividers. As for now I only come up with this method of avoiding the division by zero and null values.
(CASE(COALESCE(COUNT(column_name),1)) WHEN 0 THEN 1
ELSE (COALESCE(COUNT(column_name),1)) END)
I wonder if there is a better way of doing this?
You can use NULLIF function e.g.
something/NULLIF(column_name,0)
If the value of column_name is 0 - result of entire expression will be NULL
Since count() never returns NULL (unlike other aggregate functions), you only have to catch the 0 case (which is the only problematic case anyway). So, your query simplified:
CASE count(column_name)
WHEN 0 THEN 1
ELSE count(column_name)
END
Or simpler, yet, with NULLIF(), like Yuriy provided.
Quoting the manual about aggregate functions:
It should be noted that except for count, these functions return a
null value when no rows are selected.
I realize this is an old question, but another solution would be to make use of the greatest function:
greatest( count(column_name), 1 ) -- NULL and 0 are valid argument values
Note:
My preference would be to either return a NULL, as in Erwin and Yuriy's answer, or to solve this logically by detecting the value is 0 before the division operation, and returning 0. Otherwise, the data may be misrepresented by using 1.
Another solution avoiding division by zero, replacing to 1
select column + (column = 0)::integer;
If you want the divider to be 1 when the count is zero:
count(column_name) + 1 * (count(column_name) = 0)::integer
The cast from true to integer is 1.

Applying the MIN aggregate function to a BIT field

I want to write the following query:
SELECT ..., MIN(SomeBitField), ...
FROM ...
WHERE ...
GROUP BY ...
The problem is, SQL Server does not like it, when I want to calculate the minimum value of a bit field it returns the error Operand data type bit is invalid for min operator.
I could use the following workaround:
SELECT ..., CAST(MIN(CAST(SomeBitField AS INT)) AS BIT), ...
FROM ...
WHERE ...
GROUP BY ...
But, is there something more elegant? (For example, there might be an aggregate function, that I don't know, and that evaluates the logical and of the bit values in a field.)
One option is MIN(SomeBitField+0). It reads well, with less noise (which I would qualify as elegance).
That said, it's more hack-ish than the CASE option. And I don't know anything about speed/efficiency.
Since there are only two options for BIT, just use a case statement:
SELECT CASE WHEN EXISTS (SELECT 1 FROM ....) THEN 1 ELSE 0 END AS 'MinBit'
FROM ...
WHERE ...
This has the advantage of:
Not forcing a table scan (indexes on BIT fields pretty much never get used)
Short circuiting TWICE (once for EXISTS and again for the CASE)
It is a little more code to write but it shouldn't be terrible. If you have multiple values to check you could always encapsulate your larger result set (with all the JOIN and FILTER criteria) in a CTE at the beginning of the query, then reference that in the CASE statements.
This query is the best solution:
SELECT CASE WHEN MIN(BitField+0) = 1 THEN 'True' ELSE 'False' END AS MyColumn
FROM MyTable
When you add the BitField+0 it would automatically becomes like int
select min(convert(int, somebitfield))
or if you want to keep result as bit
select convert(bit, min(convert(int, somebitfield)))
Try the following
Note: Min represent And aggregate function , Max represent Or aggregate function
SELECT ..., MIN(case when SomeBitField=1 then 1 else 0 end), MIN(SomeBitField+0)...
FROM ...
WHERE ...
GROUP BY ...
same result
This small piece of code has always worked with me like a charm:
CONVERT(BIT, MIN(CONVERT(INT, BitField))) as BitField
AVG(CAST(boolean_column AS FLOAT)) OVER(...) AS BOOLEAN_AGGREGATE
Give a fuzzy boolean :
1 indicate that's all True;
0 indicate that's all false;
a value between ]0..1[ indicate partial matching and can be some percentage of truth.

how can I replace blank value with zero in MS-Acess

I have below query in Ms-Access but I want to replace Blank value with zero but I can't get proper answer. Is there any way to replace blank value in zero.
(SELECT
SUM(IIF(Review.TotalPrincipalPayments,0,Review.TotalPrincipalPayments))+
SUM(IIF(Review.TotalInterestPayments,0,Review.TotalInterestPayments ))
FROM
tblReviewScalars as Review
INNER JOIN tblReportVectors AS Report ON(Review.LoanID=Report.LoanID)
WHERE Report.AP_Indicator="A" AND Report.CashFlowDate=#6/5/2011# AND Review.AsofDate=#6/5/2011# AND ( Review.CreditRating =ReviewMain.CreditRating)) AS [Cash Collected During the Period],
I assume TotalPrincipalPayments and TotalInterestPayments are both numeric types, hence the 'blanks' in question is the NULL value.
In SQL, the set function SUM will disregard NULL values, unless all values resolve to NULL in which case NULL is returned (erroneously and the error is with SQL not Access for a change :)
To use a simple example, SELECT SUM(a) FROM T; will only return NULL when a IS NULL is TRUE for all rows of T or when T is empty. Therefore, you can move the 'replace NULL with zero' logic outside of the SUM() function. Noting that "NULLs propagate" in calculations, you will need to handle NULL for each SUM().
You haven't posted the whole of your query e.g. the source of the correlation name ('table alias') ReviewMain is not showm. But it seems clear you are constructing a derived table named "Cash Collected During the Period", in which case your calculated column needs an AS clause ('column alias') such as TotalPayments e.g.
...
(
SELECT IIF(SUM(Review.TotalPrincipalPayments) IS NULL, 0, SUM(Review.TotalPrincipalPayments))
+ IIF(SUM(Review.TotalInterestPayments) IS NULL, 0, SUM(Review.TotalInterestPayments))
AS TotalPayments
FROM tblReviewScalars as Review
INNER JOIN tblReportVectors AS Report
ON Review.LoanID = Report.LoanID
WHERE Report.AP_Indicator = 'A'
AND Report.CashFlowDate = #2011-05-06#
AND Review.AsofDate = #2011-05-06#
AND Review.CreditRating = ReviewMain.CreditRating
) AS [Cash Collected During the Period], ...
An alternative to #onedaywhen's answer is to use the nz function, which is specifically for null-substitution:
SELECT
SUM(NZ(Review.TotalPrincipalPayments,0))+
SUM(NZ(Review.TotalInterestPayments,0))
...
As onedaywhen pointed out, this is functionally equivalent to putting the function outside the aggregate, which may perform better (the function is called once, rather than once per un-aggregated row):
SELECT
NZ(SUM(Review.TotalPrincipalPayments),0)+
NZ(SUM(Review.TotalInterestPayments),0)
...
To change a null value to a zero in an Access 2010 database, open your table, go to design view, click on the field and set the default value to: =0.