简体   繁体   English

使用子查询在varchar列上进行联接

[英]JOIN on varchar column with subquery

i know that this is not the recommended way to join tables. 我知道这不是推荐的join表的方式。 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. 我有两个表ModelSparePart ,它们不是通过外键直接链接的。

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. 然后,我需要通过其SparePartDescription列从SparePart表中获得该模型的价格。

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: 因此,我尝试通过下面的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. 如果有多个备件与SparePartDescription匹配,我只想选择一个记录。

Sql Server 2005 and better introduced the 'APPLY' operator which allows you to join against a subquery... Try this. Sql Server 2005及更高版本引入了“ APPLY”运算符,该运算符使您可以对子查询进行联接...尝试此操作。

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'. 它内部将'modModel'表与子查询'仅匹配最上面的一个匹配的tabSparePart'连接在一起。

You can also use OUTER APPLY which will emulate a LEFT JOIN on the subquery. 您还可以使用OUTER APPLY来模拟子查询上的LEFT JOIN。 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. 首先,可以将连接条件简化一些,然后可以使用ROW_NUMBER()为结果指定某种顺序,从而允许选择第一个结果(每个模型)。 I also changed it to a LEFT JOIN in case there was no match. 如果没有匹配项,我也将其更改为LEFT JOIN。 If that is not required, it's simple to change back to an INNER JOIN :) 如果不需要,可以很容易地改回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. 我输入此内容时,@ MattMurrell的答案才出现。 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. CROSS内联函数适用通常更高性能的是相关子查询,所以我无法预测哪个更快。

Try the ROW_NUMBER function. 尝试ROW_NUMBER函数。 It guarantees that you'll only get one of each item as defined in the PARTITION BY clause. 它保证您只会得到PARTITION BY子句中定义的每一项。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM