SQL MAX из нескольких столбцов?

373

Как вы возвращаете 1 значение в строке максимум нескольких столбцов:

TableName

[Number, Date1, Date2, Date3, Cost]

Мне нужно вернуть что-то вроде этого:

[Number, Most_Recent_Date, Cost]

Запрос?

BenB
источник

Ответы:

161

Ну, вы можете использовать оператор 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

[Для Microsoft SQL Server 2008 и выше, вы можете рассмотреть более простой ответ Свена ниже.]

Лассе В. Карлсен
источник
11
Разве этого недостаточно для использования WHEN Date1 > Date2 AND Date1 > Date3 THEN Date1; WHEN Date2 > Date3 THEN Date3; ELSE Date3?
Треб
21
Очевидный ответ, но он не работает со значениями NULL, и попытка исправить это становится очень грязной.
Разочарован
5
Necro'ing этот старый пост, но вы можете обернуть каждую дату в COALESCE для обработки NULL. Тогда одно из этих утверждений WHEN выглядело бы следующим образом: WHEN Date1> = COALESCE (Date2, '') AND Date1> = COALESCE (Date3, '') THEN Date3 (делайте то же самое для других когда)
Билл Самброне,
для тех, кто пришел сюда в поисках пути для MySQL, посмотрите ответ @ bajafresh4life: stackoverflow.com/a/331873/1412157
LucaM
2
Кстати, он возвращает Date1, когда Date2 равен нулю, даже если Date3> Date1.
jumxozizi
855

Вот еще одно приятное решение для Maxфункциональности с использованием T-SQL и SQL Server

SELECT [Other Fields],
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MaxDate]
FROM [YourTableName]
Sven
источник
47
Версия SQL должна быть> = 2008.
Даниэль
10
Это очень хорошо работает с 2008 года и обрабатывает NULL. Очень хорошее решение.
nycdan
10
@Cheburek: Из value (v) «value» - это псевдоним виртуальной таблицы, а «v» - это имя виртуального столбца значений даты.
Джонас Линкольн
2
Это великолепно. Где я могу найти документацию для этой виртуальной таблицы Value ()?
My Other Me
33
Сначала я тоже не понимал VALUE (v). Если вы хотите понять VALUE, попробуйте этот запрос, который создает виртуальную таблицу с 1 столбцом: SELECT * FROM (VALUES (1), (5), (1)) как listOfValues ​​(columnName) И этот запрос, который создает виртуальную таблицу с 2 столбцами: SELECT * FROM (VALUES (1,2), (5,3), (1,4)) как tableOfValues ​​(columnName1, ColumnName2) Теперь вы можете понять, почему в этом примере запроса содержится значение AS (v). Мой последний запрос выглядел так: SELECT Max (currentValues) как Max FROM (VALUES (12), (25), (35)) AS allCurrents (currentValues) Он выберет максимальное значение, которое в данном случае равно 35.
Джексон
148

Если вы используете MySQL, вы можете использовать

SELECT GREATEST(col1, col2 ...) FROM table
bajafresh4life
источник
41
тег является sqlserver
Codewerks
104
Правда, но все же очень полезный ответ, так как люди находят этот вопрос в отношении MySQL.
Philfreo
4
Также доступно в PostgreSQL от 8.1 .
Замороженное пламя
4
Не обрабатывает NULL хорошо, но если вы объедините (col1, 0) вокруг значений вашего столбца, вы будете готовить газом, смотрите этот ответ stackoverflow.com/questions/9831851/…
Стэн Куинн
А что насчет этого решения: stackoverflow.com/a/2166693/4824854
Сандбург,
64

Есть еще 3 метода, где UNPIVOT(1) является самым быстрым на сегодняшний день, за которым следует Simulated Unpivot (3), который намного медленнее, чем (1), но все же быстрее, чем (2)

CREATE TABLE dates
    (
      number INT PRIMARY KEY ,
      date1 DATETIME ,
      date2 DATETIME ,
      date3 DATETIME ,
      cost INT
    )

INSERT  INTO dates
VALUES  ( 1, '1/1/2008', '2/4/2008', '3/1/2008', 10 )
INSERT  INTO dates
VALUES  ( 2, '1/2/2008', '2/3/2008', '3/3/2008', 20 )
INSERT  INTO dates
VALUES  ( 3, '1/3/2008', '2/2/2008', '3/2/2008', 30 )
INSERT  INTO dates
VALUES  ( 4, '1/4/2008', '2/1/2008', '3/4/2008', 40 )
GO

Решение 1 ( UNPIVOT)

SELECT  number ,
        MAX(dDate) maxDate ,
        cost
FROM    dates UNPIVOT ( dDate FOR nDate IN ( Date1, Date2,
                                            Date3 ) ) as u
GROUP BY number ,
        cost 
GO

Решение 2 (подзапрос на строку)

SELECT  number ,
        ( SELECT    MAX(dDate) maxDate
          FROM      ( SELECT    d.date1 AS dDate
                      UNION
                      SELECT    d.date2
                      UNION
                      SELECT    d.date3
                    ) a
        ) MaxDate ,
        Cost
FROM    dates d
GO

Решение 3 (Имитация UNPIVOT)

;WITH    maxD
          AS ( SELECT   number ,
                        MAX(CASE rn
                              WHEN 1 THEN Date1
                              WHEN 2 THEN date2
                              ELSE date3
                            END) AS maxDate
               FROM     dates a
                        CROSS JOIN ( SELECT 1 AS rn
                                     UNION
                                     SELECT 2
                                     UNION
                                     SELECT 3
                                   ) b
               GROUP BY Number
             )
    SELECT  dates.number ,
            maxD.maxDate ,
            dates.cost
    FROM    dates
            INNER JOIN MaxD ON dates.number = maxD.number
GO

DROP TABLE dates
GO
Niikola
источник
1
Ницца. Я не знал об операторах PIVOT и UNPIVOT.
Sako73
Любая идея, какие версии SQL Server поддерживают pivot / unpivot?
Разочарован
1
@CraigYoung SQL Server 2005 с COMPATIBILITY_LEVEL, установленным в 90.
Пол Сифретт
18

Любой из двух примеров ниже будет работать:

SELECT  MAX(date_columns) AS max_date
FROM    ( (SELECT   date1 AS date_columns
           FROM     data_table         )
          UNION
          ( SELECT  date2 AS date_columns
            FROM    data_table
          )
          UNION
          ( SELECT  date3 AS date_columns
            FROM    data_table
          )
        ) AS date_query

Второе дополнение к ответу Лассевка .

SELECT  MAX(MostRecentDate)
FROM    ( 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      data_table
        ) AS date_query 
databyss
источник
Первый ответ хорош, но может быть значительно упрощен. Второй ответ не работает со значениями NULL. Попытка решить эту проблему становится очень грязной.
Разочарован
Вы должны использовать UNION ALL, а не UNION, чтобы избежать ненужной подразумеваемой операции DISTINCT.
JamieSee
17

Для T-SQL (MSSQL 2008+)

SELECT
  (SELECT
     MAX(MyMaxName) 
   FROM ( VALUES 
            (MAX(Field1)), 
            (MAX(Field2)) 
        ) MyAlias(MyMaxName)
  ) 
FROM MyTable1
Докер
источник
9
DECLARE @TableName TABLE (Number INT, Date1 DATETIME, Date2 DATETIME, Date3 DATETIME, Cost MONEY)

INSERT INTO @TableName 
SELECT 1, '20000101', '20010101','20020101',100 UNION ALL
SELECT 2, '20000101', '19900101','19980101',99 

SELECT Number,
       Cost  ,
       (SELECT MAX([Date])
       FROM    (SELECT Date1 AS [Date]
               UNION ALL
               SELECT Date2
               UNION ALL
               SELECT Date3
               )
               D
       )
       [Most Recent Date]
FROM   @TableName
Мартин Смит
источник
Работал в любой версии SQL для меня, отличное решение
Кирилл
9

Скалярная функция вызывает всевозможные проблемы с производительностью, поэтому лучше, если возможно, обернуть логику во встроенную табличную функцию. Это функция, которую я использовал для замены некоторых пользовательских функций, которые выбирали минимальные / максимальные даты из списка до десяти дат. При тестировании на моем наборе данных из 1 миллиона строк скалярной функции потребовалось более 15 минут, прежде чем я завершил запрос, и для встроенного TVF потребовалась 1 минута, то есть столько же времени, сколько для выбора набора результатов во временной таблице. Чтобы использовать это, вызовите функцию из подзапроса в SELECT или CROSS APPLY.

