Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

nvarchar concaténation / index / nvarchar(max) comportement inexplicable

TLDR ; Il ne s'agit pas d'une approche documentée/prise en charge pour la concaténation de chaînes sur plusieurs lignes. Cela fonctionne parfois mais aussi parfois échoue car cela dépend du plan d'exécution que vous obtenez.

Utilisez plutôt l'une des approches garanties suivantes

SQL Server 2017+

SELECT @a = STRING_AGG([msg], '') WITHIN GROUP (ORDER BY [priority] ASC)
FROM bla
where   autofix = 0

SQL Server 2005+

SELECT @a = (SELECT [msg] + ''
             FROM   bla
             WHERE  autofix = 0
             ORDER  BY [priority] ASC
             FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)') 

Contexte

L'article de la base de connaissances déjà lié par VanDerNorth inclut la ligne

Le comportement correct d'une requête de concaténation agrégée n'est pas défini.

mais continue ensuite à brouiller un peu les pistes en proposant une solution de contournement qui semble indiquer qu'un comportement déterministe est possible.

Afin d'obtenir les résultats attendus d'une requête de concaténation d'agrégats, appliquez n'importe quelle fonction ou expression Transact-SQL aux colonnes de la liste SELECT plutôt qu'à la clause ORDER BY.

Votre requête problématique n'applique aucune expression aux colonnes dans le ORDER BY clause.

L'article de 2005 Commander des garanties dans SQL Server... indique

Pour des raisons de rétrocompatibilité, SQL Server prend en charge les affectations de type SELECT @p =@p + 1 ... ORDER BY dans la portée la plus élevée.

Dans les plans où la concaténation fonctionne comme prévu, le calcul scalaire avec l'expression [Expr1003] = Scalar Operator([@x]+[Expr1004]) apparaît au-dessus du tri.

Dans le plan où il ne fonctionne pas, le scalaire de calcul apparaît sous le tri. Comme expliqué dans cet élément de connexion de 2006 lorsque l'expression @x = @x + [msg] apparaît sous le tri, il est évalué pour chaque ligne mais toutes les évaluations finissent par utiliser la valeur de pré-affectation de @x . Dans un autre élément de connexion similaire de 2006, la réponse de Microsoft parlait de "résoudre" le problème.

La réponse de Microsoft sur tous les éléments Connect ultérieurs sur ce problème (et il y en a beaucoup) indique que ce n'est tout simplement pas garanti

Exemple 1

nous ne garantissons pas l'exactitude des requêtes de concaténation (comme l'utilisation d'affectations de variables avec récupération des données dans un ordre spécifique). La sortie de la requête peut changer dans SQL Server 2008 en fonction du plan choisi, des données dans les tables, etc. Vous ne devriez pas vous fier à ce travail de manière cohérente, même si la syntaxe vous permet d'écrire une instruction SELECT qui mélange la récupération de lignes ordonnées avec une affectation de variable.

Exemple 2

Le comportement que vous voyez est intentionnel. L'utilisation d'opérations d'affectation (concaténation dans cet exemple) dans des requêtes avec la clause ORDER BY a un comportement indéfini. Cela peut changer d'une version à l'autre ou même au sein d'une version de serveur particulière en raison de modifications du plan de requête. Vous ne pouvez pas compter sur ce comportement même s'il existe des solutions de contournement. Consultez l'article de la base de connaissances ci-dessous pour plus de détails :
http://support.microsoft.com/kb/287515 Le SEUL mécanisme garanti est le suivant :

  1. Utilisez le curseur pour parcourir les lignes dans un ordre spécifique et concaténer les valeurs
  2. Utiliser pour la requête xml avec ORDER BY pour générer les valeurs concaténées
  3. Utiliser l'agrégat CLR (cela ne fonctionnera pas avec la clause ORDER BY)

Exemple 3

Le comportement que vous voyez est en fait voulu par la conception. Cela a à voir avec SQL étant un langage de manipulation d'ensembles. Toutes les expressions de la liste SELECT (et cela inclut également les affectations) ne sont pas garanties d'être exécutées exactement une fois pour chaque ligne de sortie. En fait, SQL queryoptimizer s'efforce de les exécuter le moins de fois possible. Cela donnera les résultats attendus lorsque vous calculez la valeur de la variable en fonction de certaines données des tables, mais lorsque la valeur que vous attribuez dépend de la valeur précédente de la même variable, les résultats peuvent être assez inattendus. Si l'optimiseur de requête déplace l'expression à un endroit différent dans l'arborescence de la requête, elle peut être évaluée moins de fois (ou juste une fois, comme dans l'un de vos exemples). C'est pourquoi nous vous déconseillons d'utiliser les affectations de type "itération" pour calculer des valeurs agrégées. Nous constatons que les solutions de contournement basées sur XML ... fonctionnent généralement bien pour les clients

Exemple 4

Même sans ORDER BY, nous ne garantissons pas que @var =@var + produira la valeur concaténée pour toute instruction affectant plusieurs lignes. Le côté droit de l'expression peut être évalué une ou plusieurs fois pendant l'exécution de la requête et le comportement, comme je l'ai dit, dépend du plan.

Exemple 5

L'affectation de variable avec l'instruction SELECT est une syntaxe propriétaire (T-SQL uniquement) dans laquelle le comportement est indéfini ou dépend du plan si plusieurs lignes sont produites. Si vous devez effectuer la concaténation de chaînes, utilisez un agrégat SQLCLR ou une concaténation basée sur une requête FOR XML ou d'autres méthodes relationnelles.