web-dev-qa-db-fra.com

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

En révisant 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 greatest et 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 les fonctionnalités least et greatest.

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

20
Evan Carroll

Une méthode courante consiste à utiliser la clause VALUES et CROSS APPLY les deux colonnes aliasées en une seule colonne, puis obtenez les MIN et MAX de chacune.

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, le résultat plans de requête semble être le même.

33
Erik Darling

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
14

MOINS équivalent:

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

LE PLUS GRAND équivalent:

IIF(@a > @b, @a, @b)
3
Elnur

Ce serait un bon début -

CASE WHEN A > B THEN A ELSE B END
3
Jim Gettma

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

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'opérateur least 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, un simple case when @a <= @b then @a else @b end travaillerait).

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

0
Ed Avis

J'avais l'intention d'ajouter un commentaire à la réponse @ ed-avis, mais je ne pouvais pas 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 "De façon ennuyeuse, 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;

De plus, cette fonction gère [~ # ~] null [~ # ~] comme la version postgresql.

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

PS Je lance des 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 10x fois plus rapide !!!

Je n'ai pas testé en ligne CASE WHEN mais essentiellement 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: Son utilisation est plus rapide [~ # ~] iif [~ # ~] si les performances sont importantes, mais pour le prototypage, ou si la clarté du code est plus nécessaire, et qu'aucun gros calcul n'est impliqué, à condition que la fonction puisse être utilisée.

0
Bogdan Mart