CREATE FUNCTION dbo.Get_Min_Max_Date
(
    @Date1  datetime,
    @Date2  datetime,
    @Date3  datetime,
    @Date4  datetime,
    @Date5  datetime,
    @Date6  datetime,
    @Date7  datetime,
    @Date8  datetime,
    @Date9  datetime,
    @Date10 datetime
)
RETURNS TABLE
AS
RETURN
(
    SELECT      Max(DateValue)  Max_Date,
                Min(DateValue)  Min_Date
    FROM        (
                    VALUES  (@Date1),
                            (@Date2),
                            (@Date3),
                            (@Date4),
                            (@Date5),
                            (@Date6),
                            (@Date7),
                            (@Date8),
                            (@Date9),
                            (@Date10)
                )   AS Dates(DateValue)
)
MartinC
источник
5
SELECT 
    CASE 
        WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1 
        WHEN Date2 >= Date3 THEN Date2 
        ELSE Date3
    END AS MostRecentDate 

Это немного легче записать и пропускает этапы оценки, поскольку оператор case оценивается по порядку.

натуральный
источник
4
Осторожный. Если Date2 НЕДЕЙСТВИТЕЛЕН, ответ будет Date3; даже если Date1 больше.
Разочарован
4

К сожалению , ответ Лассе , хотя и кажется очевидным, имеет решающий недостаток. Он не может обрабатывать значения NULL. Любое единственное значение NULL приводит к возвращению Date1. К сожалению, любая попытка решить эту проблему имеет тенденцию быть чрезвычайно грязной и не очень хорошо масштабируется до 4 или более значений.

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

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

DECLARE @v1 INT ,
        @v2 INT ,
        @v3 INT
--SET @v1 = 1 --Comment out SET statements to experiment with 
              --various combinations of NULL values
SET @v2 = 2
SET @v3 = 3

SELECT  ( SELECT    MAX(Vals)
          FROM      ( SELECT    v1 AS Vals
                      UNION
                      SELECT    v2
                      UNION
                      SELECT    v3
                    ) tmp
          WHERE     Vals IS NOT NULL -- This eliminates NULL warning

        ) AS MaxVal
FROM    ( SELECT    @v1 AS v1
        ) t1
        CROSS JOIN ( SELECT @v2 AS v2
                   ) t2
        CROSS JOIN ( SELECT @v3 AS v3
                   ) t3
разочарованный
источник
4

Проблема: выберите минимальное значение ставки, присвоенное организации. Требования: Агентские ставки могут быть нулевыми.

[MinRateValue] = 
CASE 
   WHEN ISNULL(FitchRating.RatingValue, 100) < = ISNULL(MoodyRating.RatingValue, 99) 
   AND  ISNULL(FitchRating.RatingValue, 100) < = ISNULL(StandardPoorsRating.RatingValue, 99) 
   THEN FitchgAgency.RatingAgencyName

   WHEN ISNULL(MoodyRating.RatingValue, 100) < = ISNULL(StandardPoorsRating.RatingValue , 99)
   THEN MoodyAgency.RatingAgencyName

   ELSE ISNULL(StandardPoorsRating.RatingValue, 'N/A') 
END 

Вдохновленный этим ответом от Nat

Луис Мигель Роза
источник
3

Если вы используете SQL Server 2005, вы можете использовать функцию UNPIVOT. Вот полный пример:

create table dates 
(
  number int,
  date1 datetime,
  date2 datetime,
  date3 datetime 
)

insert into dates values (1, '1/1/2008', '2/4/2008', '3/1/2008')
insert into dates values (1, '1/2/2008', '2/3/2008', '3/3/2008')
insert into dates values (1, '1/3/2008', '2/2/2008', '3/2/2008')
insert into dates values (1, '1/4/2008', '2/1/2008', '3/4/2008')

select max(dateMaxes)
from (
  select 
    (select max(date1) from dates) date1max, 
    (select max(date2) from dates) date2max,
    (select max(date3) from dates) date3max
) myTable
unpivot (dateMaxes For fieldName In (date1max, date2max, date3max)) as tblPivot

drop table dates
Лэнс Фишер
источник
1
Я думаю, что мне больше нравится пример UNION.
Лэнс Фишер
«Как вы возвращаете ОДНО ЗНАЧЕНИЕ В РЯДУ из максимум нескольких столбцов»
Niikola
3

Использование CROSS APPLY (для 2005+) ....

SELECT MostRecentDate 
FROM SourceTable
    CROSS APPLY (SELECT MAX(d) MostRecentDate FROM (VALUES (Date1), (Date2), (Date3)) AS a(d)) md
EarlOfEnnui
источник
3

С SQL Server 2012 мы можем использовать IIF .

 DECLARE @Date1 DATE='2014-07-03';
 DECLARE @Date2 DATE='2014-07-04';
 DECLARE @Date3 DATE='2014-07-05';

 SELECT IIF(@Date1>@Date2,
        IIF(@Date1>@Date3,@Date1,@Date3),
        IIF(@Date2>@Date3,@Date2,@Date3)) AS MostRecentDate
Абдулбасит
источник
Довольно мило, но не обрабатывает нули. Например:DECLARE @Date1 DATE='2014-08-01'; DECLARE @Date2 DATE=null; DECLARE @Date3 DATE='2014-07-05'; /*this gets returned*/
jumxozizi
Мы могли бы обрабатывать нули, как это:select IIF(@Date1 > @Date2 or @Date2 is null, IIF(@Date1 > @Date3 or @Date3 is null, @Date1, @Date3), IIF(@Date2 > @Date3 or @Date3 is null, @Date2, @Date3)) as MostRecentDate
jumxozizi
1

Пожалуйста, попробуйте использовать UNPIVOT:

SELECT MAX(MaxDt) MaxDt
   FROM tbl 
UNPIVOT
   (MaxDt FOR E IN 
      (Date1, Date2, Date3)
)AS unpvt;
TechDo
источник
1

Я предпочитаю решения, основанные на случае, когда, я предполагаю, что это должно оказать наименьшее влияние на возможное падение производительности по сравнению с другими возможными решениями, такими как с перекрестным применением, values ​​(), пользовательскими функциями и т. Д.

Вот вариант case-when, который обрабатывает нулевые значения с большинством возможных тестовых случаев:

SELECT
    CASE 
        WHEN Date1 > coalesce(Date2,'0001-01-01') AND Date1 > coalesce(Date3,'0001-01-01') THEN Date1 
        WHEN Date2 > coalesce(Date3,'0001-01-01') THEN Date2 
        ELSE Date3
    END AS MostRecentDate
    , *
from 
(values
     (  1, cast('2001-01-01' as Date), cast('2002-01-01' as Date), cast('2003-01-01' as Date))
    ,(  2, cast('2001-01-01' as Date), cast('2003-01-01' as Date), cast('2002-01-01' as Date))
    ,(  3, cast('2002-01-01' as Date), cast('2001-01-01' as Date), cast('2003-01-01' as Date))
    ,(  4, cast('2002-01-01' as Date), cast('2003-01-01' as Date), cast('2001-01-01' as Date))
    ,(  5, cast('2003-01-01' as Date), cast('2001-01-01' as Date), cast('2002-01-01' as Date))
    ,(  6, cast('2003-01-01' as Date), cast('2002-01-01' as Date), cast('2001-01-01' as Date))
    ,( 11, cast(NULL         as Date), cast('2002-01-01' as Date), cast('2003-01-01' as Date))
    ,( 12, cast(NULL         as Date), cast('2003-01-01' as Date), cast('2002-01-01' as Date))
    ,( 13, cast('2003-01-01' as Date), cast(NULL         as Date), cast('2002-01-01' as Date))
    ,( 14, cast('2002-01-01' as Date), cast(NULL         as Date), cast('2003-01-01' as Date))
    ,( 15, cast('2003-01-01' as Date), cast('2002-01-01' as Date), cast(NULL         as Date))
    ,( 16, cast('2002-01-01' as Date), cast('2003-01-01' as Date), cast(NULL         as Date))
    ,( 21, cast('2003-01-01' as Date), cast(NULL         as Date), cast(NULL         as Date))
    ,( 22, cast(NULL         as Date), cast('2003-01-01' as Date), cast(NULL         as Date))
    ,( 23, cast(NULL         as Date), cast(NULL         as Date), cast('2003-01-01' as Date))
    ,( 31, cast(NULL         as Date), cast(NULL         as Date), cast(NULL         as Date))

) as demoValues(id, Date1,Date2,Date3)
order by id
;

и результат:

MostRecent    id   Date1      Date2      Date3
2003-01-01    1    2001-01-01 2002-01-01 2003-01-01
2003-01-01    2    2001-01-01 2003-01-01 2002-01-01
2003-01-01    3    2002-01-01 2001-01-01 2002-01-01
2003-01-01    4    2002-01-01 2003-01-01 2001-01-01
2003-01-01    5    2003-01-01 2001-01-01 2002-01-01
2003-01-01    6    2003-01-01 2002-01-01 2001-01-01
2003-01-01    11   NULL       2002-01-01 2003-01-01
2003-01-01    12   NULL       2003-01-01 2002-01-01
2003-01-01    13   2003-01-01 NULL       2002-01-01
2003-01-01    14   2002-01-01 NULL       2003-01-01
2003-01-01    15   2003-01-01 2002-01-01 NULL
2003-01-01    16   2002-01-01 2003-01-01 NULL
2003-01-01    21   2003-01-01 NULL       NULL
2003-01-01    22   NULL       2003-01-01 NULL
2003-01-01    23   NULL       NULL       2003-01-01
NULL          31   NULL       NULL       NULL
Роберт Лужо
источник
1
о боже, спасибо, сэр! Я потратил так много времени на эту чертову формулу монстра, которая все еще давала мне нули, и теперь я вижу свет в конце туннеля.
Макс С.
0

Вы можете создать функцию, в которой вы передадите даты, а затем добавите функцию в оператор выбора, как показано ниже. выберите Число, dbo.fxMost_Recent_Date (Дата1, Дата2, Дата3), Стоимость

create FUNCTION  fxMost_Recent_Date 

(@ Date1 smalldatetime, @ Date2 smalldatetime, @ Date3 smalldatetime) ВОЗВРАЩАЕТ smalldatetime, КАК НАЧИНАЕТСЯ @Result smalldatetime

declare @MostRecent smalldatetime

set @MostRecent='1/1/1900'

if @Date1>@MostRecent begin set @MostRecent=@Date1 end
if @Date2>@MostRecent begin set @MostRecent=@Date2 end
if @Date3>@MostRecent begin set @MostRecent=@Date3 end
RETURN @MostRecent

КОНЕЦ

DrYodo
источник
0

На основе решения ScottPletcher от http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/Q_24204894.html я создал набор функций (например, GetMaxOfDates3, GetMaxOfDates13), чтобы найти максимум до 13 значений даты с помощью UNION ALL. См. Функцию T-SQL, чтобы получить максимум значений из одной строки. Однако на момент написания этих функций я не рассматривал решение UNPIVOT.

Майкл Фрейдгейм
источник
0

Еще один способ использования CASE WHEN

SELECT CASE true 
       WHEN max(row1) >= max(row2) THEN CASE true WHEN max(row1) >= max(row3) THEN max(row1) ELSE max(row3) end ELSE
       CASE true WHEN max(row2) >= max(row3) THEN max(row2) ELSE max(row3) END END
FROM yourTable
Mabell
источник
-1

введите описание изображения здесьВыше таблицы приведена таблица окладов сотрудников с столбцами salary1, salary2, salary3, salary4. В приведенном ниже запросе будет возвращено максимальное значение из четырех столбцов.

select  
 (select Max(salval) from( values (max(salary1)),(max(salary2)),(max(salary3)),(max(Salary4)))alias(salval)) as largest_val
 from EmployeeSalary

Выполнение вышеуказанного запроса даст вывод как large_val (10001)

Логика вышеуказанного запроса выглядит следующим образом:

select Max(salvalue) from(values (10001),(5098),(6070),(7500))alias(salvalue)

выходной будет 10001

Брижеш Рэй
источник
Это почти копия решения от @sven, опубликованного 29 июля '11
Луук
-3

вот хорошее решение:

CREATE function [dbo].[inLineMax] (@v1 float,@v2 float,@v3 float,@v4 float)
returns float
as
begin
declare @val float
set @val = 0 
declare @TableVal table
(value float )
insert into @TableVal select @v1
insert into @TableVal select @v2
insert into @TableVal select @v3
insert into @TableVal select @v4

select @val= max(value) from @TableVal

return @val
end 
danvasiloiu
источник
-3

Я не знаю, работает ли он на SQL и т. Д. В справке M $ ACCESS есть функция, MAXA(Value1;Value2;...)которая должна делать это.

Надежда может помочь кому-то.

PD: значения могут быть столбцами или рассчитанными и т. Д.

Claudio
источник
1
Microsoft Access - это совершенно другой продукт. Кроме того, можете ли вы получить заявку на такую ​​функцию? Я никогда не видел и не слышал об этом в Access.
deutschZuid
1
MAXAэто функция Excel , а не Access.
Феликс Ив