简体   繁体   中英

SQL greater than IN

I'm using Postgres 9.4 and want to do something like this:

movement_id|counter|standardized_output
---------------------------------------
1          |      3|                 10
1          |      3|                 12
1          |      5|                 10
2          |      4|                  5

I have the following query:

SELECT movement_id, counter, MAX(standardized_output) AS standardized_output 
FROM "outputs" 
WHERE "outputs"."user_id" = 1 AND "outputs"."movement_id" IN (1,2) AND (counter in (1,3,5)) 
GROUP BY movement_id, counter

Which gives me:

movement_id|counter|standardized_output
---------------------------------------
1          |      3|                 12
1          |      5|                 10

But what I want to find is what the MAX(standardized_output) is for counter >= (1,3,5) . So the following result:

movement_id|counter|standardized_output
---------------------------------------
1          |      1|                 12 (MAX value where movement_id is 1 and counter is >=1)
1          |      3|                 12 (MAX value where movement_id is 1 and counter is >=3)
1          |      5|                 10 (MAX value where movement_id is 1 and counter is >=5)
2          |      1|                  5 (MAX value where movement_id is 2 and counter is >=1)
2          |      3|                  5 (MAX value where movement_id is 2 and counter is >=3)
2          |      5|               null (MAX value where movement_id is 2 and counter is >=5)

(small edit: movement_id is IN, not =)

You could probably put a CASE statement on the counter field?

CASE WHEN counter >= 5 THEN 5
WHEN counter >=3 THEN 3
WHEN counter >=1 THEN 1
ELSE 0 END

Then add it to the select and the group by clause.

SELECT movement_id,
    (CASE WHEN counter >= 5 THEN 5
      WHEN counter >=3 THEN 3
      WHEN counter >=1 THEN 1
      ELSE 0 END) as new_counter,
    MAX(standardized_output) AS standardized_output 
FROM "outputs" 
WHERE "outputs"."user_id" = 1 AND "outputs"."movement_id" = 1
GROUP BY movement_id,
    (CASE WHEN counter >= 5 THEN 5
      WHEN counter >=3 THEN 3
      WHEN counter >=1 THEN 1
      ELSE 0 END)

I was about to post something in the lines to what Kez did (making use of the case statement), but a bit different.

I am not sure of which conditions you need in the case here, I don't know if counter >= (1, 3, 5) could be translated to counter >= 5 ? In the sample below I am considering that counter >= (1, 3, 5) means that counter >= 5 (question is how to understand the semicolons here - if they are to be translated into ANDs or ORs - I went with ANDs, so counter >= (1,3,5) is the same as counter >= 5, but if the semicolons are to be understood as ORs it means counter values of 2 or 4 are of your interest)

In any case, for whatever conditions you do NOT want to consider your standardized output in the max function, include them in the case condition and make the counter for them return zero, so it does not affect your max (or a negative number, if needed)

SELECT 
       movement_id,
       counter,
       MAX(CASE WHEN COUNTER < 1 THEN 0 ELSE COUNTER END ) AS standardized_output
  FROM outputs
 WHERE
   outputs.user_id = 1
   AND outputs.movement_id = 1
   AND counter in (1, 3, 5)
 GROUP BY movement_id, counter

You can use a CASE statement with MAX() function like

SELECT movement_id, 
counter, 
MAX(CASE WHEN movement_id = 1 and counter >= 1 THEN standardized_output
WHEN movement_id = 1 and counter >= 3 THEN standardized_output
WHEN movement_id = 1 and counter >= 5 THEN standardized_output
WHEN movement_id = 2 and counter >= 1 THEN standardized_output
WHEN movement_id = 2 and counter >= 3 THEN standardized_output
WHEN movement_id = 2 and counter >= 5 THEN standardized_output
ELSE NULL END) AS standardized_output 
FROM "outputs" 
WHERE "outputs"."user_id" = 1 
AND "outputs"."movement_id" = 1 
AND (counter in (1,3,5)) 
GROUP BY movement_id, counter;

Unless I'm completely misunderstanding what you're trying to do, you should be able to handle this with a join:

SELECT o.movement_id,
    o.counter_id,
    MAX(oagg.standardized_output) AS standardized_output
FROM outputs o
LEFT JOIN outputs oagg
    ON  o.user_id       =  oagg.user_id
    AND o.other_primary =  oagg.other_primary
    AND o.movement_id   =  oagg.movement_id
    AND o.counter_id    <= oagg.counter_id
WHERE o.user_id = 1;

If you're explicitly wanting counter_id s that don't exist, you'll need to create them somehow. I'd probably join with a tally table, but I'm not sure I fully understand what you're looking for.

As you want results for rows that don't have any values you first need to create a set consisting of the rows that should be there, in this case the cartesian product of {movement_id} X {1,3,5} . To do this we can use a cross join and the table value constructor, and then it's just a case of using a left join and a subquery to get the max values.

I'm sure this query can be improved, but it should work.

select 
    all_values.movement_id, 
    all_values.num, 
    (   
       select max(standardized_output) 
       from outputs 
       where counter >= all_values.num 
         and movement_id = all_values.movement_id
    ) as standardized_output 
from (
  select movement_id, t.num
  from outputs
  cross join (values (1), (3), (5)) as t(num) 
  where "movement_id" in (1 ,2)
-- and "outputs"."user_id" = 1 --this was missing in your sample so I left it commented out.
) all_values
left join outputs o on all_values.movement_id = o.movement_id
                   and (counter in (all_values.num)) 
group by all_values.movement_id, all_values.num
order by all_values.movement_id, all_values.num;

Sample SQL Fiddle

Given your sample data the result from the query above is:

| movement_id | num | standardized_output |
|-------------|-----|---------------------|
|           1 |   1 |                  12 |
|           1 |   3 |                  12 |
|           1 |   5 |                  10 |
|           2 |   1 |                   5 |
|           2 |   3 |                   5 |
|           2 |   5 |              (null) |

Edit: the same result can be achieved using this query:

select  
  o1.movement_id, 
  t.num as counter, 
  max(o2.standardized_output) as standardized_output
from outputs o1 cross join (values (1), (3), (5)) as t(num) 
left join outputs o2 on o1.movement_id = o2.movement_id and t.num <= o2.counter
where o1.movement_id in (1,2) 
group by o1.movement_id, t.num
order by o1.movement_id, t.num;

Sample fiddle

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM