Объедините таблицы с помощью имен проектов: если имя проекта соответствует определенным критериям, замените его другим полем - PullRequest
0 голосов
/ 06 ноября 2019

Мне нужно объединить две таблицы по именам их проектов. Но для нескольких имен проектов, которые соответствуют определенным критериям, мне нужно, чтобы объединение соответствовало их описаниям (описание работы похоже на имя и уникально). Я не уверен на 100%, как это сделать. Можно ли применить выражение case? Я предоставил то, что у меня есть, но оно не соединяется должным образом, когда я делаю выражение case для имен, подобных BTG –.

SELECT
       [Name] AS 'NAME'
      ,[DATA_Id] AS 'ID_FIELD'
      ,format([ApprovedOn], 'MM/dd/yyyy') as 'DATE_APPROVED'
      ,[DATA_PROJECT_NAME]
         ,[PHASE_NAME]
          ,[DATA_JOB_ID]
      ,[JOB_TYPE]
      ,[SUB_TYPE]
      ,format([CREATED_DATE], 'MM/dd/yyyy') as 'DATE_CREATED'
,CASE 
        WHEN [DATA_JOB_ID] = [DATA_Id] THEN 'OK'
        WHEN [DATA_JOB_ID] != [DATA_Id] THEN 'NEED DATA NUMBER'
        ELSE 'NEED DATA NUMBER'
        END AS ACTION_SPECIALISTS
      ,DATA_PROJECTS

  FROM [MI].[MI_B_View].[app_build]
  LEFT JOIN 
  (SELECT * , 
    CASE
    WHEN [DATA_PROJECT_NAME] LIKE 'BTG -%' THEN [JOB_DESCRIPTION]
    ELSE [DATA_PROJECT_NAME]
    END AS DATA_PROJECTS
  FROM [ExternalUser].[DATA].[JOB] WHERE [JOB_DESCRIPTION] LIKE '%ROW%' AND REGION = 'CITY') AS B

  ON  [Name] = [DATA_PROJECTS]

  WHERE 
      REGION_ID = 1
  AND APPROVED = 1

  ORDER BY [ApprovedOn] DESC

Ответы [ 2 ]

0 голосов
/ 07 ноября 2019

TL;DR : ответ Caius Jard является правильным - вы можете присоединиться к чему угодно, если оно имеет значение true или false (игнорируя неизвестное).

К сожалению, способ объединения двух таблиц может иметьрезко отличается производительность в зависимости от вашей методологии. Если вы присоединитесь к выражению, вы, как правило, получите очень низкую производительность. Использование вычисляемых столбцов, материализация промежуточного результата в таблице или разбиение ваших условий объединения могут помочь с низкой производительностью.

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


Когда я сравниваю два метода объединения (они функционально эквивалентны, несмотря на новый магический столбец; большеоб этом позже)

SELECT *
  FROM #Build AppBuild
    LEFT OUTER JOIN #Job Job
      ON ( AppBuild.Name = Job.DATA_PROJECT_NAME
           AND Job.DATA_PROJECT_NAME NOT LIKE 'BTG -%' )
        OR ( Job.DATA_PROJECT_NAME LIKE 'BTG -%'
             AND Job.JOB_DESCRIPTION = AppBuild.Name );

SELECT *
  FROM #Build AppBuild
    LEFT OUTER JOIN #Job Job
      ON AppBuild.Name = Job.JoinOnMe;

Результирующие планы запросов имеют огромные различия:

join on LIKE vs computed column

Вы заметите, что ориентировочная стоимостьпервого соединения намного выше - но это даже не рассказывает всей истории. Если я на самом деле выполняю эти два запроса с ~ 6M строками в каждой таблице, я получаю второй, заканчивающий ~ 7 секунд, а первый почти не выполняется через 2 минуты. Это связано с тем, что предикат объединения был помещен в таблицу сканирования таблицы #Job:

Predicate Pushdown

SQL Server не знает, какой процент записей будет иметьDATA_PROJECT_NAME (NOT) LIKE 'BTG -%', поэтому он выбирает оценку в 1 строку. Затем это приводит к выбору соединения с вложенным циклом, а также к сортировке и катушке, которые все в конечном итоге приводят к тому, что все работает довольно плохо для нас, когда мы получаем намного больше, чем 1 строку из этого сканирования таблицы.

Исправление? Вычисляемые столбцы. Я создал свои таблицы следующим образом:

CREATE TABLE #Build
(
  Name varchar(50) COLLATE DATABASE_DEFAULT NOT NULL
);

