¿SQL Server admite MAYOR y MENOS, si no cuál es la solución común?

15

Al revisar esta pregunta , parece que es mucho trabajo que no debería ser necesario. Están tratando de extender un rango con una fecha. En otras bases de datos, simplemente usaría greatesty least...

least(extendDate,min), greatest(extendDate,max)

Sin embargo, cuando trato de usar estos, me sale

'least' is not a recognized built-in function name.
'greatest' is not a recognized built-in function name.

Eso cubriría la extensión en cualquier dirección.

A los fines de la pregunta, aún tendría que hacer un reemplazo de rango exclusivo.

Me pregunto cómo los usuarios de SQL Server implementan patrones de consulta para imitar leasty greatestfuncionalidad.

¿Desenrolla las condiciones en las CASEdeclaraciones o hay una extensión, un complemento de terceros o una licencia de Microsoft que habilite esta funcionalidad?

Evan Carroll
fuente
Es increíble que MSSQL no tenga una implementación para LEAST/ GREATESTfunciones: casi todos los competidores RDBMS tienen al menos equivalentes. La única excepción que pude encontrar es Sybase, pero también ha sido descontinuada durante muchos años en este momento.
bsplosion

Respuestas:

32

Un método común es usar la VALUEScláusula y CROSS APPLYlas dos columnas con alias como una sola columna, luego obtener el MINy MAXde cada uno.

SELECT MIN(x.CombinedDate) AS least, MAX(x.CombinedDate) AS greatest
FROM   dbo.Users AS u
CROSS APPLY ( VALUES ( u.CreationDate ), ( u.LastAccessDate )) AS x ( CombinedDate );

Hay otras formas de escribirlo, por ejemplo, usando UNION ALL

SELECT MIN(x.CombinedDate) AS least, MAX(x.CombinedDate) AS greatest
FROM   dbo.Users AS u
CROSS APPLY ( SELECT u.CreationDate UNION ALL SELECT u.LastAccessDate ) AS x(CombinedDate);

Sin embargo, los planes de consulta resultantes parecen ser los mismos.

Erik Darling
fuente
12

También puede poner los valores en línea en una subconsulta. Me gusta esto:

select (select max(i) from (values (1), (2), (5), (1), (6)) AS T(i)) greatest,
       (select min(i) from (values (1), (2), (5), (1), (6)) AS T(i)) least
David Browne - Microsoft
fuente
3

Este sería un buen comienzo -

CASE WHEN A > B THEN A ELSE B END
Jim Gettma
fuente
Es una buena sugerencia, pero se mencionó en la pregunta con "desenrollar la condición en declaraciones CASE"
Evan Carroll
3

MENOS equivalente:

IIF(@a < @b, @a, @b)

MAYOR equivalente:

IIF(@a > @b, @a, @b)
Elnur
fuente
3
¿Cómo se hace eso para tres o más valores, por ejemplo least(5,6,7,8,9)?
a_horse_with_no_name
@a_horse_with_no_name Use IIF anidados
Elnur
Este enfoque se volvería rápidamente difícil de leer y verificar ... ¿Cómo le va en términos de rendimiento?
Dodecaphone
0

Creo funciones definidas por el usuario, p. Ej.

create function dbo.udf_LeastInt(@a int, @b int)
returns int
with schemabinding
as
begin
  return case when @a <= @b then @a 
              when @b < @a  then @b
              else null
         end
end

