tags:

views:

117

answers:

2

I am having trouble just now with yet another SQL problem. I really need to take some time out to learn this properly.

Anyway I have this query that someone else wrote and it gets values from a few different tables.

Now more than one item can have the same ProductID. So there may be 3 items returned all with the same ProductID but they have different descriptions etc.

I want to select only 1 item per ProductID. I have tried using DISTINCT and group by but I get a lot of errors. Also this is for an ACCESS database.

I think it's because of the logic used in the select query that is messing up my grouping.

Here is the query (I have tried formatting it a little better, used an online tool but its still a huge mess)

SELECT   tblproducts.productid,
     tblproducts.categorycode,
     tblproducts.scaletitle,
     tblproducts.picture,
     tblitems.cost,
     tblitems.modelnumber,
     tblitems.itemid,
     Iif([tblitems]![tradeapproved],Iif(([tblitems]![markup] / 100) <> 0,(Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                                                                              [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                                                                              [tblitems]![cost])) * ([tblitems]![markup] / 100),
                                        0) + Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                                                 [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                                                 [tblitems]![cost]) + [tblitems]![tradeapprovedcost] + [tblitems]![shippingcost],
         Iif(([tblitems]![markup] / 100) <> 0,(Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                                                   [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                                                   [tblitems]![cost])) * ([tblitems]![markup] / 100),
             0) + Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                      [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                      [tblitems]![cost]) + [tblitems]![shippingcost]) AS price
FROM     (tblitems
      INNER JOIN tblproducts
        ON tblitems.productid = tblproducts.productid)
     INNER JOIN tblsuppliers
       ON tblproducts.supplierid = tblsuppliers.supplierid
WHERE    tblproducts.categorycode = 'BS'
     AND tblitems.tradeapproved = 0
     AND tblsuppliers.active = on
     AND tblitems.isaccessory = false
ORDER BY Iif([tblitems]![tradeapproved],Iif(([tblitems]![markup] / 100) <> 0,(Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                                                                              [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                                                                              [tblitems]![cost])) * ([tblitems]![markup] / 100),
                                        0) + Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                                                 [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                                                 [tblitems]![cost]) + [tblitems]![tradeapprovedcost] + [tblitems]![shippingcost],
         Iif(([tblitems]![markup] / 100) <> 0,(Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                                                   [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                                                   [tblitems]![cost])) * ([tblitems]![markup] / 100),
             0) + Iif(([tblitems]![supplierdiscount] / 100) <> 0,
                      [tblitems]![cost] - ([tblitems]![cost] * ([tblitems]![supplierdiscount] / 100)),
                      [tblitems]![cost]) + [tblitems]![shippingcost])

Can anyone post a quick fix for this? Thanks

A: 

Well, since you said you want to learn this stuff:

An inner join will connect Items to ProductId's but will result in a full set. So if you have 3 ProductIds and 1 Item you will get

ProdId   ItemId  Description
1        1       Handy Dandy Randy Sandy!
2        1       Easily Accessible personal grooming comb.
3        1       This item provides a man or woman with extra re...

So what you really want to do is get all the ItemIds:

select ItemId from Item_tbl

And then loop over each result, getting a single ProductId per Item:

select top 1 ProductId from Product_tbl where ItemId = 12345

Now anyone who suggests a loop with SQL gets yelled down, and (usually) rightly so. But this is a tough query to make, since it's not something people usually do.

You were along the right lines with group by. Group By says "consolidate all the rows that have distinct column X" where column X would be ItemId. So: Give me one row per ItemId.

Now you have to pick a ProductId from those 3 Products with ItemId 1. The cheater way to do it is not to pick a ProductId at random but rather a productId that fits a particular aggregate function. The most common are min and max.

select 
  ItemId, 
  max(ProductId) 
from Itemtbl i
inner join Producttbl p
  on i.itemid = p.itemId
group by ItemId

This will get the largest ProductId for each ItemId. You can do the same to get the minimum.

Now, what's trickier is finding a ProductId that fits a criteria - say the most recently updated. What you want to say is "select the ItemId, and the max(updatedDate), and then pull the ProductId of that max updatded date along - but that doesn't work in sql (dear god I wish it did though).

