Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace calculated negative values with 0 in PostgreSQL

Tags:

sql

postgresql

I have a table my_table:

case_id    first_created  last_paid       submitted_time
   3456    2021-01-27     2021-01-29      2021-01-26 21:34:36.566023+00:00
   7891    2021-08-02     2021-09-16      2022-10-26 19:49:14.135585+00:00
   1245    2021-09-13     None            2022-10-31 02:03:59.620348+00:00
   9073    None           None            2021-09-12 10:25:30.845687+00:00
   6891    2021-08-03     2021-09-17      None

I created 2 new variables:

select *,
       first_created-coalesce(submitted_time::date) as create_duration,
       last_paid-coalesce(submitted_time::date) as paid_duration 
from my_table;

The output:

  case_id    first_created  last_paid       submitted_time               create_duration paid_duration
   3456    2021-01-27     2021-01-29      2021-01-26 21:34:36.566023+00:00     1                3
   7891    2021-08-02     2021-09-16      2022-10-26 19:49:14.135585+00:00     -450            -405
   1245    2021-09-13     null            2022-10-31 02:03:59.620348+00:00     -412            null
   9073    None           None            2021-09-12 10:25:30.845687+00:00     null           null
   6891    2021-08-03     2021-09-17      null                                 null            null

My question is how can I replace new variables' value with 0, if it is smaller than 0?

The ideal output should look like:

  case_id    first_created  last_paid       submitted_time               create_duration paid_duration
   3456    2021-01-27     2021-01-29      2021-01-26 21:34:36.566023+00:00     1                3
   7891    2021-08-02     2021-09-16      2022-10-26 19:49:14.135585+00:00     0                0
   1245    2021-09-13     null            2022-10-31 02:03:59.620348+00:00     0               null
   9073    None           None            2021-09-12 10:25:30.845687+00:00     null             null
   6891    2021-08-03     2021-09-17      null                                 null             null

My code:

select  *,
        first_created-coalesce(submitted_time::date) as create_duration,
        last_paid-coalesce(submitted_time::date) as paid_duration, 
        case
            when create_duration < 0 THEN 0
            else create_duration
        end as QuantityText
from my_table
like image 706
William Avatar asked Aug 26 '26 20:08

William


1 Answers

greatest(yourvalue,0)

Given yourvalue lower than 0, 0 will be returned as the greater value:

select *
  ,greatest(0,first_created-coalesce(submitted_time::date)) as create_duration
  ,greatest(0,last_paid    -coalesce(submitted_time::date)) as paid_duration
from my_table;

This will also change null values to 0.


case statement

If you wish to keep the null results, you can resort to a regular case statement. In order to alias your calculation you'll have to put it in a subquery or a cte:

select *
  ,case when create_duration<0 then 0 else create_duration end as create_duration_0
  ,case when paid_duration  <0 then 0 else paid_duration   end as paid_duration_0
from (
    select *
      ,first_created-coalesce(submitted_time::date) as create_duration
      ,last_paid    -coalesce(submitted_time::date) as paid_duration
    from my_table ) as subquery;

(n+abs(n))/2

If you sum a number with its absolute value, then divide by two (average them out), you'll get that same number if it was positive, or you'll get zero if it was negative because a negative number will always balance itself out with its absolute value:

  • (-1+abs(-1))/2 = (-1+1)/2 = 0/2 = 0
  • ( 1+abs( 1))/2 = ( 1+1)/2 = 2/2 = 1
select *
  ,(create_duration + abs(create_duration)) / 2 as create_duration_0
  ,(paid_duration   + abs(paid_duration)  ) / 2 as paid_duration_0
from (
    select *
      ,first_created-coalesce(submitted_time::date) as create_duration,
      ,last_paid    -coalesce(submitted_time::date) as paid_duration
    from my_table ) as subquery;

Which according to this demo, is slightly faster than case and about as fast as greatest(), without affecting null values.


Note that select * pulls everything from below, so you'll end up seeing create_duration as well as create_duration_0 - you can get rid of it by listing your desired output columns explicitly in the outer query. You can also rewrite it without subquery/cte, repeating the calculation, which will look ugly but in most cases planner will notice the repetition and evaluate it only once

select *
  ,case when first_created-coalesce(submitted_time::date) < 0
        then 0
        else first_created-coalesce(submitted_time::date)
   end as create_duration
  ,(abs(last_paid-coalesce(submitted_time::date))
    +   last_paid-coalesce(submitted_time::date) )/2 
       as paid_duration
from my_table;

or using a scalar subquery

select *
  ,(select case when a<0 then 0 else a end 
    from (select first_created-coalesce(submitted_time::date)) as alias(a) ) 
    as create_duration
  ,(select case when a<0 then 0 else a end 
    from (select last_paid-coalesce(submitted_time::date)) as alias(a) ) 
    as paid_duration
from my_table;

Neither of which help with anything in this case but are good to know.

like image 132
Zegarek Avatar answered Aug 28 '26 09:08

Zegarek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!