Получить последние даты из нескольких столбцов


18

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

DROP TABLE #indebtedness
CREATE TABLE #indebtedness (call_case CHAR(10), date1 DATETIME, date2 DATETIME, date3 DATETIME)
INSERT #indebtedness VALUES ('Key1', '2019-10-30', '2019-11-30', '2019-10-25')
INSERT #indebtedness VALUES ('Key2', '2019-10-20', '2019-10-30', '2019-10-15')
INSERT #indebtedness VALUES ('Key3', '2019-11-11', '2019-10-29', '2019-10-30')
INSERT #indebtedness VALUES ('Key4',     null    , '2019-10-29', '2019-10-13')

select call_case, ?? AS 'Latest Date' from #indebtedness 

Я хотел бы, чтобы результат был:

call_case   Latest Date
Key1        2019-11-30 
Key2        2019-10-30 
Key3        2019-11-11 
Key4        2019-10-29 

Ответы:


20

Используйте CASEвыражение:

SELECT
    call_case,
    CASE WHEN date1 > date2 AND date1 > date3
         THEN date1
         WHEN date2 > date3
         THEN date2
         ELSE date3 END AS [Latest Date]
FROM #indebtedness;

демонстрация

Обратите внимание, что некоторые базы данных, такие как MySQL, SQL Server и SQLite, поддерживают скалярную наибольшую функцию. SQL Server нет, поэтому мы можем использовать CASEвыражение в качестве обходного пути.

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

Похоже, что в вашей фактической таблице один или несколько столбцов даты могут иметь NULLзначения. Мы можем адаптировать вышеуказанный запрос следующим образом:

SELECT
    call_case,
    CASE WHEN (date1 > date2 OR date2 IS NULL) AND (date1 > date3 OR date3 IS NULL)
         THEN date1
         WHEN date2 > date3 OR date3 IS NULL
         THEN date2
         ELSE date3 END AS [Latest Date]
FROM #indebtedness;

демонстрация


он не работает, он получает дату3, только не получает последнюю дату в 3 столбцах
Ахмед Алхтеб,

1
@AhmedAlkhteeb Я отредактировал свой ответ, чтобы также обработать случай, когда может быть один или несколько столбцов даты NULL.
Тим Бигелейзен

3
Тогда многие ответы, приведенные здесь, сломаются и не сработают. Честно говоря, если вам нужно провести это сравнение даже по четырем столбцам, вы можете переосмыслить дизайн таблицы базы данных и вместо этого получить каждое значение даты в отдельной строке . Ваше требование было бы тривиальным, если бы у вас была каждая дата в отдельной строке, потому что тогда мы могли бы просто взять MAXиспользование GROUP BY. Поэтому мой ответ на ваш вопрос «не исправит», потому что я думаю, что, возможно, ваш дизайн базы данных должен измениться.
Тим Бигелейзен

1
Тим прямо здесь, @AhmedAlkhteeb, если у вас есть десятки столбцов даты, вы, вероятно, денормализованы данные. Пара в одной строке - это хорошо, это означает разные вещи (скажем, начало и конец, а также дату рождения и дату, когда человек был добавлен в систему), но многие даты (10 из них) предполагают, что вы добавление новой даты в столбец каждый раз, когда что-то меняется; не вставлять новую строку для ведения истории. Например, если бы это была база данных компании службы доставки, в ней не было бы столбца даты для каждого возможного шага поездки; Вы вставили бы новую строку для каждого.
Ларну

1
@AhmedAlkhteeb в этом случае Ларну верен - у вас должна быть таблица с действием ( call_case) и отметка времени. Ни одна таблица с 50 столбцами
Dannnno

13

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

SELECT @@VERSION

Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64) 
Mar 18 2018 09:11:49 
Copyright (c) Microsoft Corporation
Developer Edition (64-bit) on Windows 10 Enterprise 10.0 <X64> (Build 17763: )

Вот как я все настроил

DECLARE @Offset bigint = 0;
DECLARE @Max bigint = 10000000;

DROP TABLE IF EXISTS #Indebtedness;
CREATE TABLE #Indebtedness
(
  call_case char(10) COLLATE DATABASE_DEFAULT NOT NULL,
  date1     datetime NULL,
  date2     datetime NULL,
  date3     datetime NULL
);

