0

I am trying to create a single query to retrieve the current price and special sale price if a sale is running; When there isn't a sale on I want store_picture_monthly_price_special.price AS special_price to return as null.

Before adding the 2nd WHERE condition the query executes as I expect it to: store_picture_monthly_price_special.price returns null since there is no sale running at present.

 store_picture_monthly_reference | tenure |   name    | regular_price | special_price 
                               3 |     12 | 12 Months |        299.99 | {Null}             
                               2 |      3 | 3 Months  |         79.99 | {Null}            
                               1 |      1 | 1 Month   |         29.99 | {Null}            

pgSQL is treating the 2nd WHERE condition as "all or none". If there is no sale running there are no results.

Is it possible to tweak this query so I get regular pricing each and every time and special sale price either as a dollar value when a special is running or returning null Is what I am trying to do be accomplished require sub-query?

This is the query how I presently have it:

SELECT store_picture_monthly.reference AS store_picture_monthly_reference , store_picture_monthly.tenure , store_picture_monthly.name , store_picture_monthly_price_regular.price AS regular_price , store_picture_monthly_price_special.price AS special_price

FROM ( store_picture_monthly INNER JOIN store_picture_monthly_price_regular ON store_picture_monthly_price_regular.store_picture_monthly_reference = store_picture_monthly.reference )

FULL OUTER JOIN store_picture_monthly_price_special ON store_picture_monthly.reference = store_picture_monthly_price_special.store_picture_monthly_reference

WHERE 
    ( store_picture_monthly_price_regular.effective_date < NOW() ) 
    AND
    ( NOW() BETWEEN store_picture_monthly_price_special.begin_date AND store_picture_monthly_price_special.end_date )

GROUP BY store_picture_monthly.reference , store_picture_monthly_price_regular.price , store_picture_monthly_price_regular.effective_date , store_picture_monthly_price_special.price

ORDER BY store_picture_monthly_price_regular.effective_date DESC

Table "store_picture_monthly"

reference bigint,
name text,
description text,
tenure bigint,
available_date timestamp with time zone,
available_membership_reference bigint

Table store_picture_monthly_price_regular

reference bigint ,
store_picture_monthly_reference bigint,
effective_date timestamp with time zone,
price numeric(10,2),
membership_reference bigint

Table store_picture_monthly_price_special

