在设计糟糕的架构中进行复杂查询的替代方法

CJM*_*CJM 5 t-sql sql-server optimization

我正在努力将来自第三方系统的一些数据集成到我的一个应用程序(传统的基于ASP Classic的Web应用程序/ SQL 2000)中 - 当涉及到他们的方法和数据结构时,他们做出了一些糟糕的决定(恕我直言)虽然也许我们可能在某个时候有机会重构......但在那之前,我必须与我面前的事情一起工作.

主表保存检查数据,其中一个字段用于记录是否观察到某些特征.这些特性存储在一个名为的表中Categories,但不幸的是,主检查表(Test)通过将相关的CategoryIDs 连接到一个字段(SelectedCategories)来链接到类别.因此,例如,如果观察到特征01和02,则SelectedCategories该行的列Test将具有值"01C02C".

修剪DDL:

CREATE TABLE [dbo].[Test](
[ItemID] [varchar](255) NOT NULL,
[Result] [varchar](255) NULL,
[Comments] [varchar](255) NULL,
[ResultReason] [varchar](255) NULL,
[ImageLocation] [varchar](255) NULL,
[TestDateTime] [smalldatetime] NOT NULL,
[SelectedCategories] [varchar](255) NULL)
Run Code Online (Sandbox Code Playgroud)

问题是,鉴于这种情况,我如何才能最好地从Test观察到的特征细分中提取数据?

我想要的客户端上的输出是一个包含以下列的表:Test.PK,Test.Field2 ... Test.Fieldn,Categories.ID1,Categories.ID2,Categories.IDn

这可能不够清楚 - 第一个字段将是通常的嫌疑人Test,然后是每个类别的刻度线或交叉(或其他视觉指示符)Categories.

显然,如果这可以在一个查询中实现,那么在效率和性能方面就更好了.但是,我不确定如何实现这一目标 - 你将如何Categories通过该表加入SelectedCategories

我显然可以简单地报告该SelectedCategories值并让应用程序解析该值.这可能是硬编码的,或者更有可能我们会重新查询CategoriesTest中的每一行 - 尽管这会产生性能影响.TBH,在这种情况下表现可能不是问题,但仅仅因为你可以逃避某些事情,并不意味着你应该养成它的习惯.

同样,如果我有机会重构第三方应用程序,我会删除SelectedCategories列并添加到TestCategories表中吗?或者我会将每个类别硬编码为一系列Bit列.很可能,在Categories系统的整个生命周期中都不会改变,但是如果它们这样做,则意味着DB和应用程序都会发生变化(尽管很小).

我希望我已经足够清楚地解释了它.从本质上讲,我说如果我坚持使用当前的系统,最好的方法是什么?如果我要重构,我可以采取什么不同的方法?

进度更新:

非常感谢列文,我到目前为止:

DML:

SELECT  c.ID, c.Category, t.FilterID, t.OperatorResult, t.SelectedCategories
FROM    dbo.Categories c
        inner JOIN dbo.Test t ON CHARINDEX(Cast(c.ID as varchar), t.SelectedCategories, 1) <> 0
order by FilterID, ID
Run Code Online (Sandbox Code Playgroud)

输出:

ID   Category             FilterID   OperatorResult   SelectedCategories
4    Cracked Ceramic      137667     FAILED           04C
4    Cracked Ceramic      284821     FAILED           04C
4    Cracked Ceramic      287617     FAILED           04C05C
5    Damaged Case         287617     FAILED           04C05C
4    Cracked Ceramic      310112     FAILED           04C05C
5    Damaged Case         310112     FAILED           04C05C
Run Code Online (Sandbox Code Playgroud)

这就足够了,只是为了达到我想要的屏幕输出......

Filter ID  Operator Result    Cat Matl   Crack    Damage   High Soot   
137667     FAILED             X          X        
178643     FAILED           
284821     FAILED                        X        
287617     FAILED                                 X        X     
310112     FAILED                        X        X    
Run Code Online (Sandbox Code Playgroud)

...我要么需要进一步研究SQL(这样我只需要在一个查询中实现所需的输出),或者我需要在应用程序本身做一些额外的工作.

结论:

如果我们看看Lieven的最新示例(下面),我们可以看到问题可以在TSQL中解决,但类别是硬编码的.