WHILE @Offset < @Max
BEGIN

  INSERT INTO #Indebtedness
  ( call_case, date1, date2, date3 )
    SELECT @Offset + ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )),
           DATEADD( DAY,
                    CASE WHEN RAND() > 0 THEN 1
                         ELSE -1 END * ROUND( RAND(), 0 ),
                    CURRENT_TIMESTAMP ),
           DATEADD( DAY,
                    CASE WHEN RAND() > 0 THEN 1
                         ELSE -1 END * ROUND( RAND(), 0 ),
                    CURRENT_TIMESTAMP ),
           DATEADD( DAY,
                    CASE WHEN RAND() > 0 THEN 1
                         ELSE -1 END * ROUND( RAND(), 0 ),
                    CURRENT_TIMESTAMP )
      FROM master.dbo.spt_values a
        CROSS APPLY master.dbo.spt_values b;


  SET @Offset = @Offset + ROWCOUNT_BIG();
END;

В моей системе это дает мне 12 872 738 строк в таблице. Если я попытаюсь SELECT INTOвыполнить каждый из указанных выше запросов (настроенный таким образом, чтобы мне не нужно было ждать, пока он завершит печать результатов в SSMS), я получу следующие результаты:

Method                                | CPU time (ms) | Elapsed time (ms) | Relative Cost
-----------------------------------------------------------------------------------------
Tim Biegeleisen (CASE)                | 13485         | 2167              | 2%
Red Devil (Subquery over MAX columns) | 55187         | 9891              | 14%
Vignesh Kumar (Subquery over columns) | 33750         | 5139              | 5%
Serkan Arslan (UNPIVOT)               | 86205         | 15023             | 12%
Metal (STRING_SPLIT)                  | 459668        | 186742            | 68%

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

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


Возник вопрос, что делать, если вам нужно продолжать добавлять новые столбцы и расширять оператор case. Да, это становится громоздким, как и любое другое решение. Если это действительно правдоподобный рабочий процесс, то вам следует переделать таблицу. То, что вы хотите, вероятно, выглядит примерно так:

CREATE TABLE #Indebtedness2
(
  call_case     char(10) COLLATE DATABASE_DEFAULT NOT NULL,
  activity_type bigint   NOT NULL,  -- This indicates which date# column it was, if you care
  timestamp     datetime NOT NULL
);

SELECT Indebtedness.call_case,
       Indebtedness.activity_type,
       Indebtedness.timestamp
  FROM ( SELECT call_case,
                activity_type,
                timestamp,
                ROW_NUMBER() OVER ( PARTITION BY call_case
                                    ORDER BY timestamp DESC ) RowNumber
           FROM #Indebtedness2 ) Indebtedness
  WHERE Indebtedness.RowNumber = 1;

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


В случае, если какие-либо ответы будут удалены, вот версии, которые я сравнивал (по порядку)

SELECT
    call_case,
    CASE WHEN date1 > date2 AND date1 > date3
         THEN date1
         WHEN date2 > date3
         THEN date2
         ELSE date3 END AS [Latest Date]
FROM #indebtedness;

SELECT call_case,
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MostRecentDate]
FROM #indebtedness

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case

select call_case, MAX(date)  [Latest Date] from #indebtedness 
UNPIVOT(date FOR col IN ([date1], [date2], [date3])) UNPVT
GROUP BY call_case

select call_case , max(cast(x.Item as date)) as 'Latest Date' from #indebtedness  t
cross apply dbo.SplitString(concat(date1, ',', date2, ',', date3), ',') x
group by call_case

Это отличная детективная работа +1, и я удивлен, что она не привлекла никаких голосов.
Тим Бигелейзен

это очень полезный ответ +1
Ахмед Алхтеб

11

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

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case

@AhmedAlkhteeb. , , Это лучший ответ. Он обрабатывает NULLs, должен иметь хорошую производительность и легко обобщаться на большее количество столбцов.
Гордон Линофф

MAX () в VALUES () и GROUP BY не нужны и замедляют запрос; лучше просто использовать SELECT i.call_case, (SELECT MAX (d.date) FROM (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (дата)) AS max_date ОТ # Indebtedness AS i
Томас Франц

8

SQL FIDDLE

использование MAX()

SELECT call_case,
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MostRecentDate]
FROM #indebtedness

использование CASE

 SELECT
        CASE
            WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1
            WHEN Date2 >= Date1 AND Date2 >= Date3 THEN Date2
            WHEN Date3 >= Date1 AND Date3 >= Date2 THEN Date3
            ELSE                                        Date1
        END AS MostRecentDate
 FROM  #indebtedness

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

