Отримайте останні дати з кількох стовпців


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 стовпців дати, ви, ймовірно, маєте денормалізовані дані. Пара в одному ряді чудово, це означає різні речі (скажімо, початок і кінець, дата народження та дата, коли людину було додано до системи), але багато дат (з них 10) підказують, що ви додавання нової дати у стовпчик щоразу, коли щось змінюється; не вставляючи новий рядок для збереження історії. Якби це, наприклад, база даних фірми служби доставки, вона не мала б стовпчика дати для кожного можливого кроку шляху; ви б вставили новий рядок для кожного.
Ларну

1
@AhmedAlkhteeb у цьому випадку Ларну вірна - у вас повинна бути таблиця з дією ( call_case) та часовою позначкою. Ні однієї таблиці з 50 стовпцями
Даннно

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засноване рішення не йде паралельно, працює дуже швидко і неймовірно просте.

У цьому випадку, якщо у вас немає необмежених ресурсів (у вас немає), ви повинні вибрати найпростіший і швидкий підхід.


Виникло питання, що робити, якщо вам потрібно продовжувати додавати нові стовпці та розширювати контент. Так, це стає непростим, але це стосується будь-якого іншого рішення. Якщо це насправді правдоподібний робочий процес, то вам слід переглянути дизайн таблиці. Те, що ви хочете, мабуть, виглядає приблизно так:

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 ВІД # Заборгованість AS я
Томас Франц

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.