Tricky sql query required, finding a sum of a subquery - mysql

A relevant part of my db looks as follows (MS Visio, I know I'm pathetic :D):
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

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
);

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

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));

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

Related

SQL SUM and divide linked tables

I have the following tables:
create table Cars
(
CarID int,
CarType varchar(50),
PlateNo varchar(20),
CostCenter varchar(50),
);
insert into Cars (CarID, CarType, PlateNo, CostCenter) values
(1,'Coupe','BC18341','CALIFORNIA'),
(2,'Hatchback','AU14974','DAKOTA'),
(3,'Hatchback','BC49207','NYC'),
(4,'SUV','AU10299','FLORIDA'),
(5,'Coupe','AU32703','NYC'),
(6,'Coupe','BC51719','CALIFORNIA'),
(7,'Hatchback','AU30325','IDAHO'),
(8,'SUV','BC52018','CALIFORNIA');
create table Invoices
(
InvoiceID int,
InvoiceDate date,
CostCenterAssigned bit,
InvoiceValue money
);
insert into Invoices (InvoiceID, InvoiceDate, CostCenterAssigned, InvoiceValue) values
(1, '2021-01-02', 0, 978.32),
(2, '2021-01-15', 1, 168.34),
(3, '2021-02-28', 0, 369.13),
(4, '2021-02-05', 0, 772.81),
(5, '2021-03-18', 1, 469.37),
(6, '2021-03-29', 0, 366.83),
(7, '2021-04-01', 0, 173.48),
(8, '2021-04-19', 1, 267.91);
create table InvoicesCostCenterAllocations
(
InvoiceID int,
CarLocation varchar(50)
);
insert into InvoicesCostCenterAllocations (InvoiceID, CarLocation) values
(2, 'CALIFORNIA'),
(2, 'NYC'),
(5, 'FLORIDA'),
(5, 'NYC'),
(8, 'DAKOTA'),
(8, 'CALIFORNIA'),
(8, 'IDAHO');
How can I calculate the total invoice values allocated to that car based on its cost center?
If the invoice is allocated to cars in specific cost centers, then the CostCenterAssigned column is set to true and the cost centers are listed in the InvoicesCostCenterAllocations table linked to the Invoices table by the InvoiceID column. If there is no cost center allocation (CostCenterAssigned column is false) then the invoice value is divided by the total number of cars and summed up.
The sample data in Fiddle: http://sqlfiddle.com/#!18/9bd18/3
The data structure here isn't perfect, hence we need some extra code to solve for this. I needed to gather the amount of cars in each location, as well as to allocate the amounts for each invoice, depending on whether or not it was assigned to a location. I broke out the totals for each invoice type so that you can see the components which are being put together, you won't need those in your final result.
;WITH CarsByLocation AS(
SELECT
CostCenter
,COUNT(*) AS Cars
FROM Cars
GROUP BY CostCenter
UNION ALL
SELECT
''
,COUNT(*) AS Cars
FROM Cars
),CostCenterAssignedInvoices AS (
SELECT
InvoicesCostCenterAllocations.CarLocation
,SUM(invoicevalue) / CarsByLocation.cars AS InvoiceTotal
FROM Invoices
INNER JOIN InvoicesCostCenterAllocations ON invoices.InvoiceID = InvoicesCostCenterAllocations.InvoiceID
INNER JOIN CarsByLocation on InvoicesCostCenterAllocations.CarLocation = CarsByLocation.CostCenter
WHERE CostCenterAssigned = 1 --Not needed, put here for clarification
GROUP BY InvoicesCostCenterAllocations.CarLocation,CarsByLocation.Cars
),UnassignedInvoices AS (
SELECT
'' AS Carlocation
,SUM(invoicevalue)/CarsByLocation.Cars InvoiceTotal
FROM Invoices
INNER JOIN CarsByLocation on CarsByLocation.CostCenter = ''
WHERE CostCenterAssigned = 0
group by CarsByLocation.Cars
)
SELECT
Cars.*
,cca.InvoiceTotal AS AssignedTotal
,ui.InvoiceTotal AS UnassignedTotal
,cca.InvoiceTotal + ui.InvoiceTotal AS Total
FROM Cars
LEFT OUTER JOIN CostCenterAssignedInvoices CCA ON Cars.CostCenter = CCA.CarLocation
LEFT OUTER JOIN UnassignedInvoices UI ON UI.Carlocation = ''
ORDER BY
Cars.CostCenter
,Cars.PlateNo;

