views:

577

answers:

3

The purpose of this query is to bring back products and their prices for products on sale and the price should be from the date closest but not equal to the date passed in, essentially the most recent price available. There are not price records for every day. Something feels a little wrong about having the aggregate select statement in the where clause. Is there a better way to do this? Maybe in the join criteria?

        select  
        p.ProductName,
        pp.Price,
        pp.Date,
        from product p
        inner join productprice pp  on p.productid = pp.productid
        where 
        pp.evaluationdate = (select  max(Date) from productprice 
                             where productid = p.productid  
                             and date < @DateIn) 
        and p.producttype = 'OnSale'

The actually query is a little more complicated but this is essentially the problem. Thanks for your input.

EDIT There will be more than one product returned

EDIT I'm experimenting with both @Remus Rusanu's and @km's suggestions (although @Remus Rusanu removed his) all three, including my original, seem to be about the same in terms of performance. I'm trying to decide if one offers a benefit over the others in some other intangible way i.e. maintenance, self documenting etc. as this will be maintained by someone else. Thanks again.

A: 
SELECT TOP 1 p.ProductName, pp.Price, pp.Date,
FROM product p
INNER JOIN productprice pp on ...
WHERE pp.date < @DateIn
ORDER BY pp.date DESC
orthod0ks
Sorry There will be more than one product returned, all products on sale, so top 1 will only bring me the first record for the first prodcut
Gratzy
+4  A: 

try this:

;WITH CurrentPrice AS 
(
SELECT productid,max(Date) AS Date
    FROM productprice 
    WHERE date < @DateIn 
    GROUP BY productid
)

select  
    p.ProductName,
    pp.Price,
    pp.Date,
    from product p
        inner join CurrentPrice pa  on p.productid = pa.productid
        inner join productprice pp  on pa.productid = pp.productid AND pa.Date=pp.Date
    where p.producttype = 'OnSale'

EDIT based on OP's comment:

I think the above query with CTE will have the same query plan as the the derived table version from @Remus Rusanu

However, if the productprice table is large, you may want to reduce it by filtering by the "OnSale" like here:

;WITH CurrentPrice AS 
(
select  
    p.productid,
    MAX(pp.Date) AS Date
    from product p
        inner join productprice pp  on pa.productid = pp.productid
    where p.producttype = 'OnSale' AND pp.date < @DateIn 
    GROUP BY productid
)
select  
    p.ProductName,
    pp.Price,
    pp.Date,
    from CurrentPrice           pa
        inner join product      p   on pa.productid = p.productid
        inner join productprice pp  on pa.productid = pp.productid AND pa.Date=pp.Date
    where p.producttype = 'OnSale'
KM
I think this probably would work, though I would limit the CTE to filter the products for the "onsale" criteria, but it seems like essentially the same thing. Would performance be any different with the CTE over the subquery? ProductPrice is a big table
Gratzy
My response was basically identical with yours, so I removed mine.
Remus Rusanu
+1  A: 

Is this a job for window functions?

    SELECT * FROM (select  
            p.ProductName,
            pp.Price,
            pp.Date,
            RANK() OVER(PARTITION BY p.ProductId ORDER BY pp.Date DESC) as row_rank
            from product p
              join productprice pp  on p.productid = pp.productid
            where 
              pp.date < @DateIn
              and p.producttype = 'OnSale'
    ) saleprice
    where row_rank = 1

EDIT partitions by id (assuming your primary key is fastest), partiton on price removed

Buckwad
I thought about a solution like this, but went with the CTE. I think the RANK() should be: _RANK() OVER(PARTITION BY p.Productid ORDER BY p.Productid,pp.Date DESC) as row_rank_
KM
You're right I did goof the RANK(), it need only partiton on the product id but the order by does not need it, I think. edited
Buckwad