替代方案是坚持原始数据并使IIS/ASP做更多工作.它会使源代码复杂化,但如果添加或删除类别,将消除更新TSQL的潜在开销.我当然可以忍受更新TSQL的这种非常偶然的需求,但我正在考虑类别表将定期主动更改的不同问题.

Lie*_*ers 3

Categories通过以下方式加入表SelectedCategories可以这样工作

编辑

一些需要考虑的事情

  • 虽然交叉表功能可以通过 group by 和 all 实现,但也许最好在客户端应用程序中处理此问题。
  • 您在问题中提供的输入与您提供的输出不匹配。我已经使用了您的输入并假设了有关所请求输出的一些内容。我认为Cat MatlHigh Soot只是类别的其他可能值。

让我们知道它是否对您有用。

BEGIN TRAN

CREATE TABLE [dbo].[Categories](
[CategorieID] INTEGER NOT NULL)

CREATE TABLE [dbo].[Test](
[ItemID] [varchar](255) NOT NULL,
[Result] [varchar](255) NULL,
[Comments] [varchar](255) NULL,
[ResultReason] [varchar](255) NULL,
[ImageLocation] [varchar](255) NULL,
[TestDateTime] [smalldatetime] NOT NULL,
[SelectedCategories] [varchar](255) NULL)

INSERT INTO dbo.Categories VALUES (4)
INSERT INTO dbo.Categories VALUES (5)


INSERT INTO dbo.Test VALUES (137667, 'FAILED', NULL, 'Cracked Ceramic', NULL, GetDate(), '04C')
INSERT INTO dbo.Test VALUES (284821, 'FAILED', NULL, 'Cracked Ceramic', NULL, GetDate(), '04C')
INSERT INTO dbo.Test VALUES (287617, 'FAILED', NULL, 'Cracked Ceramic', NULL, GetDate(), '04C05C')
INSERT INTO dbo.Test VALUES (287617, 'FAILED', NULL, 'Damaged Case'   , NULL, GetDate(), '04C05C')
INSERT INTO dbo.Test VALUES (310112, 'FAILED', NULL, 'Cracked Ceramic', NULL, GetDate(), '04C05C')
INSERT INTO dbo.Test VALUES (310112, 'FAILED', NULL, 'Damaged Case'   , NULL, GetDate(), '04C05C')

SELECT  [Filter ID] = t.ItemID
        , [Operator Result] = t.Result
        , [Reason] = t.ResultReason
INTO    #Output
FROM    dbo.Categories c                  
        LEFT OUTER JOIN dbo.Test t ON 
          /* Search for "C<{00}CategorieID>C" */
          CHARINDEX('C'                                                       -- Prefix CategorieID & SelectedCategories with 'C'
                      + REPLICATE('0', 2 - LEN(CAST(CategorieID AS VARCHAR))) -- Left Pad CategorieID with '0'
                      + CAST(CategorieID AS VARCHAR)                          -- Add CategorieID itself
                      + 'C'                                                   -- Suffix search string with 'C'.
                    , 'C' + t.SelectedCategories                              -- Prefix CategorieID & SelectedCategories with 'C'
                    , 1) <> 0

SELECT    [Filter ID]
          , [Operator Result]
          , [Cat Matl] = CASE WHEN [Cat Matl] = 1 THEN 'X' ELSE '' END
          , [Crack] = CASE WHEN [Crack] = 1 THEN 'X' ELSE '' END
          , [Damage] = CASE WHEN [Damage] = 1 THEN 'X' ELSE '' END
          , [High Soot] = CASE WHEN [High Soot] = 1 THEN 'X' ELSE '' END
FROM      (
            SELECT    [Filter ID]
                      , [Operator Result]
                      , [Cat Matl] = MAX(CASE WHEN Reason = 'Cat Matl' THEN 1 ELSE 0 END) 
                      , [Crack] = MAX(CASE WHEN Reason = 'Cracked Ceramic' THEN 1 ELSE 0 END) 
                      , [Damage] = MAX(CASE WHEN Reason = 'Damaged Case' THEN 1 ELSE 0 END) 
                      , [High Soot] = MAX(CASE WHEN Reason = 'High Soot' THEN 1 ELSE 0 END) 
            FROM      #Output
            GROUP BY  [Filter ID]
                      , [Operator Result]
          ) o

DROP TABLE #Output

ROLLBACK TRAN
Run Code Online (Sandbox Code Playgroud)