SQL Server prend-il en charge GREATEST et LEAST, sinon quelle est la solution de contournement courante?

15

En examinant cette question, il semble que ce soit beaucoup de travail qui ne devrait pas être nécessaire. Ils essaient d'étendre une plage avec une date. Dans d'autres bases de données, vous utiliseriez simplement greatestet least..

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

Quand j'essaie de les utiliser, je reçois

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

Cela couvrirait l'extension dans les deux sens.

Aux fins de la question, vous devrez toujours effectuer un remplacement de gamme exclusif.

Je me demande simplement comment les utilisateurs de SQL Server implémentent des modèles de requête pour imiter leastet greatestfonctionner.

Déroulez-vous les conditions dans des CASEinstructions ou existe-t-il une extension, un module complémentaire tiers ou une licence de Microsoft qui active cette fonctionnalité?

Evan Carroll
la source
Il est incroyable que MSSQL n'ait pas d'implémentation pour les fonctions LEAST/ GREATEST- presque tous les concurrents SGBDR ont au moins des équivalents. La seule exception que j'ai pu trouver est Sybase, mais cela a également été interrompu pendant de nombreuses années à ce stade.
bsplosion
1
feedback.azure.com/forums/908035-sql-server/suggestions/… si vous souhaitez voter pour cela
J Brune

Réponses:

32

Une méthode courante consiste à utiliser la VALUESclause et CROSS APPLYles deux colonnes aliasées comme une seule colonne, puis à obtenir le MINet MAXde chacun.

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 );

Il existe d'autres façons de l'écrire, par exemple en utilisant 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);

Cependant, les plans de requête résultants semblent être les mêmes.

Erik Darling
la source
12

Vous pouvez également mettre les valeurs en ligne dans une sous-requête. Comme ça:

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
la source
3

Ce serait un bon début -

CASE WHEN A > B THEN A ELSE B END
Jim Gettma
la source
C'est une bonne suggestion mais elle a été mentionnée dans la question avec "dérouler la condition dans les déclarations CASE"
Evan Carroll
3

MOINS équivalent:

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

PLUS GRAND équivalent:

IIF(@a > @b, @a, @b)
Elnur
la source
3
Comment faites-vous cela pour trois valeurs ou plus, par exemple least(5,6,7,8,9)?
a_horse_with_no_name
@a_horse_with_no_name Utiliser des IIF imbriqués
Elnur
Cette approche deviendrait rapidement difficile à lire et à vérifier ... Comment se comporte-t-elle en termes de performances?
Dodecaphone du
0

Je crée des fonctions définies par l'utilisateur, par exemple

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

Bien qu'elle puisse fonctionner dans des cas simples, cette approche présente cependant plusieurs problèmes:

  • De façon ennuyeuse, vous devez créer des fonctions distinctes pour chaque type de données.
  • Il ne gère que 2 paramètres, de sorte que l'on peut avoir besoin de plus de fonctions pour gérer de nombreux paramètres ou utiliser des appels imbriqués des mêmes fonctions.
  • Ce serait mieux (plus efficace) comme TVF en ligne plutôt que comme fonction scalaire. Cela a à voir avec la mise en œuvre des fonctions scalaires dans l'âme. Il existe de nombreux blogs à ce sujet, voir par exemple SQL 101: Parallelism Inhibitors - Scalar User Defined Functions (par John Kehayias .
  • Si l'un des arguments est null, il renvoie null. Cela correspond à ce que fait l' leastopérateur dans Oracle et MySQL, mais diffère de Postgres. Mais ce blindage contre null le rend plus verbeux (si vous savez qu'ils ne seront pas nuls, une plaine case when @a <= @b then @a else @b endfonctionnerait).

Dans l'ensemble, il peut être préférable de rédiger la casedéclaration à la main si les performances sont importantes. J'ai même eu recours à la génération d' caseinstructions imbriquées côté client lorsqu'il y a plusieurs valeurs à comparer.

Ed Avis
la source
0

J'avais l'intention d'ajouter un commentaire à la réponse @ ed-avis, mais je n'ai pas pu le faire, en raison du manque de réputation, alors je poste ceci en tant qu'extension à sa réponse.

J'ai éliminé l'inconvénient de "Vous devez créer des fonctions distinctes pour chaque type de données." Utilisation de SQL_VARIANT .

Voici ma mise en œuvre:

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;

Cette fonction gère également les valeurs NULL comme la version postgresql.

Cette fonction pourrait être ajoutée à DB pour plus de commodité, mais elle est 10 fois plus lente que l'utilisation intégrée IIF. Mes tests montrent que cette fonction de type exact ( datetime ) fonctionne de la même manière que la version sql_variant .

PS J'exécute quelques tests sur un ensemble de données de 350k valeurs, et il semble que les performances soient les mêmes, sql_variant est un peu plus rapide, mais je crois que ce n'est que de la frousse.

Mais de toute façon la version IIF est 10 fois plus rapide !!!

Je n'ai pas testé en ligne CASE WHENmais fondamentalement pour t-sql IIF est identique à case , et iif get est converti par l'optimiseur en expression case.

Le fait que l'IIF soit traduit en CASE a également un impact sur d'autres aspects du comportement de cette fonction.

CONCLUSION: Il est plus rapide d'utiliser IIF si les performances sont importantes, mais pour le prototypage, ou si la clarté du code est plus nécessaire, et qu'aucun calcul important n'est impliqué, à condition que la fonction puisse être utilisée.

Bogdan Mart
la source
1
Vous dites que "sqlvariant est un tout petit peu plus rapide" et que "la version IIF est 10 fois plus rapide". plus vite que quoi?
ypercubeᵀᴹ
Sql variant ver est à peu près la même vitesse que la version concreete, comme fourni par une autre réponse. Dans mon test, il était de 80 ms de plus (à partir de 15 secondes), je suppose que juste une erreur de statistique. Et l'utilisation iif(a<b, a, b) est 10 fois plus rapide que n'importe quelle fonction définie par l'utilisateur.
Bogdan Mart
Pour être clair, j'ai utilisé mon code avec sql_variant remplacé par datetime, comme deuxième fonction. Après les tests, il semble que sql_variant n'ajoute pas de surcharge, mais les fonctions définies par l'utilisateur sont beaucoup plus lentes que celles intégrées
Bogdan Mart
Mais l'une de ces fonctions - y compris IIF()- est-elle plus rapide que l'utilisation d'une CASEexpression? Mon point est que, puisque vous vous êtes donné la peine de tester les performances, vous devez tester toutes les méthodes / réponses suggérées.
ypercubeᵀᴹ
1
@ yper-crazyhat-cubeᵀᴹ réponse mise à jour. Je ne le modifierai pas plus, je voulais juste ajouter un commentaire concernant sql_variant à la réponse d'ed-avis, mais en raison du manque de pintes, j'ai dû écrire une réponse développée :-)
Bogdan Mart