3

In PostgreSQL I am trying to build a where clause in a function that uses an inbound parameter to determine the contents of the IN

For example:

select fld1, count(fld1)
from xyz
where fld1 in (
 case 1ドル
 when 1 then 'Value1'
 when 2 then 'Value2'
 when 3 then '''Value1'',''Value2'''
 when 4 then '''Value4'',''Value5'',''Value6'''
 else NULL
)
group by fld1

Value 1 works fine as does Value2, Values 3 and 4 fail not syntactically but no rows are returned.

Evan Carroll
65.7k50 gold badges259 silver badges510 bronze badges
asked Nov 16, 2016 at 19:12
0

4 Answers 4

3

Your suggestion is bad practice

From the code given it looks like you want to

  • supply a variable from your application 1ドル
  • depending on that supplied variable make sure column fld1 is set to a specific set of values.
  • I like the suggestion of using simple OR however, if your condition is complex and you want to maintain it that can get to be very messy. Here is another way.

Here was your code,

select fld1, count(fld1)
from xyz
where fld1 in (
 case 1ドル
 when 1 then 'Value1'
 when 2 then 'Value2'
 when 3 then '''Value1'',''Value2'''
 when 4 then '''Value4'',''Value5'',''Value6'''
 else NULL
)
group by fld1

However, I would unroll this as a JOIN, and later spring it off into another table. Really CASE should likely never be used in a WHERE clause.

select fld1, count(fld1)
from xyz
join ( VALUES
 (1, ARRAY['Value1'])
 , (2, ARRAY['Value2'])
 , (3, ARRAY['Value1','Value2'])
 , (4, ARRAY['Value4','Value5','Value6'])
) AS cond(code,values)
 ON ( code = 1ドル AND fld1 = any(cond.values) )
group by fld1

From there you can even use CTE's which may make it faster, and it'll look more maintainable.

WITH cond(code,values) AS (VALUES
 (1, ARRAY['Value1'])
 , (2, ARRAY['Value2'])
 , (3, ARRAY['Value1','Value2'])
 , (4, ARRAY['Value4','Value5','Value6'])
)
select fld1, count(fld1)
from xyz
join cond ON ( code = 1ドル AND fld1 = any(cond.values) )
group by fld1

From the CTE, you can just as well make cond(fld1,values) its own table if it gets too bloody.

answered Nov 17, 2016 at 3:30
1
  • 2
    I slightly disagree on the "bad practice". His intention is bad practice. His code is just bad code because it doesn't do what he was hoping to do. ("Values 3 and 4 fail not syntactically but no rows are returned.") Commented Nov 18, 2016 at 9:35
5

The output of a CASE expression has to be a single value not a list of values.

The statement did not give a syntax error for cases 3 and 4 because '''Value1'',''Value2''' is evaluated as a single string (which includes 4 quote characters and a comma): 'Value1','Value2'

You could rewrite your condition using a CASE expression that evaluates to boolean, like this:

select fld1, count(*)
from xyz
where case 1ドル
 when 1 then fld1 in ('Value1')
 when 2 then fld1 in ('Value2')
 when 3 then fld1 in ('Value1', 'Value2')
 when 4 then fld1 in ('Value4', 'Value5', 'Value6')
 end
group by fld1 ;

but I'd prefer to write it more more simply using OR:

select fld1, count(*)
from xyz
where ( 1ドル = 1 and fld1 in ('Value1')
 or 1ドル = 2 and fld1 in ('Value2')
 or 1ドル = 3 and fld1 in ('Value1', 'Value2')
 or 1ドル = 4 and fld1 in ('Value4', 'Value5', 'Value6')
 )
group by fld1 ;
answered Nov 16, 2016 at 19:21
0
4

Using arrays:

select fld1, count(fld1)
from xyz
where fld1 = any(
 case 1ドル
 when 1 then array('Value1')
 when 2 then array('Value2')
 when 3 then array('Value1','Value2')
 when 4 then array('Value4','Value5','Value6')
 else NULL -- actually can be skipped as mentioned by ypercubeTM
 end 
)
group by fld1;

if you execute explain verbose select 1 in (1,2,3); then you will see that the in operator translated to ... = any(... internally:

╔════════════════════════════════════════════╗
║ QUERY PLAN ║
╠════════════════════════════════════════════╣
║ Result (cost=0.00..0.01 rows=1 width=0) ║
║ Output: (1 = ANY ('{1,2,3}'::integer[])) ║
╚════════════════════════════════════════════╝
Paul White
95.4k30 gold badges440 silver badges689 bronze badges
answered Nov 16, 2016 at 22:23
0
4

Since implementing the filter with the help of a virtual table is an option for you, you could also consider the classic one-to-many form instead of arrays:

values
 (1, 'Value1'),
 (2, 'Value2'),
 (3, 'Value1'),
 (3, 'Value2'),
 (4, 'Value4'),
 (4, 'Value5'),
 (4, 'Value6')

You can use it in a number of ways:

  • join it conventionally:

    select
     xyz.fld1,
     count(xyz.fld1)
    from
     xyz
     inner join
     (
     values
     (1, 'Value1'),
     (2, 'Value2'),
     (3, 'Value1'),
     (3, 'Value2'),
     (4, 'Value4'),
     (4, 'Value5'),
     (4, 'Value6')
     ) as fltr (paramValue, fld1Value)
     on (1,ドル xyz.fld1) = (fltr.paramValue, fltr.fld1Value)
    ;
    
  • use it as the right side of an IN predicate:

    select
     xyz.fld1,
     count(xyz.fld1)
    from
     xyz
    where
     (1,ドル xyz.fld1) in
     (
     values
     (1, 'Value1'),
     (2, 'Value2'),
     (3, 'Value1'),
     (3, 'Value2'),
     (4, 'Value4'),
     (4, 'Value5'),
     (4, 'Value6')
     )
    ;
    
  • intersect it with each source row, checking the matches in an EXISTS predicate:

    select
     xyz.fld1,
     count(xyz.fld1)
    from
     xyz
    where
     exists
     (
     values
     (1,ドル xyz.fld1)
     intersect
     values
     (1, 'Value1'),
     (2, 'Value2'),
     (3, 'Value1'),
     (3, 'Value2'),
     (4, 'Value4'),
     (4, 'Value5'),
     (4, 'Value6')
     )
    ;
    
answered Nov 17, 2016 at 23:38
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.