i know that this is not the recommended way to join
tables. But it's only relevant for one rarely used report for one person and i don't want to change my datamodel for it.
I have two tables Model
and SparePart
that are not directly linked with each other via foreign keys.
Model SparePart
idModel idSparePart
ModelName SparePartDescription
Price
In special cases a model is also a sparepart(exchange unit). Then i need the price for this model from the SparePart
table via its SparePartDescription
column.
For example:
ModelName = C510
SparePartDescription = C510/Exchange Unit/Exch unit/Red
So i try to join both tables to get the price with following SQL:
SELECT m.idModel, m.ModelName, sp.Price, sp.SparePartDescription
FROM modModel AS m INNER JOIN
tabSparePart AS sp ON m.ModelName =
(SELECT TOP 1 LEFT(sp.SparePartDescription, CHARINDEX('/', sp.SparePartDescription) - 1)order by price desc)
WHERE (CHARINDEX('/', sp.SparePartDescription) > 0)
AND (sp.fiSparePartCategory = 6)
ORDER BY m.ModelName, sp.SparePartDescription
But i get multiple records for one model:
idModel ModelName Price SparePartDescription
569 C510 70,75 C510/Exchange Unit/Exch unit/Red
569 C510 70,75 C510/Exchange Unit/Latin/Generic/Black
569 C510 70,75 C510/Exchange Unit/Latin/Generic/Silver
433 C702 80,72 C702/Exchange Unit/Latin/Generic/Black
433 C702 NULL C702/Exchange Unit/Latin/Generic/Cyan
433 C702 80,72 C702/Exchange Unit/Orange Global/Black
I only want to select one record if there are multiple spareparts with matching SparePartDescription.
Sql Server 2005 and better introduced the 'APPLY' operator which allows you to join against a subquery... Try this.
SELECT m.idModel, m.ModelName, sp.Price, sp.SparePartDescription
FROM modModel AS m
CROSS APPLY
(
SELECT TOP 1 * FROM tabSparePart
WHERE m.ModelName =
LEFT(SparePartDescription, LEN(ModelName))
ORDER BY Price DESC
) sp
WHERE (sp.fiSparePartCategory = 6)
ORDER BY m.ModelName, sp.SparePartDescription
It inner joins the 'modModel' table with the subquery 'only the top one matching tabSparePart'.
You can also use OUTER APPLY which will emulate a LEFT JOIN on the subquery. Documentation is here .
First, your join condition can be simplified some, and then you can use ROW_NUMBER() to specify some kind of order to your results, allowing the first result (per model) to be selected. I also changed it to a LEFT JOIN in case there was no match. If that is not required, it's simple to change back to an INNER JOIN :)
WITH
ranked_results AS
(
SELECT
m.idModel, m.ModelName, sp.Price, sp.SparePartDescription,
ROW_NUMBER() OVER (PARTITION BY m.idModel ORDER BY sp.Price DESC) AS rank
FROM
modModel AS m
LEFT JOIN
tabSparePart AS sp
ON LEFT(sp.SparePartDescription, LEN(m.ModelName)) = m.ModelName
AND (CHARINDEX('/', sp.SparePartDescription) > 0)
AND (sp.fiSparePartCategory = 6)
)
SELECT
*
FROM
ranked_results
WHERE
rank = 1
ORDER BY
ModelName,
SparePartDescription
@MattMurrell's answer just appeared while I was typing this. One difference here is that the selection criteria is being applied to the whole set, rather than separately in the CROSS APPLY. This may have a performance benefit, you'd have to try and see. CROSS APPLY with inline functions is normally more performant that correlated sub-queries, so I can't predict which is faster.
Try the ROW_NUMBER function. It guarantees that you'll only get one of each item as defined in the PARTITION BY clause.
SELECT a.idModel, a.ModelName, Price, SparePartDescription
FROM modModel a
LEFT JOIN
(
SELECT m.idModel, m.ModelName, sp.Price, sp.SparePartDescription
, ROW_NUMBER() OVER (PARTITION BY m.idModel, m.ModelName ORDER BY sp.price DESC) AS r
FROM modModel AS m
INNER JOIN tabSparePart AS sp
ON m.ModelName = LEFT(sp.SparePartDescription, CHARINDEX('/', sp.SparePartDescription) - 1)
WHERE (CHARINDEX('/', sp.SparePartDescription) > 0)
AND (sp.fiSparePartCategory = 6)
) b
ON a.idModel = b.idModel
AND b.r = 1
ORDER BY ModelName, SparePartDescription
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.