reference bigint,
store_picture_monthly_reference bigint,
begin_date timestamp with time zone,
end_date timestamp with time zone,
price numeric(10,2),
created_date timestamp with time zone DEFAULT now(),
membership_reference bigint
Ron Piggott
  • 705
  • 1
  • 8
  • 26
  • 2
    Please find & use table aliases. Learn what left join on returns: inner join on rows plus unmatched left table rows extended by nulls. Always know what inner join you want as part of a left join. An inner join on or where that requires a right table column to be not null after a left join on removes any rows extended by nulls, ie leaves only inner join on rows, ie "turns outer join into inner join". You have that. – philipxy Mar 16 '19 at 06:17
  • This is a faq. Before considering posting please always google many clear, concise & precise phrasings of your question/problem/goal and/or error message, with & without your particular strings/names, & read many answers. If you post a question, use one phrasing as title. Please in code questions give a [mcve]--cut & paste & runnable code plus desired output plus clear specification & explanation. Minimal means adding minimal problem code to minimal working code. So give minimal code that you show does what you expect & minimal code with the first place you go wrong. – philipxy Mar 16 '19 at 06:17
  • Possible duplicate of [Left Outer Join doesn't return all rows from my left table?](https://stackoverflow.com/questions/4707673/left-outer-join-doesnt-return-all-rows-from-my-left-table) – philipxy Mar 16 '19 at 06:29
  • 1
    I second PhilipXY's comment of Alison your tables.. your table names are loooong and writing them 100 times in every sql means that most of that wall of monospace up there is just table names, very hard to read – Caius Jard Mar 16 '19 at 06:36
  • 1
    I haven't really looked in depth, but why do you want a FULL outer? If I had two tables of normal and special prices my query would basically look like this: `SELECT COALESCE(sp.price, no.price) FROM normalprices no LEFT JOIN specialprices sp ON no.prodid=sp.prodid AND NOW() BETWEEN sp.promostart AND sp.promoend` - this joins the two tables on product id and only brings special prices if the promo period is in force, then coalesce gives "get special price or if null get normal price" – Caius Jard Mar 16 '19 at 06:40

2 Answers2

1

Any time that you put a where predicate on a table that is outer joined it converts the outer join to an inner join because the nulls introduced by the outer join can never be compared to anything to produce a true (so the outer join puts a load of rows-with-nulls in where rows don't match, and then the WHERE takes the entire row out again)

Consider this simpler example:

SELECT * FROM 
a LEFT JOIN b ON a.id = b.id
WHERE b.col = 'value'

Is identical to:

SELECT * FROM 
a INNER JOIN b ON a.id = b.id
WHERE b.col = 'value'

To resolve this, move the predicate out of the where and into the ON

SELECT * FROM 
a LEFT JOIN b ON a.id = b.id AND b.col = 'value'

You can also consider:

SELECT * FROM 
a LEFT JOIN b ON a.id = b.id
WHERE b.col = 'value' OR b.col IS NULL

but this might pick up data you don't want, if b.col naturally contains some nulls; it cannot differentiate between nulls that are natively present in b.col and nulls that are introduced by a fail in the join to match a row from b with a row from a (unless we also look at the nullness of the joined id column)

A
id
1
2
3

B
id, col
1, value
3, null

--wrong, behaves like inner
A left join B ON a.id=b.id WHERE b.col = 'value'
1, 1, value

--maybe wrong, b.id 3 might be unwanted
A left join B ON a.id=b.id WHERE b.col = 'value' or b.col is null
1, 1, value
2, null, null
3, 3, null

--maybe right, simpler to maintain than the above
A left join B ON a.id=b.id AND b.col = 'value' 
1, 1, value
2, null, null
3, null, null

In these last two the difference is b.id is null or not, though the row count is the same. If we were counting b.id our count could end up wrong. It's important to appreciate this nuance of join behavior. You might even want it, if you were looking to exclude row 3 but include row 2, by crafting a where clause of a LEFT JOIN b ON a.id=b.id WHERE b.col = 'value' OR b.id IS NULL - this will keep row 2 but exclude row 3 because even though the join succeeds to find a b.id of 3 it is not kept by either predicate

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
1

The description of the problem suggests that you want a LEFT JOIN, not a FULL JOIN. FULL JOINs are quite rare, particularly in databases with well defined foreign key relationships.

In your case, the WHERE clause is turning your FULL JOIN into a LEFT JOIN anyway, because the WHERE clause requires valid values from the first table.

SELECT spm.reference AS store_picture_monthly_reference, 
       spm.tenure, spm.name, 
       spmpr.price AS regular_price,
       spmps.price AS special_price
FROM store_picture_monthly spm INNER JOIN
     store_picture_monthly_price_regularspmpr
     ON spmpr.store_picture_monthly_reference = spm.reference LEFT JOIN
     store_picture_monthly_price_special spmps 
     ON spm.reference = spmps.store_picture_monthly_reference AND
        NOW() BETWEEN spmps.begin_date AND spmps.end_date
WHERE spmpr.effective_date < NOW();

Notes:

  • I introduced table aliases so the query is easier to write and to read.
  • The condition on the dates for the sale are now in the ON clause.
  • I removed the GROUP BY. It doesn't seem necessary. If it is, you can use SELECT DISTINCT instead. And, I would investigate data problems if this is needed.
  • I am suspicious about the date comparisons. NOW() has a time component. The naming of the comparison columns suggests that the are just dates with no time.
Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786