Как я могу улучшить этот запрос SQL? - PullRequest
4 голосов
/ 26 августа 2011

Сегодня я столкнулся с интересной проблемой SQL, и хотя я нашел решение, которое работает, я сомневаюсь, что это лучший или самый эффективный ответ. Я полагаюсь на экспертов здесь - помогите мне узнать что-то и улучшить мой запрос! СУБД - это SQL Server 2008 R2, запрос является частью отчета SSRS, который будет работать с примерно 100 000 строк.

По сути, у меня есть список идентификаторов, которые могут иметь несколько значений, связанных с ними, значения Да, Нет, или некоторые другие строки. Для идентификатора x, если любое из значений является Да, x должно быть Да, если они все Нет, это должно быть Нет, если они содержат какие-либо другие значения, кроме Да и Нет, отобразить это значение. Я хочу вернуть только 1 строку для каждого идентификатора, без дубликатов.

Упрощенная версия и контрольный пример:

DECLARE @tempTable table ( ID int, Val varchar(1) )

INSERT INTO @tempTable ( ID, Val ) VALUES ( 10, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 11, 'N')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 11, 'N')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 13, 'N')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 14, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 14, 'N')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 15, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 16, 'Y')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 17, 'F')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 18, 'P')


SELECT DISTINCT t.ID, COALESCE(t2.Val, t3.Val, t4.Val)
FROM @tempTable t
LEFT JOIN
(
    SELECT ID, Val
    FROM @tempTable
    WHERE Val = 'Y'
) t2 ON t.ID = t2.ID
LEFT JOIN
(
    SELECT 
    ID, Val FROM @tempTable
    WHERE Val = 'N'
) t3 ON t.ID = t3.ID
LEFT JOIN
(
    SELECT ID, Val
    FROM @tempTable
    WHERE Val <> 'Y' AND Val <> 'N'
) t4 ON t.ID = t4.ID

Заранее спасибо.

Ответы [ 4 ]

4 голосов
/ 26 августа 2011

Давайте ответим на более простую задачу: для каждого идентификатора получаем Val, который является последним в алфавите. Это будет работать, если Y и N являются единственными значениями. И запрос гораздо проще:

SELECT t.ID, MAX(t.Val) FROM t GROUP BY t.ID;

Итак, сведите ваш случай к простому. Используйте перечисление (если ваша БД его поддерживает) или разбейте коды значений на другую таблицу со столбцом сортировки (в этом случае у вас может быть 1 для Y, 2 для N и 999 для всех других возможных значений, и вы хотите, чтобы маленький ). Тогда

SELECT ID, c.Val FROM
     (SELECT t.ID, MIN(codes.collation) AS mx
      FROM t join codes on t.Val = codes.Val GROUP BY t.ID) AS q
JOIN codes c ON mx=c.collation;

Здесь коды имеют два столбца, Val и Collation.

Вы также можете сделать это с запросом типа CTE, если у вас есть значения, упорядоченные так, как вы хотите. Этот подход предусматривает одно соединение с небольшой справочной таблицей и должно быть намного, намного быстрее, чем 3 самостоятельных соединения.

WITH q AS (SELECT t.id, t.Val, ROW_NUMBER() AS r FROM t JOIN codes ON t.Val=codes.Val 
    PARTITION BY t.id ORDER BY codes.collation)
SELECT q.id, q.Val WHERE r=1;            
3 голосов
/ 26 августа 2011

Попробуйте это:

;WITH a AS ( 
SELECT
  ID,
  SUM(CASE Val WHEN 'Y' THEN 1 ELSE 0 END) AS y,
  SUM(CASE Val WHEN 'N' THEN 0 ELSE 1 END) AS n,
  MIN(CASE WHEN Val IN ('Y','N') THEN NULL ELSE Val END) AS first_other
FROM @tempTable
GROUP BY ID
) 
SELECT
  ID,
  CASE WHEN y > 0 THEN 'Y' WHEN n = 0 THEN 'N' ELSE first_other END AS Val
FROM a 
  • Если есть какие-либо значения 'Y', то сумма y будет больше 0
  • Если все значения равны 'N', тосумма n будет равна нулю
  • Получить первый доступный не 'Y' или 'N' символ, если необходимо
  • В этом случае результат может быть определен только с одним проходом через таблицу
3 голосов
/ 26 августа 2011

Я бы поменял его на это, чтобы было легче читать:

SELECT DISTINCT t.ID, COALESCE(t2.Val, t3.Val, t4.Val)
FROM @tempTable t
LEFT JOIN @tempTable t2 ON t.ID = t2.ID and t2.Val = 'Y'
LEFT JOIN @tempTable t3 ON t.ID = t3.ID and  t3.Val = 'N'
LEFT JOIN @tempTable t4 ON t.ID = t4.ID and t4.Val <> 'Y' AND t4.Val <> 'N'

Дает те же результаты, что и ваш пример.

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

2 голосов
/ 26 августа 2011

Я читаю вашу спецификацию так:

  1. если идентификатор - Y, то Y
  2. если все идентификаторы N, то N
  3. иначе отображаемое значение (кроме Y или N)

исключить строки за (1)

delete from @tempTable
where not Val='Y' and ID in (
    select distinct ID
    from @tempTable
    where Val='Y'
)

выберите отличные, чтобы исключить несколько N в (2).

select distinct * from @tempTable

сгруппировать различные "другие" значения, чтобы получить одну строку для каждого идентификатора.

SELECT A.Id, AllVals = 
    SubString(
        (SELECT ', ' + B.Val 
         FROM C as B 
         WHERE A.Id = B.Id 
         FOR XML PATH ( '' ) ), 3, 1000) 
FROM C as A 
GROUP BY Id

Весь выполняемый запрос:

declare @tempTable table (ID int, Val char(1))
INSERT INTO @tempTable ( ID, Val ) VALUES ( 10, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 11, 'N') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 11, 'N') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 12, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 12, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 12, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 13, 'N') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 14, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 14, 'N') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 15, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 16, 'Y') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 17, 'F') 
INSERT INTO @tempTable ( ID, Val ) VALUES ( 18, 'P')
INSERT INTO @tempTable ( ID, Val ) VALUES ( 18, 'F')
delete from @tempTable
where not Val='Y' and ID in (
    select distinct ID
    from @tempTable
    where Val='Y'
);
WITH C as (select distinct * from @tempTable)
SELECT A.Id, AllVals = 
    SubString(
        (SELECT ', ' + B.Val 
         FROM C as B 
         WHERE A.Id = B.Id 
         FOR XML PATH ( '' ) ), 3, 1000) 
FROM C as A 
GROUP BY Id

ВЫВОД:

Id  AllVals
10  Y
11  N
12  Y
13  N
14  Y
15  Y
16  Y
17  F
18  F, P
...