CREATE TABLE #Job
(
  JOB_DESCRIPTION   varchar(50) COLLATE DATABASE_DEFAULT NOT NULL,
  DATA_PROJECT_NAME varchar(50) COLLATE DATABASE_DEFAULT NOT NULL,
  JoinOnMe          AS CASE WHEN DATA_PROJECT_NAME LIKE N'BTG -%' THEN DATA_PROJECT_NAME
                            ELSE JOB_DESCRIPTION END
);

Оказывается, SQL Server будет вести статистику по JoinOnMe, даже если внутри него есть выражение, и это значение нигде не материализовалось. Если бы вы захотели, вы могли бы даже индексировать вычисляемый столбец.

Поскольку у нас есть статистика по JoinOnMe, объединение по ней даст хорошую оценку количества элементов (когда я проверял это было точно), и, таким образом,хороший план.

Если у вас нет свободы изменять таблицу, то вы должны хотя бы разделить объединение на два объединения. Это может показаться нелогичным, но если вы объединяете множество условий для внешнего объединения, SQL Server обычно получает более точную оценку (и, следовательно, лучшие планы), если каждое условие OR является отдельным, и вызатем COALESCE набор результатов.

Когда я включаю запрос, подобный этому:

SELECT AppBuild.Name,
       COALESCE( Job.JOB_DESCRIPTION, Job2.JOB_DESCRIPTION ) JOB_DESCRIPTION,
       COALESCE( Job.DATA_PROJECT_NAME, Job2.DATA_PROJECT_NAME ) DATA_PROJECT_NAME
  FROM #Build AppBuild
    LEFT OUTER JOIN #Job Job
      ON ( AppBuild.Name = Job.DATA_PROJECT_NAME
           AND Job.DATA_PROJECT_NAME NOT LIKE 'BTG -%' )
    LEFT OUTER JOIN #Job Job2
      ON ( Job2.DATA_PROJECT_NAME LIKE 'BTG -%'
           AND Job2.JOB_DESCRIPTION = AppBuild.Name );

Это также 0% от общей стоимости относительно первого запроса. При сравнении с объединением в вычисляемом столбце разница составляет около 58% / 42%

One vs two joins


Вот как я создал таблицы инаполнил их данными испытаний

DROP TABLE IF EXISTS #Build;
DROP TABLE IF EXISTS #Job;
CREATE TABLE #Build
(
  Name varchar(50) COLLATE DATABASE_DEFAULT NOT NULL
);

CREATE TABLE #Job
(
  JOB_DESCRIPTION   varchar(50) COLLATE DATABASE_DEFAULT NOT NULL,
  DATA_PROJECT_NAME varchar(50) COLLATE DATABASE_DEFAULT NOT NULL,
  JoinOnMe          AS CASE WHEN DATA_PROJECT_NAME LIKE N'BTG -%' THEN DATA_PROJECT_NAME
                            ELSE JOB_DESCRIPTION END
);

INSERT INTO #Build
( Name )
  SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ))
    FROM master.dbo.spt_values
      CROSS APPLY master.dbo.spt_values SV2;

INSERT INTO #Job
( JOB_DESCRIPTION, DATA_PROJECT_NAME )
  SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )),
         CASE WHEN ROUND( RAND(), 0 ) = 1 THEN CAST(ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )) AS nvarchar(20))
              ELSE 'BTG -1' END
    FROM master.dbo.spt_values SV
      CROSS APPLY master.dbo.spt_values SV2;
0 голосов
/ 06 ноября 2019

Конечно, любое выражение, которое соответствует истине, может быть использовано в объединении:

SELECT * 
FROM 
  person
  INNER JOIN
  country 
  ON
    country.name =
    CASE person.homeCity
    WHEN 'London' THEN 'England'
    WHEN 'Amsterdam' THEN 'Holland'
    ELSE person.homecountry
    END

Предположим, что в домашней стране есть такие записи, как «Великобритания», «Великобритания» и «Нидерланды», но они нене совпадают с названиями наших стран в таблице стран - мы могли бы использовать случай, когда их нужно преобразовать (и я указал на название города только для демонстрации того, что это не должно иметь ничего общего со страной)в случае), но для всех остальных (случай ELSE) мы просто пропускаем название страны из таблицы person без изменений

В конечном итоге CASE WHEN выведет некоторую строку, и это будет сопоставлено с другой таблицейстолбец (но это может быть сопоставлено с другим случаем, когда и т. д.)

В вашем сценарии вы можете обнаружить, что можете избежать всего этого и просто написать что-то, используя AND и OR, например

a JOIN b 
ON
  (a.projectname like 'abc%' and a.projectname = b.description) OR
  (a.projectname like '%def' and a.whatever = b.othercolumn)

Оценкив случае, когда короткое замыкание, оценка слева направо

Помните;все, что в конечном итоге приводит к истине, может быть использовано в ON. Четный ON 5<10 действителен (объединяет все строки со всеми другими строками, потому что это всегда верно)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...