Given the requirement I think that the design of the mapping table needs changing to allow the query to be written in a way to produce the correct results and be flexible for the future. I would propose dividing the mapping table into 2 tables (IN and OUT). Your examples show cases where upto 3 orderitems can be passed in and upto 2 passed out.
Using the table structure below would give the flexibility required.
declare @Order table (OrderID int, OrderItem varchar(10), Quantity int, Price money)
declare @Mapping_IN table (MapID varchar(3), OrderItem varchar(10), MajorProduct int)
declare @Mapping_OUT table (MapID varchar(3), OrderItem varchar(10))
Your example data would then be :
insert into @Order values
(1,'A',3,960),(1,'B',1,0),(1,'C',1,0),(1,'D',1,0),
(2,'E',5,100),(2,'B',1,0),(2,'C',1,0),
(3,'Q',6,1000),
(4,'B',1,0),(4,'A',3,300),
(5,'A',7,4000)
insert into @Mapping_IN values
('S1','A',1),('S1','B',0),('S1','C',0),('S2','A',1),('S2','B',0),('S3','A',1),('S4','D',1),
('S5','E',1),('S5','B',0),('S5','C',0),('S6','Q',1),('S7','J',1),('S7','B',0),('S7','C',0)
insert into @Mapping_OUT values
('S1','X'),('S1','S'),('S2','P'),('S2','R'),('S3','O'),('S4','Z'),('S5','Y'),('S6','N'),('S7','N');
Your requirements state that all orderitems must be present. As OrderID (containing ABC) could match 3 of the mapping rows I have assumed that the one with the most products matching would be required. If you had the case where two mappings, mapping 1 (ABC) and mapping 2 (BCD), which would you require for an order with products ABCD as both would match.
Query to give the result set you outline (first using CTE and second using subqueries).
--CTE
;with MapItemList as
(
select MapID, count(*) as NumberOfItemsInList
from @Mapping_IN
group by MapID
),
MatchesList as
(
select MI.MapID, O.OrderID, count(*) as NumberOfMatches
from @Order as O
inner join @Mapping_IN as MI
on O.OrderItem = MI.OrderItem
group by MI.MapID, O.OrderID
),
FindMatches as
(
select ML.*, row_number() over (partition by ML.OrderID order by ML.NumberOfMatches desc) as RN
from MatchesList as ML
inner join MapItemList as MA
on ML.MapID = MA.MapID and ML.NumberOfMatches = MA.NumberOfItemsInList
)
select O.OrderID, MO.OrderItem, O.Quantity, O.Price
from FindMatches as FM
inner join @Mapping_IN as MI
on FM.MapID = MI.MapID and MI.MajorProduct = 1
inner join @Order as O
on O.OrderID = FM.OrderID and MI.OrderItem = O.OrderItem
inner join @Mapping_OUT as MO
on FM.MapID = MO.MapID
where RN = 1; -- Only allows the best match available i.e. most amount of products matched
-- Subquery
select O.OrderID, MO.OrderItem, O.Quantity, O.Price
from (select ML.*, row_number() over (partition by ML.OrderID order by ML.NumberOfMatches desc) as RN
from (select MI.MapID, O.OrderID, count(*) as NumberOfMatches
from @Order as O
inner join @Mapping_IN as MI
on O.OrderItem = MI.OrderItem
group by MI.MapID, O.OrderID) as ML
inner join (select MapID, count(*) as NumberOfItemsInList
from @Mapping_IN
group by MapID) as MA
on ML.MapID = MA.MapID and ML.NumberOfMatches = MA.NumberOfItemsInList) as FM
inner join @Mapping_IN as MI
on FM.MapID = MI.MapID and MI.MajorProduct = 1
inner join @Order as O
on O.OrderID = FM.OrderID and MI.OrderItem = O.OrderItem
inner join @Mapping_OUT as MO
on FM.MapID = MO.MapID
where RN = 1; -- Only allows the best match available i.e. most amount of products matched
Fitz