Returning records which only have one specific many to many relation

Given this structure
CREATE TABLE locations
(`id` int, `Name` varchar(128))
;
INSERT INTO locations
(`id`, `Name`)
VALUES
(1, 'Location 1'),
(2, 'Location 2'),
(3, 'Location 3')
;
CREATE TABLE locations_publications
(`id` int, `publication_id` int, `location_id` int)
;
INSERT INTO locations_publications
(`id`, `publication_id`, `location_id`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 1, 3)
;
I would like to find only Location 2 based on the fact that it has only one relation with a publication_id = 2.
It should not return location one due to the fact that it has two relation rows.
This is sort of what I'm looking for but of course dosnt work because it limits the relationship to where publication_id = 2.
select * from locations
join locations_publications on locations_publications.location_id = locations.id
where locations_publications.publication_id = 2
group by (locations.location_id)
having count(*) = 1
You can do this with aggregation:
select location_id
from locations_publications
group by location_id
having count(*) = 1
If a location might have multiple records with the same publication, change the having criteria to count(distinct publication_id) = 1
Given your edits, you can use conditional aggregation for that:
select location_id
from locations_publications
group by location_id
having count(*) = sum(case when publication_id = 2 then 1 else 0 end)

Loop through each ID with more than 3 records in the same table

This is my table:
placeID | name | time
My goal is to remove the oldest records if the placeID has more than 3 record in the table.
It's pretty hard to understand, so example will make it clear:
1 | "Some Name" | *fresher timestamp*
1 | "Some Name" | *fresher timestamp*
1 | "Some Name" | *older timestamp* -- > Delete this result since there are more than 3 records, and this is the older than the rest
1 | "Some Name" | *older timestamp* -- > Delete this result since there are more than 3 records, and this is older than the rest
1 | "Some Name" | *fresher timestamp*
2 | "Some Name" | *fresher timestamp*
2 | "Some Name" | *fresher timestamp*
My trouble: How do I loop for each placeID that has more than 3 record in my table?
Removing the oldest results will not be a problem.
What I tried:
SELECT placeID,COUNT(placeID) FROM place_fbStatus WHERE count(placeID) > 3
Error: #1111 - Invalid use of group function
Another Try:
SELECT id, placeID
FROM place_fbStatus
HAVING COUNT( placeID ) >4
LIMIT 0 , 30
Problem: Show only one placeID
Here my Answer:
First thing is to create a PRIMARY KEY on your Table. So it is easy to DELETE a ROW with this.
Create the Table:
CREATE TABLE `mytable` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`placeID` INT(11) DEFAULT NULL,
`name` VARCHAR(32) DEFAULT NULL,
`time` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `placeID` (`placeID`,`time`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Insert some stuff:
INSERT INTO `mytable` (`id`, `placeID`, `name`, `time`)
VALUES
(1, 1, 'Some Name', '2015-01-01 00:00:00'),
(2, 1, 'Some Name', '2015-01-02 00:00:00'),
(3, 1, 'Some Name', '2015-01-10 00:00:00'),
(4, 1, 'Some Name', '2015-01-09 00:00:00'),
(5, 2, 'Some Name', '2015-02-01 00:00:00'),
(6, 2, 'Some Name', '2015-01-30 00:00:00'),
(7, 1, 'Some Name', '2015-01-08 00:00:00');
i add rid ( ROW ID per Place ) and the Rows are in the right order
SELECT #rid:=IF( #pid = m.placeID, #rid+1 , 1) rid, m.*, #pid:=m.placeID AS pid
FROM mytable m, (SELECT #rid:=0, #pid:=-1) AS tmp
ORDER BY placeID,TIME ASC
And at last the Query to DELETE the ROWs:
DELETE mt FROM mytable mt
INNER JOIN (
SELECT * FROM (
SELECT #rid:=IF( #pid = m.placeID, #rid+1 , 1) rid, m.*, #pid:=m.placeID AS pid
FROM mytable m, (SELECT #rid:=0, #pid:=-1) AS tmp
ORDER BY placeID,TIME ASC
) AS x
WHERE x.rid >3
) AS dt ON mt.id = dt.id ;
You almost nailed it. What you need is to combine GROUP BY and HAVING
SELECT placeID, count(placeID) AS cnt
FROM place_fbStatus
GROUP BY placeID HAVING cnt>3
DELETE FROM Table WHERE ID NOT IN (SELECT TOP 3 ID FROM Table)
Refer to this thread
Although, it seems after trying it myself, it seems some versions of mysql dont yet support limit clauses in subqueries (This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'). To workaround this, try using an alternate subquery that doesnt involve limits.

LEFT OUTER JOIN...with differing matching keys

So...this is a little confusing. I have 2 tables, one is basically a list of Codes and Names of people and topics and then a value, for example:
The second table is just a list of topics, with a value and a "result" which is just a numerical value too:
Now, what I want to do is do a LEFT OUTER JOIN on the first table, matching on topic and value, to get the "Result" field from the second table. This is simple in the majority of cases because they will almost always be an exact match, however there will be some cases there won't be, and in those cases the problem will be that the "Value" in table 1 is lower than all the Values in table 2. In this case, I would like to simply do the JOIN as though the Value in table 1 equalled the lowest value for that topic in table 2.
To highlight - the LEFT OUTER JOIN will return nothing for Row 2 if I match on topic and value, because there's no Geography row in table 2 with the Value 30. In that case, I'd like it to just pick the row where the value is 35, and return the Result field from there in the JOIN instead.
Does that make sense? And, is it possible?
Much appreciated.
You can use Cross Apply here. There may be a better solution performance wise.
declare #people table(
Code int,
Name varchar(30),
Topic varchar(30),
Value int
)
declare #topics table(
[Subject] varchar(30),
Value int,
Result int
)
INSERT INTO #people values (1, 'Doe,John', 'History', 25),
(2, 'Doe,John', 'Geography', 30),
(3, 'Doe,John', 'Mathematics', 45),
(4, 'Doe,John', 'Brad Pitt Studies', 100)
INSERT INTO #topics values ('History', 25, 95),
('History', 30, 84),
('History', 35, 75),
('Geography', 35, 51),
('Geography', 40, 84),
('Geography', 45, 65),
('Mathematics', 45, 32),
('Mathematics', 50, 38),
('Mathematics', 55, 15),
('Brad Pitt Studies', 100, 92),
('Brad Pitt Studies', 90, 90)
SELECT p.Code, p.Name,
case when p.Value < mTopic.minValue THEN mTopic.minValue
else p.Value
END, mTopic.minValue
FROM #people p
CROSS APPLY
(
SELECT [Subject],
MIN(value) as minValue
FROM #topics t
WHERE p.Topic = t.Subject
GROUP BY [Subject]
) mTopic
I am also assuming that:
This is simple in the majority of cases because they will almost always be an exact match, however there will be some cases there won't be, and in those cases the problem will be that the "Value" in table 1 is lower than all the Values in table 2.
is correct. If there is a time when Value is not equal to any topic values AND is not less than the minimum, it will currently return the people.value even though it is not a 'valid' value (assuming topics is a list of valid values, but I can't tell from your description.)
Also technically you only need that case statement in the select statement, not the following mTopic.minValue but I thought the example showed the effect better with it.
Another method of performing this is by using a temporary table to hold the different values.
First insert the exact matches, then insert the non-exact matches that where not found in the initial select and finally grab all the results from the temp table. This solution is more code than the other, so just adding it as an alternative.
Example (SqlFiddle):
Schema first
create table students
( code integer,
name varchar(50),
topic varchar(50),
value integer );
create table subjects
( subject varchar(50),
value varchar(50),
result integer );
insert students
( code, name, topic, value )
values
( 1, 'Doe, John', 'History', 25),
( 2, 'Doe, John', 'Geography', 30),
( 3, 'Doe, Jane', 'Mathematics', 45),
( 4, 'Doe, Jane', 'Brad Pitt Studies', 100);
insert subjects
( subject, value, result )
values
( 'History', 25, 95 ),
( 'History', 30, 84 ),
( 'History', 35, 75 ),
( 'Geography', 35, 51 ),
( 'Geography', 40, 84 ),
( 'Geography', 45, 65 ),
( 'Mathematics', 45, 32 ),
( 'Mathematics', 50, 38 ),
( 'Mathematics', 55, 15 ),
( 'Brad Pitt Studies', 100, 92 ),
( 'Brad Pitt Studies', 90, 90 );
The actual SQL query:
-- Temp table to hold our results
create temporary table tempresult
( code integer,
name varchar(50),
topic varchar(50),
studentvalue integer,
subjectvalue integer,
result integer );
-- Get the exact results
insert tempresult
( code,
name,
topic,
studentvalue,
subjectvalue,
result )
select stu.code,
stu.name,
stu.topic,
stu.value as 'student_value',
sub.value as 'subject_value',
sub.result
from students stu
join
subjects sub on sub.subject = stu.topic
and sub.value = stu.value;
-- Get the non-exact results, excluding the 'students' that we already
-- got in the first insert
insert tempresult
( code,
name,
topic,
studentvalue,
subjectvalue,
result )
select stu.code,
stu.name,
stu.topic,
stu.value as 'student_value',
sub.value as 'subject_value',
sub.result
from students stu
join
subjects sub on sub.subject = stu.topic
-- Business logic here: Take lowest subject value that is just above the student's value
and sub.value = (select min(sub2.value)
from subjects sub2
where sub2.subject = stu.topic
and sub2.value > stu.value)
where not exists (select 1
from tempresult tmp
where tmp.code = stu.code
and tmp.name = stu.name
and tmp.topic = stu.topic)
-- Get our resultset
select code,
name,
topic,
studentvalue,
subjectvalue,
result
from tempresult
order by code,
name,
topic,
studentvalue,
subjectvalue,
result
In this case I would make two joins instead of one. Something like this:
select *
from Table1 T1
LEFT JOIN Table2 T2 on T1.Topic=T2.subject and T1.Value=T2.VALUE
LEFT JOIN Table2 as T3 on T1.Topic=T3.Subject and T1.Value<T2.Value
The do a case to choose the table to take values from. If T2.value is null then use T3.Value ELSE T2.Value. Hope this helps you
A left join is not called for in the requirements. You want to join when T1.Subject = T2.Topic and then either when T1.Value = T2.Value or when T1.Value < T2.Value and T2.Value is the smallest value. Just write it out that way:
select p.*, t.Result
from #People p
join #Topics t
on t.Subject = p.Topic
and( t.Value = p.Value
or( p.Value < t.value
and t.Value =(
select Min( Value )
from #Topics
where Subject = t.Subject )));
Which generates:
Code Name Topic Value Result
---- -------- ----------------- ----- ------
1 Doe,John History 25 95
2 Doe,John Geography 30 51
3 Doe,John Mathematics 45 32
4 Doe,John Brad Pitt Studies 100 92

MySQL, conditionally adding columns

I have two tables, the first holds data about patients, it looks like this.
id patient sex impact
------------------------------------------
1 Bill Jones male .1
2 Sarah Smith female .4
The second holds "multipliers". These multipliers will be used to multiply the impact in the table above.
id type type_value multiplier
-----------------------------------------------
1 patient Bill Jones .5
2 sex male .3
3 sex male .8
4 sex female .7
I am trying to run a query that will return the following:
patient patient_total sex_total new_impact
-------------------------------------------------------------------------------
Bill Jones .5 1.1 .16
Sarah Smith 0 .7 .28
Where the new impact is the (patient_total + sex_total) * impact for each patient.
Here are the create statements:
--
-- Table structure for table `impact`
--
CREATE TABLE IF NOT EXISTS `impact` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`patient` varchar(20) NOT NULL,
`sex` varchar(7) NOT NULL,
`impact` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `impact`
--
INSERT INTO `impact` (`id`, `patient`, `sex`, `impact`) VALUES
(1, 'Bill Jones', 'male', 0.1),
(2, 'Sarah Smith', 'female', 0.4);
--
-- Table structure for table `multipliers`
--
CREATE TABLE IF NOT EXISTS `multipliers` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(20) NOT NULL,
`type_value` varchar(60) NOT NULL,
`multiplier` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=14 ;
--
-- Dumping data for table `multipliers`
--
INSERT INTO `multipliers` (`id`, `type`, `type_value`, `multiplier`) VALUES
(1, 'patient', 'Bill Jones', 0.5),
(2, 'sex', 'male', 0.3),
(3, 'sex', 'male', 0.8),
(13, 'sex', 'female', 0.7);
I have tried several iterations of the following query, but can't get it to work :/
Updated quer - working for sex_total column, not for patient_total column :/
select p.patient, ifnull(sum(ipatient.multiplier), 0) as patient_total, ifnull(sum(isex.multiplier), 0) as sex_total, (ifnull(sum(ipatient.multiplier), 0) + ifnull(sum(isex.multiplier), 0) * p.impact) as new_impact
from impact p
left outer join multipliers ipatient
on ipatient.type = 'patient' and ipatient.type_value = p.patient
left outer join multipliers isex
on isex.type = 'sex' and isex.type_value = p.sex
group by p.patient
Could someone please help?
Thanks
You just need to join in the impact rows and then aggregate the results. The following does two joins, one for each type of impact:
select p.patient, sum(ipatient.multiplier) as patient_total,
sum(isex.multiplier) as sex_total,
(sum(ipatient.multiplier) * sum(isex.multiplier) * p.imact
) as new_impact
from impact p left outer join
multipliers ipatient
on ipatient.type = 'patient' and ipatient.type_value = p.patient left outer join
multipliers isex
on isex.type = 'sex' and isex.type_value = p.sex
group by p.patient;
You might need a coalesce() if there might be some rows that have no matches for either the patient name or sex.
EDIT:
Dumb. Dumb. Dumb. The above fails because the isex multiplies the rows, so it affects the sum() of ipatient. This version works:
select p.patient,
sum(case when m.type = 'patient' then m.multiplier else 0 end) as patient_total,
sum(case when m.type = 'sex' then m.multiplier else 0 end) as sex_total,
(sum(m.multiplier)* p.impact
) as new_impact
from impact p left outer join
multipliers m
on m.type = 'patient' and m.type_value = p.patient or
m.type = 'sex' and m.type_value = p.sex
group by p.patient;
You can see it here.
If you don't like all the decimal places from the float, you can switch to a numeric/decimal data type.
This query will give you the columns you need, but you'll have to do the multiplication yourself.
SELECT i.id, i.patient, i.impact,
(SELECT IFNULL(SUM(multiplier), 0) FROM multipliers
WHERE type='patient' AND type_value=i.patient) `patient_total`,
(SELECT IFNULL(SUM(multiplier), 0) FROM multipliers
WHERE type='sex' AND type_value=i.sex) `sex_total`
FROM impact i