Cowbless
Cowbless

Reputation: 103

Tricky sql query required, finding a sum of a subquery

A relevant part of my db looks as follows (MS Visio, I know I'm pathetic :D): Description?

I need to extract a list consisting of all items in a category as well as bundles. So I have to use UNION. First part of a UNION for your reference (as it sets the data format for the SELECT in the second part of UNION; note that ? signifies where an argument goes in node-mysql):

SELECT `ID`, `Name`, `Description`, 
       `PictureID`, `SellingPrice`,
       `Cost`, 0 AS `Bundle` 
FROM `Item` 
WHERE `CategoryID`=? AND 
`ID` IN ( 
         SELECT `ItemID` 
         FROM `Stock` 
         WHERE `CityID`=? 
         AND `IsLimitless`=1 OR `Quantity`>0
        ) 

So I want to present my Bundles as if they are also items, with all same fields etc.

My attempt:

SELECT `ID`, `Name`, `Description`, `PictureID`, 
      (
       SELECT SUM( // Here SQL indicates a syntax problem
                  SELECT `ItemAmount`*`PriceModifier`*(
                                         SELECT `SellingPrice` 
                                         FROM `Item` 
                                         WHERE `ID`=`BundleItem`.`ItemID`
                                         ) 
                   FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
                 )
      ) AS `SellingPrice`,
      (
        SELECT SUM(
                   SELECT `ItemAmount`*(
                                        SELECT `Cost` 
                                        FROM `Item` 
                                        WHERE `ID`=`BundleItem`.`ItemID`
                                       )
                    FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
                  )
      ) AS `Cost`, 
      1 AS `Bundle`
FROM `Bundle` 
WHERE `ID` IN (
               SELECT `BundleID` 
               FROM `BundleCategory` 
               WHERE `CategoryID`=?
              )
//No need to check bundles for stock due to business logic

I have a faint idea that I'm overcomplicating this, but I can't put my finger on it, unfortunately.

Any advise will be very welcome and thanks in advance for taking your time. <3

Sample data:

Fields of no interest like "Description"/"PictureID"/"SupplierID" will be omitted
for the relevant parts to fit on screen

**Bundle**
ID  Name            Description             PictureID
1   Valentine Pack  Blah-blah tasty buy me  imgur link in text

**Item**
ID  Name               SellingPrice  Cost  CategoryID
1   Movie Ticket       10            2     24
2   Box of Chocolates  5             1     4
3   Teddy Bear         15            3     2
4   Roses              10            4     8

**Stock**
ItemID  CityID  Quantity  IsLimitLess 
1       1       25        false
1       2       11        false
2       1       84        false
3       1       33        false
4       1       1         true
4       3       1         true

**BundleItem**
BundleID  ItemID  ItemAmount  PriceModifier
1         1       2           1.25
1         2       1           1
1         3       1           1
1         4       5           0.75

**BundleCategory** (bundle for marketing reasons can appear in different
categories depending on its contents)
BundleID  CategoryID
1         4 //Sweets
1         2 //Toys
1         8 //Flowers

Desired output: (For searching CityID 1, CategoryID 8, Flowers)

ID  Name    (Descr/PicID)    SellingPrice Cost         Bundle

4   Roses                    10           4            false

1   Valentine Pack           82.5         28           true
                           /*2*10*1.25+   2*2+  <movie
                             1*1*5+       1*1+  <chocolate
                             1*1*15+      3*1+  <teddy bear
                             5*0.75*10    5*4   <roses */

User suggested solutions As per @drakin8564 's suggestion I tried doing

SELECT `ID`, `Name`, `Description`, `PictureID`, 
      (
       SELECT SUM(( 
                  SELECT `ItemAmount`*`PriceModifier`*(
                                         SELECT `SellingPrice` 
                                         FROM `Item` 
                                         WHERE `ID`=`BundleItem`.`ItemID`
                                         ) 
                   FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
                        ))
      ) AS `SellingPrice`,
      (
        SELECT SUM((
                   SELECT `ItemAmount`*(
                                        SELECT `Cost` 
                                        FROM `Item` 
                                        WHERE `ID`=`BundleItem`.`ItemID`
                                       )
                    FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
                  ))
      ) AS `Cost`, 
      1 AS `Bundle`
FROM `Bundle` 
WHERE `ID` IN (
               SELECT `BundleID` 
               FROM `BundleCategory` 
               WHERE `CategoryID`=8
              )

Returns

(1242): Subquery returns more than 1 row. 

This happens even when I try SELECT SUM((SELECT ID FROM Item)). Weird. I commented on other solutions about how good they work. I appreciate all you guys taking part in this. <3

Upvotes: 2

Views: 152

Answers (4)

Rich G
Rich G

Reputation: 256

It looks like you had a few syntax issues. Your code worked with a few changes. See comments in query for details.

http://sqlfiddle.com/#!9/ee0725/16

SELECT `ID`, `Name`, `Description`, `PictureID`, 
                  (SELECT SUM(`ItemAmount`*`PriceModifier`*( -- changed order of SELECT and SUM; removed extra SELECT; fixed Parens
                                         SELECT `SellingPrice` 
                                         FROM `Item` 
                                         WHERE `ID`=`BundleItem`.`ItemID`
                                         ))
                   FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`)
       AS `SellingPrice`,
                   (SELECT SUM(`ItemAmount`*( -- changed order of SELECT and SUM; removed extra SELECT; fixed Parens
                                        SELECT `Cost` 
                                        FROM `Item` 
                                        WHERE `ID`=`BundleItem`.`ItemID`
                                       ))
                    FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`)
       AS `Cost`, 
      1 AS `Bundle`
FROM `Bundle` 
WHERE `ID` IN (
               SELECT `BundleID` 
               FROM `BundleCategory` 
               WHERE `CategoryID`=8
              );

Upvotes: 2

Rich G
Rich G

Reputation: 256

Assumption #1: All items must have enough stock in a city for a bundle to be available in that city. (See query comments for how to remove this business rule)

In the sample data, there are no bundles that are fully in stock in any cities - to remedy this, I changed the Quanity for ItemID=4 in CityID=1 from "1" to "5". This created your desired output.

Assumption #2: Stock.Quantity=0 is allowed.

This solution produces query results that contain all Items and Bundles for every City and Category where the Item or Bundle is in stock. The where clause at the bottom filters it to CityID=1 and Category=8 per the original request.

Note: You can paste the Solution and Schema below into www.sqlfiddle.com and see the results.

UPDATE Fixed BundleCategory join.

Solution

select * from (
select 
    Stock.CityID,
    Item.CategoryID,
    Item.ID, 
    Item.Name, 
    Item.Description, 
    Item.SellingPrice, 
    Item.Cost,
    'false' as Bundle 
  from Item
  inner join Stock on Stock.ItemID = Item.ID
  where IFNULL(Stock.Quantity,0) > 0 -- remove this to show out of stock items
 union
  select 
    BundleSummary.CityID,
    BundleCategory.CategoryID,
    Bundle.ID, 
    Bundle.Name, 
    Bundle.Description, 
    BundleSummary.SellingPrice as SellingPrice,
    BundleSummary.Cost as Cost,
    'true' as Bundle 
  from Bundle 
    inner join (
      select
          BundleItem.BundleID, 
          City.CityID,
          MIN(IF(IFNULL(Stock.Quantity, 0) < BundleItem.ItemAmount, 0, 1)) as InStock,
          SUM(Item.SellingPrice * BundleItem.ItemAmount * BundleItem.PriceModifier) as SellingPrice,
          SUM(Item.Cost * BundleItem.ItemAmount) as Cost
        from BundleItem
          inner join Item on Item.ID = BundleItem.ItemID
          inner join (select distinct CityID from Stock where CityID IS NOT NULL) as City on 1=1
          left join Stock on Stock.ItemID = Item.ID and Stock.CityID = City.CityID
        group by BundleItem.BundleID, City.CityID
      ) as BundleSummary on BundleSummary.BundleID = Bundle.ID
    inner join BundleCategory on BundleCategory.BundleID = Bundle.ID
    where BundleSummary.InStock = 1 -- remove this to show out of stock bundles
) as qry1
where CityID=1 and CategoryID=8;

I also generated a script to create the database schema and populate it with the sample data. Thought this might be helpful to anyone who is using this solution to investigate their own issues.

Schema

create table Item (
  ID int,
  Name varchar(255),
  Description varchar(255),
  PictureID int,
  SellingPrice DECIMAL(12,4),
  Cost DECIMAL(12,4),
  SupplierID int,
  CategoryID int
);
insert into Item values (1, 'Movie Ticket', '', NULL, 10, 2, NULL, 24);
insert into Item values (2, 'Box of Chocolates', '', NULL, 5, 1, NULL, 4);
insert into Item values (3, 'Teddy Bear', '', NULL, 15, 3, NULL, 2);
insert into Item values (4, 'Roses', '', NULL, 10, 4, NULL, 8);

create table Bundle (
  ID int,
  Name varchar(255),
  Description varchar(255),
  PictureID int
);
insert into Bundle values (1, 'Valentine Pack', 'Blah-blah tasty buy me', NULL);

create table Stock (
  ItemID int,
  CityID int,
  Quantity int,
  IsLimitless bit
);
insert into Stock values (1, 1, 25, false);
insert into Stock values (1, 2, 11, false);
insert into Stock values (2, 1, 84, false);
insert into Stock values (3, 1, 33, false);
insert into Stock values (4, 1, 5, true);
insert into Stock values (4, 3, 1, true);

create table BundleItem (
  BundleID int,
  ItemID int,
  ItemAmount int,
  PriceModifier DECIMAL(12,4)
);
insert into BundleItem values (1, 1, 2, 1.25);
insert into BundleItem values (1, 2, 1, 1);
insert into BundleItem values (1, 3, 1, 1);
insert into BundleItem values (1, 4, 5, 0.75);

create table BundleCategory (
  BundleID int,
  CategoryID int
);
insert into BundleCategory values (1, 4); -- Sweets
insert into BundleCategory values (1, 2); -- Toys
insert into BundleCategory values (1, 8); -- Flowers

Upvotes: 1

derek.wolfe
derek.wolfe

Reputation: 1116

Your syntax error is because your subquery is not wrapped in (). Examples below.

This will fail:

SELECT SUM(SELECT 1);

This will work:

SELECT SUM((SELECT 1));

Upvotes: 1

Vasya
Vasya

Reputation: 469

Something like this should work

SELECT tb.`ID`, MAX(tb.`Name`), MAX(tb.`Description`), MAX(tb.`PictureID`), 
            SUM(`ItemAmount`*`PriceModifier`*`SellingPrice`) AS `SellingPrice`,
            SUM(`ItemAmount`*`Cost`) AS `Cost`, 
            1 AS `Bundle`
FROM `Bundle` tb 
JOIN `BundleItem` tbi on tb.ID=tbi.BundleID 
JOIN `Item` ti on tbi.ItemID=ti.ID
WHERE tb.`ID` IN (
               SELECT `BundleID` 
               FROM `BundleCategory` 
               WHERE `CategoryID`=?
              )
GROUP BY tb.ID
//No need to check bundles for stock due to business logic

Upvotes: 1

Related Questions