Aunque puede funcionar en casos simples, hay varios problemas con este enfoque, sin embargo:

  • Molesto, tienes que hacer funciones separadas para cada tipo de datos.
  • Maneja solo 2 parámetros, por lo que uno puede necesitar más funciones para manejar muchos parámetros o usar llamadas anidadas de las mismas funciones.
  • Sería mejor (más eficiente) como una TVF en línea en lugar de una función escalar. Eso tiene que ver con la implementación de funciones escalares en el fondo. Hay muchos blogs al respecto, consulte, por ejemplo, SQL 101: Inhibidores de paralelismo - Funciones escalares definidas por el usuario (por John Kehayias .
  • Si uno de los argumentos es nulo, devuelve nulo. Esto coincide con lo leastque hace el operador en Oracle y MySQL, pero difiere de Postgres. Pero esta armadura contra nulo lo hace más detallado (si sabe que no serán nulos, una llanura case when @a <= @b then @a else @b endfuncionaría).

En general, puede ser mejor escribir la casedeclaración a mano si el rendimiento es importante. Incluso he recurrido a generar casedeclaraciones anidadas en el lado del cliente cuando hay varios valores para comparar.

Ed Avis
fuente
0

Tenía la intención de agregar comentarios a la respuesta @ ed-avis, pero no pude hacerlo, debido a la falta de reputación, por lo que publico esto como una extensión de su respuesta.

Eliminé la desventaja de "Molesto, tienes que hacer funciones separadas para cada tipo de datos". Usando SQL_VARIANT .

Aquí está mi implementación:

CREATE OR ALTER FUNCTION my_least(@a SQL_VARIANT, @b SQL_VARIANT)
returns SQL_VARIANT
with schemabinding
as
begin
  return case when @a <= @b then @a 
              when @b < @a  then @b
              WHEN @a IS NULL THEN @b
              WHEN @b IS NULL THEN @a
              else null
         end
END;

También esta función maneja NULL s como la versión postgresql.

Esta función podría agregarse a la base de datos por conveniencia, pero es 10 veces más lenta que el uso integrado IIF. Mis pruebas muestran que dicha función con tipo exacto ( fecha y hora ) funciona igual que la versión sql_variant .

PD : ejecuto algunas pruebas en un conjunto de datos de valores de 350k, y parece que el rendimiento es el mismo, sql_variant es un poco más rápido, pero creo que es solo inquietud.

Pero de cualquier manera, la versión IIF es 10 veces más rápida

No he probado en línea, CASE WHENpero básicamente para t-sql IIF es lo mismo que mayúsculas y minúsculas , y si el optimizador lo convierte en expresión de mayúsculas y minúsculas.

El hecho de que IIF se traduzca a CASE también tiene un impacto en otros aspectos del comportamiento de esta función.

CONCLUSIÓN: Es más rápido usar IIF si el rendimiento es importante, pero para la creación de prototipos, o si se necesita más claridad de código, y no hay grandes cálculos involucrados, siempre que se pueda utilizar la función.

Bogdan Mart
fuente
1
Usted dice que "sqlvariant es un poco más rápido" y que "la versión IIF es 10 veces más rápida". más rápido que qué?
ypercubeᵀᴹ
La variante SQL ver es aproximadamente la misma velocidad que la versión concreete, como lo proporciona otra respuesta. En mi prueba fue 80 ms fater (fuera de 15 segundos), supongo que solo error de estadísticas. Y el uso iif(a<b, a, b) es 10 veces más rápido que cualquier función definida por el usuario.
Bogdan Mart el
Para que quede claro, utilicé mi código con sql_variant reemplazado por datetime, como segunda función. Después de las pruebas, parece que sql_variant no agrega ninguna sobrecarga, pero las funciones definidas por el usuario son mucho más lentas que las integradas
Bogdan Mart el
Pero, ¿alguna de estas funciones, incluida IIF(), es más rápida que usar una CASEexpresión? Mi punto es que, dado que te metiste en problemas con las pruebas de rendimiento, debes probar todos los métodos / respuestas sugeridos.
ypercubeᵀᴹ
1
@ yper-crazyhat-cubeᵀᴹ respuesta actualizada. No lo editaré más, solo quería agregar comentarios sobre sql_variant a la respuesta de ed-avis, pero debido a la falta de pintas tuvo que escribir una respuesta ampliada :-)
Bogdan Mart