This query will give bad results:

select 
  ItemId, 
  max(ProductId), 
  max(updatdedDate)
from Itemtbl i
inner join Producttbl p
  on i.itemid = p.itemId
group by ItemId

Because the max ProductId does not necessarily come from the row with the max updatedDate.

Instead you have to write a query that does this:

  1. Selects the ItemId (e.g. 5), and the maxUpdated date (e.g. 5/5/2005)
  2. Goes back to the Products_tbl and finds the ProductId whose ItemId is 5 and updatedDate is 5/5/2005

That query is left as an exercise. (but there's a bug! what if two products have the same last updated date and the same ItemId!)

Tom Ritter
I like your answer and the effort. However it's not really helped me just now. I do know the basics of SQL but this query is just a nightmare to work with and no matter what i try I get lots of errors, I think it's to do with the if statements. VS2008 can't even parse the query! Thanks again though
dean nolan
I tried wrapping the original query up as a derived table and selecting the specific properties. when i try to do max on itemId i get an error that there is no aggregate function for productId
dean nolan
There is no aggregate function for productId; you don't have a "group by" clause, so you can't use max. Frankly, the best way to approach this is to break it down. Examine what tables you have, what PKs and FKs you have, then figure out what it is you're trying to get. As it is, it looks like spaghetti (even re-formatted).
Michael Todd
+1  A: 

First step to increase readability is to create a View for your tblItems that includes the fancy logic for Price, eg:

View [vwItemsWithAdjustedCost]

SELECT
    ProductID,
    TradeApproved,
    IsAccessory,
    Cost,
    ModelNumber,
    ItemID,
    IIf(
        ( SupplierDiscount / 100 ) <> 0,
        Cost - ( Cost * ( SupplierDiscount / 100 ) ),
        Cost
    ) AS AdjustedCost
FROM tblItems

View [vwItemsWithPrice]

SELECT
    ProductID,
    TradeApproved,
    IsAccessory,
    Cost,
    ModelNumber,
    ItemID,
    IIf(
        ( Markup / 100 ) <> 0,
        AdjustedCost * ( Markup / 100 ),
        0
    ) 
    + AdjustedCost
    IIf(
        TradeApproved,
        TradeApprovedCost,
        0
    )
    + ShippingCost AS Price
FROM vwItemsWithAdjustedCost

Next you have to decide what the criteria is for picking one item out of the many that match the same ProductID, if 3 items have the same ID which one do you want to show!?

As stated by Tom, an easy way is to just get the first (lowest) ID that matches, something like this:

SELECT
    P.ProductID,
    P.CategoryCode,
    P.ScaleTitle,
    P.Picture,
    IP.Cost,
    IP.ModelNumber,
    IP.ItemID,
    IP.Price
FROM
    tblProducts P
    INNER JOIN (
        SELECT
            ProductID,
            MIN( ItemID ) AS MinItemID
        FROM tblItems I
        GROUP BY ProductID
    ) S
        ON S.ProductID = P.ProductID
    INNER JOIN vwItemsWithPrice IP
        ON IP.ItemID = S.MinItemID
WHERE
    P.CategoryCode = 'BS'
    AND IP.TradeApproved = 0
    AND IP.IsAccessory = false
ORDER BY IP.Price

This says for each ProductID, give me the first (lowest) ItemID from tblItems, and using that join to my view.

Hope this helps!

Timothy Walters
Thanks for the answer, it's helping. I have tried your method but I'm still a little stuck. I have your last snippet but without the where and the order by and just select * for simplicity. I get a syntax error in access pointing to the final inner join on the view. I even tried substituting the view for just the items table so I knew there wouldn't be a problem but still the same error.
dean nolan
The error is syntax error operator missing and shows the code SONS.ProductID = P.ProductIDINNER JOIN (SELECT ItemID from tblItems) IPON IP.ItemID = S.MinItemID
dean nolan
You're using Access, and I think it requires nesting of the joins (been a while since I used it, could be wrong), simply put brackets around the first join, FROM ( tblProducts P ... ON S.ProductID = P.ProductID ) INNER JOIN ...
Timothy Walters