在没有正确链接ID的情况下联接两个表

时间:2019-04-02 14:26:37

标签: sql sql-server join

SQL FIDDLE (省略astAssets以降低复杂性)

我需要获取资产的某些维护任务的列表,以及它们各自的子任务和备件数量。

任务链接到资产,子任务和备件都在单独的表中,这些表链接到具有ID的任务。

因此,以下两个查询将分别获取所有资产的所有子任务和任务

Select T.Code, ST.Description From astTasks T
    Join astTaskSubTasks ST ON ST.ParentId = T.Id
    Join astAssets A ON A.Id = T.AssetId

Select T.Code, S.StockItemId From astTasks T
    Join astTaskSpares S ON S.ParentId = T.Id
    Join astAssets A ON A.Id = T.AssetId

子任务记录的描述包含一个库存项目代码。备用记录通过StockItemId链接到库存代码。

我想获取资产中所有任务的列表,以及这些任务的所有子任务和备用零件。子任务和备件经常(但不总是)引用相同的库存物料。例如,任务A拥有一个子任务,该子任务显示库存项目0990每12个月将被更换,因此该项目的备件清单中已列出库存项目0990

问题在于子任务和备用任务之间没有链接,即使它们有时是明确相关的(如上例所示)。

使事情更复杂:

  • 安全库存任务可保留备用任务,但不包含子任务(无需执行任何操作)
  • 某些子任务只需要目视检查一个库存项目,因此不存在相应的备用零件
  • 由于用户错误,可能会丢失备用件
  • 子任务和备用任务都不存在,这是另一个用户错误

即使子任务,备件或两者都不存在,我也想列出库存项目代码,任务,子任务描述和备件数量。以下查询不起作用,因为它不能将备件链接到子任务。结果,一个库存项目可能针对同一任务以不同数量多次列出,因为它从该任务中所有备件中获取数量。结果,您可能会错误地为一个库存项目获得4条记录。

Select Distinct
 CASE WHEN ST.Description IS NULL THEN SI.Code ELSE LTRIM(SUBSTRING(ST.Description, CHARINDEX('x ' , Substring(ST.Description, PATINDEX('%(%[^A-Z]% x %', ST.Description),50) ) +  PATINDEX('%(%[^A-Z]% x %', ST.Description)+1, (CHARINDEX(') - (', ST.Description) - (CHARINDEX('x ' , Substring(ST.Description, PATINDEX('%(%[^A-Z]% x %', ST.Description),50) ) +  PATINDEX('%(%[^A-Z]% x %', ST.Description)))-1)) END
, T.Code
, Left(ST.Description, CHARINDEX(' ',ST.Description, 1))
, CASE WHEN Left(ST.Description, CHARINDEX(' ',ST.Description, 1)) = 'Check' Then 'Check' ELSE CAST(S.Quantity as nvarchar) END
From astTasks T

Join astAssets A ON A.Id = T.AssetId 

Left Join astTaskSubTasks ST ON ST.ParentId = T.Id
Left Join astTaskSpares S ON S.ParentId = T.Id
Left Join stkStockItems SI ON SI.Id = S.StockItemId

Where
A.Code = '2016404991'

下图是fiddle的屏幕抓图,并显示了问题。顶层表是各自任务(行1)中所有备件(行2)和相应数量(行3)的列表。第二个表是使用上面的查询构造的。如您所知,它将显示备件数量错误的库存物料代码。它仅显示该特定任务和库存的所有可能组合。

Problem

1 个答案:

答案 0 :(得分:1)

您真的应该为sqlfiddle中给出的示例数据提供一些“预期输出”。如果我说很容易理解您的需求,那我会撒谎,但我仍然不确定我是否正确。

因此,基本上,此方法的工作方式是尝试从任务描述(subTasks CTE)中提取零件ID,然后尝试从那里匹配备用记录(SubTasksWithMatchingSpares CTE)。不能与子任务匹配的给定任务的所有剩余备件被单独列出(WithoutSubtasks CTE)。然后将两个结果集合并在一起,以提供任务->子任务->备用+任务->备用的完整列表。

;WITH subTasks AS
(

    SELECT ST.Id
         , ST.ParentId
         , Description
         , SI.Id AS StockItemId         
      FROM astTaskSubTasks ST
     -- Find the space, delimiting the verb from part code, if any
     CROSS
     APPLY (SELECT CHARINDEX(' ', ST.Description) spaceIndex) x1
     -- Extract the part code
     CROSS
     APPLY (SELECT RIGHT(ST.Description, LEN(ST.Description) - x1.spaceIndex) AS PartCode) x2
      -- Related stock item
      LEFT OUTER
      JOIN stkStockItems SI
        ON SI.Code = x2.PartCode
),
-- Match spares against the spare codes extracted from subtask description, if there is one
SubTasksWithMatchingSpares AS
(
SELECT T.Id AS TaskId
     , T.Code AS TaskCode
     , ST.Id AS SubTaskId
     , ST.Description AS SubTaskDescription
     , ST.Id
     , ST.StockItemId AS SpareId
     , TS.Quantity AS SpareQuantity
  FROM astTasks T
  LEFT OUTER
  JOIN subTasks ST 
    ON ST.ParentId = T.Id
  LEFT OUTER
  JOIN astTaskSpares TS
    ON TS.ParentId = T.Id
   AND TS.StockItemId = ST.StockItemId
),
-- Leftover task spares that were not matched against the subtask description
WithoutSubtasks AS
(
SELECT T.Id AS TaskId
     , T.Code AS TaskCode
     , SI.Code AS Code
     , TS.Quantity AS SpareQuantity
  FROM astTasks T 
  LEFT OUTER
  JOIN astTaskSpares TS
    ON TS.ParentId = T.Id
 INNER
  JOIN stkStockItems SI
    ON SI.Id = TS.StockItemId 
   -- check if the subtask successfully matched it
   AND NOT EXISTS (SELECT 1 FROM subTasks checkIfAlreadyMatched
                     WHERE checkIfAlreadyMatched.ParentId = TS.ParentId
                       AND checkIfAlreadyMatched.StockItemId = TS.StockItemId)

),
AllTogether AS
(
-- All tasks will be here, whether subtask matched or not; if it did, it also matched against the spare
SELECT ST.TaskId
     , ST.TaskCode
     , ST.SubTaskId
     , ST.SubTaskDescription
     , SI.Code
     , ST.SpareQuantity
  FROM SubTasksWithMatchingSpares ST
  LEFT OUTER
  JOIN stkStockItems SI
    ON SI.Id = ST.SpareId
 -- check to match tasks without a subtask that have been matched against a spare
 -- to avoid empty records, if such a match exists
 WHERE NOT EXISTS (SELECT 1 
                     FROM WithoutSubtasks withoutST 
                    WHERE withoutST.TaskId = ST.TaskId
                      AND SI.Code IS NULL)


UNION ALL
-- Only tasks that have a spare are here, and the spare was not matched against a subtask
SELECT ST.TaskId
     , ST.TaskCode
     , NULL
     , NULL
     , ST.Code
     , ST.SpareQuantity
  FROM WithoutSubtasks ST
 )
 SELECT * FROM AllTogether
 ORDER BY TaskId

sqlfiddle results