Несколько объединений в запросе - возможна ли замена для увеличения производительности? - PullRequest
0 голосов
/ 04 сентября 2018

У меня есть таблица, состоящая из 10 миллионов строк, где я пытаюсь выяснить, кто был первым / последним сопровождающим некоторых машин (id), в зависимости от некоторых дат, а также в зависимости от состояния машины. Мой запрос использует шесть объединений, есть ли другой предпочтительный вариант? РЕДАКТИРОВАТЬ: Исходная таблица имеет индекс, пытаясь оптимизировать запрос, заменяя объединения - если это возможно? SQL Fiddle с примером:

SQL Fiddle

РЕДАКТИРОВАТЬ (добавлена ​​дополнительная информация ниже):

Пример таблицы:

CREATE TABLE vendor_info (
  id INT,
  datestamp INT,
  statuz INT,
  maintainer VARCHAR(25));

  INSERT INTO vendor_info VALUES (1, 20180101, 0, 'Jay');
  INSERT INTO vendor_info VALUES (2, 20180101, 0, 'Eric');
  INSERT INTO vendor_info VALUES (3, 20180101, 1, 'David');
  INSERT INTO vendor_info VALUES (1, 20180201, 1, 'Jay');
  INSERT INTO vendor_info VALUES (2, 20180201, 0, 'Jay');
  INSERT INTO vendor_info VALUES (3, 20180201, 1, 'Jay');
  INSERT INTO vendor_info VALUES (1, 20180301, 1, 'Jay');
  INSERT INTO vendor_info VALUES (2, 20180301, 1, 'David');
  INSERT INTO vendor_info VALUES (3, 20180301, 1, 'Eric');

Запрос и желаемый вывод:

SELECT
   id
  , MIN(datestamp) AS min_datestamp
  , MAX(datestamp) AS max_datestamp
  , MAX(case when statuz = 0 then datestamp end) AS max_s0_date
  , MAX(case when statuz = 1 then datestamp end) AS max_s1_date
  , MIN(case when statuz = 0 then datestamp end) AS min_s0_date
  , MIN(case when statuz = 1 then datestamp end) AS min_s1_date
 INTO vendor_dates
 FROM vendor_info
 GROUP BY id;

SELECT 
    vd.id
  , v1.maintainer  AS first_maintainer
  , v2.maintainer AS last_maintainer 
  , v3.maintainer AS last_s0_maintainer
  , v4.maintainer AS last_s1_maintainer
  , v5.maintainer AS first_s0_maintainer
  , v6.maintainer AS first_s1_maintainer

FROM vendor_dates vd
LEFT JOIN vendor_info v1 ON vd.id = v1.id AND vd.min_datestamp = v1.datestamp
LEFT JOIN vendor_info v2 ON vd.id = v2.id AND vd.max_datestamp = v2.datestamp
LEFT JOIN vendor_info v3 ON vd.id = v3.id AND vd.max_s0_date = v3.datestamp
LEFT JOIN vendor_info v4 ON vd.id = v4.id AND vd.max_s1_date = v4.datestamp
LEFT JOIN vendor_info v5 ON vd.id = v5.id AND vd.min_s0_date = v5.datestamp
LEFT JOIN vendor_info v6 ON vd.id = v6.id AND vd.min_s1_date = v6.datestamp;

Ответы [ 5 ]

0 голосов
/ 04 сентября 2018

Также возможно использовать UNPIVOT / JOIN / PIVOT:

WITH
  a AS (
    SELECT
      id, statuz,
      MIN(datestamp) AS fzm, MAX(datestamp) AS lzm,
      MIN(MIN(datestamp)) OVER(PARTITION BY id) AS fm,
      MAX(MAX(datestamp)) OVER(PARTITION BY id) AS lm
    FROM vendor_info
    GROUP BY id, statuz
  ),
  b AS (
    SELECT
      v.id,
      up.[type] + IIF(up.[type] IN('fm', 'lm'), '', STR(up.statuz, 1)) AS p,
      v.maintainer
    FROM a
    UNPIVOT(datestamp FOR [type] IN(fm, lm, fzm, lzm)) AS up
    JOIN vendor_info v
      ON up.id = v.id AND up.datestamp = v.datestamp
  )
SELECT
  id,
  fm AS first_maintainer, lm AS last_maintainer,
  lzm0 AS last_s0_maintainer, lzm1 AS last_s1_maintainer,
  fzm0 AS fzmfirst_s0_maintainer, fzm1 AS first_s1_maintainer
FROM b
PIVOT(MIN(maintainer) FOR p IN(fm, lm, lzm0, lzm1, fzm0, fzm1)) AS p;

Может быть протестировано на SQL Fiddle .

0 голосов
/ 04 сентября 2018

Я бы пошел с ответом Андрея Одегова.

Идеальным решением была бы функция агрегации, которая дает вам имя для максимальной или минимальной даты, как у Oracle KEEP FIRST/LAST. В SQL Server такой функции нет, поэтому использование оконных функций, показанных Андреем Одеговым, кажется лучшим решением.

Если это все еще слишком медленно, возможно, стоит попытаться объединить дни и имена и найти MIN/MAX из них (например, '20180101Eric' <<code>'20180201Jay'), а затем извлечь имена. Много манипуляций со строками, но простая агрегация, и вы все равно должны прочитать всю таблицу.

WITH vi AS
(
  SELECT
    id,
    statuz,
    CONVERT(VARCHAR, datestamp) + maintainer AS date_and_name
  FROM vendor_info
)
SELECT
   id
  , SUBSTRING(MIN(date_and_name), 9, 100) AS first_maintainer
  , SUBSTRING(MAX(date_and_name), 9, 100) AS last_maintainer
  , SUBSTRING(MAX(case when statuz = 0 then date_and_name end), 9, 100) AS last_s0_maintainer
  , SUBSTRING(MAX(case when statuz = 1 then date_and_name end), 9, 100) AS last_s1_maintainer
  , SUBSTRING(MIN(case when statuz = 0 then date_and_name end), 9, 100) AS first_s0_maintainer
  , SUBSTRING(MIN(case when statuz = 1 then date_and_name end), 9, 100) AS first_s1_maintainer
FROM vi
GROUP BY id
ORDER BY id;

(Если вы храните даты как даты, а не как целые числа, как показано в скрипте SQL, вам придется изменить CONVERT и, возможно, SUBSTRING соответственно.)

SQL-скрипта: http://sqlfiddle.com/#!18/9ee2c7/46

0 голосов
/ 04 сентября 2018

У меня еще не было времени, чтобы сгенерировать тестовые записи 10 млн., Но попробуйте это с индексом Id, datetamp - у меня есть на это надежда - план выполнения выглядел хорошо - отредактируйте - с 50-миллионными записями, которые я сгенерировал, это выглядело довольно быстро, пока существует индекс (id, datetamp) (или другой подходящий индекс).

SELECT tID.id, V1.first_maintainer, V2.last_maintainer, V3.last_s0_maintainer, V4.last_s1_maintainer, V5.first_s0_maintainer, V6.first_s1_maintainer
        FROM (SELECT DISTINCT ID from vendor_info) tID 
            OUTER APPLY 
                    (SELECT TOP 1 vi1.maintainer first_maintainer 
                                FROM vendor_info vi1 
                                        WHERE vi1.id = tID.id 
                                            ORDER BY vi1.datestamp ASC) V1
            OUTER APPLY 
                    (SELECT TOP 1 vi2.maintainer last_maintainer  
                                FROM vendor_info vi2 
                                        WHERE vi2.id = tID.id 
                                            ORDER BY vi2.datestamp DESC) V2
            OUTER APPLY 
                    (SELECT TOP 1 vi3.maintainer last_s0_maintainer  
                                FROM vendor_info vi3 
                                        WHERE vi3.statuz = 0 AND vi3.id = tID.id 
                                            ORDER BY vi3.datestamp DESC) V3
            OUTER APPLY 
                    (SELECT TOP 1 vi4.maintainer last_s1_maintainer  
                                FROM vendor_info vi4 
                                        WHERE vi4.statuz = 1 AND vi4.id = tID.id 
                                            ORDER BY vi4.datestamp DESC) V4
            OUTER APPLY 
                    (SELECT TOP 1 vi5.maintainer first_s0_maintainer  
                                FROM vendor_info vi5 
                                        WHERE vi5.statuz = 0 AND vi5.id = tID.id 
                                            ORDER BY vi5.datestamp ASC) V5
            OUTER APPLY 
                    (SELECT TOP 1 vi6.maintainer first_s1_maintainer  
                                FROM vendor_info vi6 
                                        WHERE vi6.statuz = 1 AND vi6.id = tID.id 
                                            ORDER BY vi6.datestamp ASC) V6
