Альтернативные подходы к сложному запросу в плохо спроектированной схеме - PullRequest
5 голосов
/ 11 февраля 2010

Я работаю над интеграцией некоторых данных из сторонней системы в одно из моих приложений (устаревшее веб-приложение на базе ASP Classic / SQL 2000) - они приняли некоторые неверные решения (IMHO), когда дело доходит до их подхода и структура данных, хотя, возможно, в какой-то момент у нас будет шанс провести рефакторинг ... но до тех пор я должен работать с тем, что передо мной.

Основная таблица содержит данные проверки, при этом одно из полей используется для записи, если были соблюдены определенные характеристики. Характеристики хранятся в таблице с именем Categories, но, к сожалению, главная контрольная таблица (Test) связывается с категориями, объединяя соответствующие CategoryID s в одно поле (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)

Вопрос, учитывая ситуацию, как лучше всего соответствующим образом извлечь данные из Test с разбивкой наблюдаемых характеристик?

Выходные данные на клиенте, который я хочу, представляют собой таблицу со следующими столбцами: Test.PK, Test.Field2 ... Test.Fieldn, Categories.ID1, Categories.ID2, Categories.IDn

Это, вероятно, не совсем ясно - первые поля будут обычными подозреваемыми из Test, за которыми следует галочка или крестик (или какой-либо другой визуальный индикатор) для каждой из категорий в Categories.

Очевидно, что если этого можно достичь одним запросом, тем лучше с точки зрения эффективности и производительности. Однако я не уверен, как этого достичь - как бы вы присоединились к таблице Categories через SelectedCategories?

Я, очевидно, мог бы просто сообщить значение SelectedCategories и заставить приложение проанализировать значение. Это может быть жестко запрограммировано или более вероятно, что мы повторно запросим Categories для каждой строки в Test - хотя это может повлиять на производительность. TBH, производительность, вероятно, не проблема в этом случае, но только потому, что вы можете сойти с рук с чем-то, не означает, что вы должны привыкнуть к этому.

Точно так же, если бы у меня была возможность реорганизовать стороннее приложение, я бы удалил столбец SelectedCategories и добавил бы в таблицу TestCategories? Или я бы жестко закодировал каждую категорию как ряд битовых столбцов. По всей вероятности, Categories не будет меняться в течение всего срока службы системы, но если они это сделали, это означает изменения (хотя и очень незначительные) как для БД, так и для приложения.

Надеюсь, я объяснил это достаточно четко. По сути, я говорю, каков наилучший подход, если я придерживаюсь текущей системы? И если бы мне пришлось провести рефакторинг, какой другой подход я бы выбрал?

Обновление прогресса:

Во многом благодаря Ливену, я до сих пор получил:

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

Выход:

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

Этого будет достаточно, за исключением того, что для достижения желаемого результата на экране ...

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    

... Мне нужно либо продолжить работу с SQL (чтобы я мог получить желаемый результат всего одним запросом), либо мне нужно проделать дополнительную работу в самом приложении.

Вывод:

Если мы посмотрим на последний пример Ливена (ниже), то увидим, что проблему можно решить с помощью TSQL, но что категории жестко закодированы.

Альтернатива - придерживаться необработанных данных и заставить IIS / ASP выполнять больше работы. Это наверняка усложнит исходный код, но удалит потенциальные издержки обновления TSQL, если категория будет добавлена ​​или удалена. Я, конечно, мог бы справиться с этой очень редкой потребностью в обновлении TSQL, но я обдумываю другие проблемы, когда таблица категорий будет активно меняться на регулярной основе.

1 Ответ

3 голосов
/ 11 февраля 2010

Чтобы присоединиться к таблице Categories через SelectedCategories, это может работать следующим образом

Редактировать

Некоторые вещи, чтобы думать о

  • Несмотря на то, что функциональность кросс-таблицы возможна для группы «все», возможно, было бы лучше справиться с этим в клиентском приложении.
  • Входные данные, которые вы указали в своем вопросе, не совпадают с данными, предоставленными вами. Я использовал ваши данные и предположил некоторые вещи относительно запрошенного вывода. Я предполагаю, что Cat Matl и High 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...