return more than one row as comma separated values from case when statement - sql

Trying to create a function in Postgresql with 4 arguments that returns a table(multiple rows).
Arguments: in_customer_id, in_start_date, in_end_date, in_risk_flag
The sql query I am using in that function is:
select * from customer as k
where k.customer_id IN (case when $1 = 0 then (select distinct(customer_id) from customer)
when $1 != 0 then $1
end)
and k.start_date >= $2
and k.end_date <= $3
and k.risk_flag IN (case when $4 = 0 then (select distinct(risk_flag) from customer)
when $4 != 0 then $4
end)
Error I am getting is error [21000]: more than one row returned by subquery used as an expression.
Is there any way to get return from case statement as (1,2,3,4,5,6) (comma separated values) instead of a column with multiple rows?

First: distinct is not a function. Writing distinct (customer_id) makes no sense. And in a sub-select that's used for an IN condition the distinct is useless anyway.
It seems that you want to select a specific customer in case you pass a parameter, otherwise you want to select all of them. As far as I can tell, you don't need a sub-select for that. Something like that should do it:
where k.customer_id = case
when $1 <> 0 then $1
else k.customer_id
end
It essentially turns the condition to where customer_id = customer_id when the first parameter is passed as 0 (although you should better use a null value for that, rather then a "magic" value like zero)
This assumes that customer_id is defined as NOT NULL otherwise this will not work.
You can apply the same pattern for risk_id (again: it will only work if risk_id can not contain NULL values).

This logic is usually simplified to avoid the case in the where clause:
where ($1 = 0 or $1 = k.customer_id) and
. . .

Related

Combine CASE and BETWEEN in WHERE clause