0 голосов
/ 04 сентября 2018

Проверьте следующий запрос.

WITH
  a AS (
    SELECT
      id, datestamp, maintainer, statuz,
      MIN(datestamp) OVER(PARTITION BY id) AS fm,
      MAX(datestamp) OVER(PARTITION BY id) AS lm,
      MIN(datestamp) OVER(PARTITION BY id, statuz) AS fZm,
      MAX(datestamp) OVER(PARTITION BY id, statuz) AS lZm
    FROM vendor_info
  )
SELECT
  id,
  MIN(IIF(datestamp = fm, maintainer, NULL)) AS  first_maintainer,
  MAX(IIF(datestamp = lm, maintainer, NULL)) AS last_maintainer,
  MAX(IIF(datestamp = lZm AND statuz = 0, maintainer, NULL)) AS last_s0_maintainer,
  MAX(IIF(datestamp = lZm AND statuz = 1, maintainer, NULL)) AS last_s1_maintainer,
  MIN(IIF(datestamp = fZm AND statuz = 0, maintainer, NULL)) AS first_s0_maintainer,
  MIN(IIF(datestamp = fZm AND statuz = 1, maintainer, NULL)) AS first_s1_maintainer
FROM a
GROUP BY id;

Может быть протестировано на SQL Fiddle .

0 голосов
/ 04 сентября 2018

Добавление индекса к vendor_info уменьшает длительность вашего второго запроса с более 300 мс до 30 мс в среднем при повторных запусках

ПЕРВИЧНЫЙ КЛЮЧ, КЛАСТЕРНЫЙ (id, метка даты)

Изменение двухэтапного процесса в CTE сокращает общую продолжительность еще больше до среднего значения менее 15 мс при повторных прогонах.

Метод CTE позволяет оптимизатору запросов использовать новый первичный ключ

CREATE TABLE vendor_info (
  id INT,
  datestamp INT,
  statuz INT,
  maintainer VARCHAR(25)

  PRIMARY KEY CLUSTERED (id, datestamp)
);

  INSERT INTO vendor_info VALUES (1, 20180101, 0, 'Jay');
  INSERT INTO vendor_info VALUES (2, 20180101, 0, 'Eric');
  INSERT INTO vendor_info VALUES (3, 20180101, 1, 'David');
  INSERT INTO vendor_info VALUES (1, 20180201, 1, 'Jay');
  INSERT INTO vendor_info VALUES (2, 20180201, 0, 'Jay');
  INSERT INTO vendor_info VALUES (3, 20180201, 1, 'Jay');
  INSERT INTO vendor_info VALUES (1, 20180301, 1, 'Jay');
  INSERT INTO vendor_info VALUES (2, 20180301, 1, 'David');
  INSERT INTO vendor_info VALUES (3, 20180301, 1, 'Eric');

WITH vendor_dates AS
(SELECT
   id
  , MIN(datestamp) AS min_datestamp
  , MAX(datestamp) AS max_datestamp
  , MAX(case when statuz = 0 then datestamp end) AS max_s0_date
  , MAX(case when statuz = 1 then datestamp end) AS max_s1_date
  , MIN(case when statuz = 0 then datestamp end) AS min_s0_date
  , MIN(case when statuz = 1 then datestamp end) AS min_s1_date
 FROM vendor_info
 GROUP BY id
 )
 SELECT 
    vd.id
  , v1.maintainer  AS first_maintainer
  , v2.maintainer AS last_maintainer 
  , v3.maintainer AS last_s0_maintainer
  , v4.maintainer AS last_s1_maintainer
  , v5.maintainer AS first_s0_maintainer
  , v6.maintainer AS first_s1_maintainer

FROM vendor_dates vd
LEFT JOIN vendor_info v1 ON vd.id = v1.id AND vd.min_datestamp = v1.datestamp
LEFT JOIN vendor_info v2 ON vd.id = v2.id AND vd.max_datestamp = v2.datestamp
LEFT JOIN vendor_info v3 ON vd.id = v3.id AND vd.max_s0_date = v3.datestamp
LEFT JOIN vendor_info v4 ON vd.id = v4.id AND vd.max_s1_date = v4.datestamp
LEFT JOIN vendor_info v5 ON vd.id = v5.id AND vd.min_s0_date = v5.datestamp
LEFT JOIN vendor_info v6 ON vd.id = v6.id AND vd.min_s1_date = v6.datestamp;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...