1
Я согласен, что при большем количестве значений используемый метод VALUESгораздо более масштабируем, чем большое CASEвыражение. Я также хотел бы узнать, почему за него проголосовали, поскольку избиратель, похоже, считает, что есть проблема с SQL, и поэтому, если они скажут нам эту проблему, мы все сможем извлечь из нее урок.
Ларну

1

На мой взгляд, Pivot - лучший и эффективный вариант для этого запроса. Скопируйте и вставьте в MS SQL SERVER. Пожалуйста, проверьте код, написанный ниже:

CREATE TABLE #indebtedness (call_case CHAR(10), date1 DATETIME, date2 DATETIME, date3 DATETIME)
INSERT #indebtedness VALUES ('Key1', '2019-10-30', '2019-11-30', '2019-10-31')
INSERT #indebtedness VALUES ('Key2', '2019-10-20', '2019-10-30', '2019-11-21')
INSERT #indebtedness VALUES ('Key3', '2019-11-11', '2019-10-29', '2019-10-30')
INSERT #indebtedness VALUES ('Key4', Null, '2019-10-29', '2019-10-13')

--Solution-1:
SELECT        
    call_case,
    MAX(RecnetDate) as MaxDateColumn         
FROM #indebtedness
UNPIVOT
(RecnetDate FOR COL IN ([date1], [date2], [date3])) as TRANSPOSE
GROUP BY call_case 

--Solution-2:
select 
    call_case, case 
    when date1>date2 and date1 > date3 then date1
    when date2>date3                   then date2
    when date3>date1                   then date1 
   else date3 end as date
from #indebtedness as a 


Drop table #indebtedness

0

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

Вот пример (используются разные имена таблиц):

-- Drop pre-existing tables
DROP TABLE #call_log
DROP TABLE #case_type

-- Create table for Case Types
CREATE TABLE #case_type (id INT PRIMARY KEY CLUSTERED NOT NULL, 
    descript VARCHAR(50) NOT NULL)
INSERT #case_type VALUES (1,'No Answer')
INSERT #case_type VALUES (2,'Answer')
INSERT #case_type VALUES (3,'Not Exist')
INSERT #case_type VALUES (4,'whatsapp')
INSERT #case_type VALUES (5,'autodial')
INSERT #case_type VALUES (6,'SMS')

-- Create a Call Log table with a primary identity key and also an index on the call types
CREATE TABLE #call_log (call_num BIGINT PRIMARY KEY CLUSTERED IDENTITY NOT NULL,
    call_type INT NOT NULL REFERENCES #case_type(id), call_date DATETIME)
CREATE NONCLUSTERED INDEX ix_call_log_entry_type ON #call_log(call_type)
INSERT #call_log(call_type, call_date) VALUES (1,'2019-11-30')
INSERT #call_log(call_type, call_date) VALUES (2,'2019-10-15')
INSERT #call_log(call_type, call_date) VALUES (3,null)
INSERT #call_log(call_type, call_date) VALUES (3,'2019-10-29')
INSERT #call_log(call_type, call_date) VALUES (1,'2019-10-25')
INSERT #call_log(call_type, call_date) VALUES (2,'2019-10-30')
INSERT #call_log(call_type, call_date) VALUES (3,'2019-10-13')
INSERT #call_log(call_type, call_date) VALUES (2,'2019-10-20')
INSERT #call_log(call_type, call_date) VALUES (1,'2019-10-30')

-- use an aggregate to show only the latest date for each case type
SELECT DISTINCT ct.descript, MAX(cl.call_date) AS "Date" 
    FROM #call_log cl JOIN #case_type ct ON cl.call_type = ct.id GROUP BY ct.descript

Это позволяет добавлять больше типов дел, добавлять больше записей журнала и обеспечивает лучший дизайн.

Это всего лишь пример для учебных целей.


Редизайн базы данных не может быть вариантом, в зависимости от ситуации пользователя. Существуют и другие доступные варианты, которые не требуют реструктуризации данных.
DWRoelands

@DWRoelands Я бы согласился, что это может быть не вариант, и, возможно, мне следовало бы прояснить это. Я просто отвечал, основываясь на других комментариях, что редизайн, если это возможно , будет лучшим решением и предоставит пример. И я хорошо знаю, что есть много причин, по которым база данных не сможет быть переработана.
Енох
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.