I'm trying to execute what I presumed to be a simple enough query, however I'm encountering syntax errors and could use some guidance. Essentially I have a function with a text input parameter $1. This parameter can take on 7 different values controlled by the front end of an application.
Depending on the value of this parameter, I need to return a subset of a larger table between a certain range of one of the integer columns returned. To be slightly clearer, the returning query within the function looks something like:
SELECT val_1, val_2, val_3
FROM schema_name.table_name
WHERE val_3 BETWEEN
CASE WHEN $1 = 'a' THEN 0 AND 1
WHEN $1 = 'b' THEN 2 AND 7
.
.
.
WHEN $1 = 'g' THEN 50 AND 100
END;
However, I'm getting a typically vague error:
ERROR: syntax error at end of input
LINE 42: END;
^
********** Error **********
ERROR: syntax error at end of input
SQL state: 42601
Character: 1133
The ranges set out in the case statement all differ, with no discernible pattern between them. I could of course write an IF block with 7 ELSE statements essentially writing the same select statement 7 times, but I'm guessing the above can't be too far off. I've never had the need to use a CASE statement in a WHERE clause in this way before. Any hints in the right direction would be greatly appreciated.
A CASE expression can only return a single value (boolean in my fixed version), not conditional code like you tried:
SELECT val_1, val_2, val_3
FROM schema_name.table_name
WHERE CASE $1
WHEN 'a' THEN val_3 BETWEEN 0 AND 1
WHEN 'b' THEN val_3 BETWEEN 2 AND 7
.
.
.
WHEN 'g' THEN val_3 BETWEEN 50 AND 100
END;
Using the "simple" form of CASE to make it slightly shorter and faster.
Alternatively, just use boolean logic only:
...
WHERE (
$1 = 'a' AND val_3 BETWEEN 0 AND 1
OR $1 = 'b' AND val_3 BETWEEN 2 AND 7
.
.
.
OR $1 = 'g' AND val_3 BETWEEN 50 AND 100
)
Parentheses are not strictly needed since operator precedence works in our favor anyway. AND binds before OR and BETWEEN binds before both. But you'll need those parentheses if you add another WHERE condition with AND.
Performance?
For both variants, Postgres 12 (didn't test for earlier versions) is smart enough to still use an index on (val_3) or not, depending on actual column statistics. Even for prepared statements.
If that function is called a lot,
and you work with prepared statements (directly or indirectly),
and performance is critical,
and some of your ranges benefit from an index while others don't
then you might still fork (at least) two distinct queries in order to work with generic, saved query plans for either case to get absolute best performance.
A CASE expression returns 1 scalar value and not 2 or an interval of values.
What you can do is use it as a query cross joined to the table:
SELECT t.val_1, t.val_2, t.val_3
FROM schema_name.table_name t
CROSS JOIN (
SELECT CASE $1
WHEN 'a' THEN 0
WHEN 'b' THEN 1
.
END val
) c
WHERE t.val_3 BETWEEN 2 * c.val AND 2 * c.val + 1
Edit.
You can use a CTE which returns all the possible values of $1 and the intervals for each value:
WITH cte(val, min, max) AS (
VALUES ('a', 0, 1), ('b', 2, 7), .........
)
SELECT t.val_1, t.val_2, t.val_3
FROM table_name t
CROSS JOIN (SELECT * FROM cte WHERE val = $1) c
WHERE t.val_3 BETWEEN c.min AND c.max

Case inside 'where' section that changes the condition of an AND statement

I am creating a store procedure and i am wondering how can i add a case block in an Add statement inside the where statement.That case statement checks an input parameter and depending its value it will change the condition from greater that to smaller than and of course be added to the add conditions
So a part of the query is like:
WHERE
AND BM.Example1 IS NOT NULL
AND BM.Example2 IS NOT NULL
AND ( Case When #inputParamter= 'A' THEN AND BM.Example < 0 ELSE And BM.Example> 0 )
ORDER BY 'SEG' ASC, 'CCY' ASC
So by this approach i am thinking to extract an add statement depending on the input parameter but unfortunately i keep getting syntax errors.
Is that possible?
Yepp, just use this:
AND (( #inputParamter= 'A' AND BM.Example < 0) OR ( #inputParamter<>'A' AND BM.Example> 0) )
However, be carefull with NULL, you have to put it in the logic as a third option.
here is a similar answer using case
AND ( Case When #inputParamter = 'A' AND BM.Example < 0 THEN 'Y'
When #inputParamter <> 'A' AND BM.Example > 0 THEN 'Y' ELSE 'N' END = 'Y')

Check if any substring from array appear in string?

I have the following query:
select case when count(*)>0 then true else false end
from tab
where param in ('a','b') and position('T' in listofitem)>0
This checks if 'T' exists in the column listofitem and if it does the count is > 0. Basically it's a search for sub string.
This works well in this private case. However my real case is that I have text[] called sub_array meaning multiple values to check. How can I modify the query to handle the sub_array type? I prefer to have it in a query rather than a function with a LOOP.
What I actualy need is:
select case when count(*)>0 then true else false end
from tab
where param in ('a','b') and position(sub_array in listofitem)>0
This is not working since sub_array is of type Text[]
Use the unnest() function to expand your array & bool_and() (or bool_or() -- this depends on what you want match: all array elements, or at least one) to aggregate:
select count(*) > 0
from tab
where param in ('a','b')
and (select bool_and(position(u in listofitem) > 0)
from unnest(sub_array) u)
A brute force method would be to convert the array to a string:
select (count(*) > 0) as flag
from tab
where param in ('a','b') and
array_to_string(listofitem, '') like '%T%';
I should note that comparing count(*) is not the most efficient way of doing this. I would suggest instead:
select exists (select 1
from tab
where param in ('a','b') and
array_to_string(listofitem, '') like '%T%'
) as flag;
This stops the logic at the first match, rather than counting all matching rows.

Case When Condition in Where Clause. Use filter condition if it matches the case when condition only

Is it possible to use case when condition in where clause to filter select statement.
For Eg:
Select * from table_name
where source ='UHC'
and
to_char(termdate,'YYYYMM') <= '201603';
But i want second filter condition to work only if policy number is '1'. For Eg:
case when policy_number = '1' then to_char(termdate,'YYYYMM') <= '201603';
if the policy number is not 1 then only 1st where clause should work but if policy number is 1 then both the where clause should work.
i hope i made my situation clear.
You don't need case at all:
Select * from table_name
where source ='UHC'
and ((policy_number = '1' and to_char(termdate,'YYYYMM') <= '201603')
or nvl(policy_number, '0') != '1');
With case condition will be like:
where source ='UHC' and case when policy_number = '1' then to_char(termdate,'YYYYMM') else '000000' end <= '201603');
in else you need something that is always less than '201603'. Another problem here is why you're comparing numbers as varchars? Is it really what you need?

check field1 and field2 from single table

In SQL:
I want to check. From single table,
if(field1 = 1 and DATE_SUB(CURDATE(),INTERVAL 7 DAY) <= field2) then
return count(*)
else
return "false"
thanks
v.srinath
SELECT CASE WHEN (field1 = 1 AND DATE_SUB(CURDATE(),INTERVAL 7 DAY) <= field2)
THEN COUNT(*)
ELSE 0
END
FROM SomeTable
CASE
WHEN field1 = 1
AND DATE_SUB(CURDATE(),INTERVAL 7 DAY) <= field2
THEN COUNT(*)
ELSE 'false'
END
Note that this only works in the SELECT list, or HAVING clause, and you should have a GROUP BY clause on field1 and field2. The reason is that COUNT() is an aggregate function. Because field1 and field2 appear outside an aggregatee function, aggregation for those fields must be enforced with a GROUP BY clause. Most RDBMS-es won't allow you to write this unless there is an appropriate GROUP BY clause, but MySQL will, and will silently return rubbish for those fields.
BTW - I think you should probably not write "false" but simply FALSE - right now both legs of the choice have a different type (boolean vs string). The db will probably coerce types, but it is still bad practice. You should ensure that the expression for each brach of the CASE expression have the same data type.