Pourquoi ne puis-je pas utiliser une instruction CASE pour voir si une colonne existe et non SELECT à partir de celle-ci?

17

Pourquoi quelque chose comme ça ne marche pas?

SELECT
CASE 
WHEN NULLIF(COL_LENGTH('Customers', 'Somecol'), '') IS NULL THEN NULL
ELSE Somecol
END AS MyTest
FROM Customers;

Je vérifie simplement si la colonne existe, cependant, SQL Server se plaint de Somecolne pas exister. Y a-t-il une alternative à cela dans une seule déclaration?

Carson Reinke
la source
3
Avez-vous un exemple pour expliquer pourquoi vous voudriez faire cela? Je ne comprends pas pourquoi vous souhaitez écrire une requête qui tente de sélectionner une colonne qui pourrait ne pas exister.
Mark Sinkinson
4
SQL Server évalue que la syntaxe de votre instruction est correcte avant de l'exécuter. Par conséquent, toutes les colonnes référencées doivent exister dans les tables, même si elles sont encapsulées dans une CASEinstruction.
Mark Sinkinson
@MarkSinkinson: Les noms sont vérifiés après la syntaxe, mais oui, SQL Server le fait avant d'exécuter réellement le lot.
Andriy M
1
La sélection de INFORMATION_SCHEMApourrait fonctionner comme solution de contournement.
Brilliand
4
@Brilliand sys.columns est bien meilleur à mon humble avis .
Aaron Bertrand

Réponses:

43

La requête suivante utilise la même idée que dans cette étonnante réponse de ypercube :

SELECT x.*
FROM (SELECT NULL AS SomeCol) AS dummy
CROSS APPLY
(
  SELECT
    ID,
    SomeCol AS MyTest
  FROM dbo.Customers
) AS x;

Cela fonctionne comme ceci:

  • si dbo.Customersa une colonne nommée SomeCol, alors SomeColin SomeCol AS MyTestsera résolu comme dbo.Customers.SomeCol;

  • si la table n'a pas une telle colonne, la référence sera toujours valide, car maintenant elle sera résolue comme dummy.SomeCol: les dummycolonnes peuvent être référencées dans ce contexte.

Vous pouvez spécifier plusieurs colonnes "de rechange" de cette façon. L'astuce n'est pas d'utiliser l'alias de table pour de telles colonnes (ce qui est une pratique désapprouvée dans la plupart des situations, mais dans ce cas, l'omission de l'alias de table vous aide à résoudre le problème).

Si la table est utilisée dans une jointure et que l'autre table a la sienne SomeCol, vous devrez probablement utiliser la requête ci-dessus comme table dérivée avant de l'utiliser dans la jointure afin de continuer à fonctionner, quelque chose comme ceci:

SELECT ...
FROM
(
  SELECT x.*
  FROM (SELECT NULL AS SomeCol) AS dummy
  CROSS APPLY (
    SELECT
      ID,
      SomeCol AS MyTest
    FROM dbo.Customers
  ) AS x
) AS cust
INNER JOIN ...
;
Andriy M
la source
1
Je me demande si le compilateur SQL est juste un tout petit peu compliqué. Super cool ce que vous pouvez faire.
Max Vernon
9

Une façon de procéder consiste à vérifier l'existence des colonnes, puis à créer le Dynamic SQL en fonction de l'existence ou non de cette colonne.

Sans Dynamic SQL, SQL Server tentera d'évaluer si la colonne existe ou non avant même d'exécuter l'instruction, ce qui entraîne une erreur.

Cela signifie cependant que vous aurez 2 requêtes à écrire et potentiellement modifier à l'avenir. Mais je ne pense pas que vous devriez vraiment cibler des SELECTdéclarations sur des colonnes qui peuvent ne pas exister.

declare @SQL varchar(max)

If exists (select 1 from sys.columns where Name = N'NameOfColumn' and object_id=object_id(N'yourTableName'))
begin
set @SQL = 'select ID, NameOfColumn from yourTableName'
exec(@sql)
end
else
begin
Print 'Column does not exist'
end
Mark Sinkinson
la source
Oui, est logique, cependant, doit être dans une seule déclaration. En fin de compte, je recherche probablement une fonction de système magique qui n'existe pas.
Carson Reinke
4

Vous pouvez utiliser du XML pour interroger les colonnes qui pourraient se trouver dans le tableau.

Créez un XML à partir de toutes les colonnes par ligne dans une application croisée et extrayez la valeur à l'aide de la values()fonction.

Dans cette requête, l'ID est connu, récupérez-le directement dans la table. Col1 et Col2 peuvent être présentes ou non, alors utilisez-les en XML.

select T.ID,
       TX.X.value('(Col1/text())[1]', 'int') as Col1,
       TX.X.value('(Col2/text())[1]', 'int') as Col2
from T
  cross apply (select T.* for xml path(''), type) as TX(X)

SQL Fiddle

Mikael Eriksson
la source
-1

Mon approche ne diffère que légèrement des autres. Je préfère utiliser le système pour cela et obtenir simplement un nombre car vous pouvez attribuer le nombre de colonnes à une variable en haut d'une requête, puis choisir de continuer ou non en fonction de cela. L'inconvénient est que… si vous avez le même nom de colonne dans plusieurs tables, vous n'êtes pas certain que la colonne existe dans la table que vous souhaitez interroger. Cependant, la technique fonctionne également sur des tables particulières, car vous ne cherchez qu'à obtenir un compte.

Le «problème» de le demander spécifiquement est le problème que vous rencontrez. En général, si une valeur NULL vous pose des problèmes… trouvez un autre moyen de vérifier l'existence. C'est une façon de le faire sans risquer de perturber le serveur.

SELECT COUNT(*) FROM sys.columns WHERE sys.columns.name = 'FarmID'
jinzai
la source
1
Pourquoi ne pas utiliser sysobjectségalement le dans votre requête pour vérifier si la table spécifique a une telle colonne?
ypercubeᵀᴹ
Oui… J'ai mentionné que cela pouvait être fait… vous pourriez même faire la même chose sur la table particulière sur laquelle vous interrogez… Je viens de montrer le format général d'utilisation de COUNT car COUNT ne fait pas d'erreur lorsque le COUNT est ZERO et… mentionner que vous pouvez également l'affecter à une variable. (par exemple, SELECT COUNT (*) AS myVarName…)
jinzai
1
Je ne vois pas comment cela serait mieux que la requête de Mark. SELECT 1 ...ne fait pas d'erreur non plus.
ypercubeᵀᴹ
Je n'ai pas dit que c'était mieux, mais c'est un moyen beaucoup plus simple d'obtenir le même résultat. SELECT 1 peut ne pas générer d'erreur, mais ce n'est pas la même chose que COUNT. SELECT renvoie QUELQUE CHOSE… même s'il est NULL. COUNT n'a qu'à renvoyer un seul numéro. Cette façon serait plus rapide et j'ai mentionné que le compte peut être utilisé plus tard.
jinzai
Si vous avez besoin du décompte ok. Mais EXISTS (SELECT ...)est généralement plus rapide que (SELECT COUNT(*) ...), et non l'inverse.
ypercubeᵀᴹ
-3

Si je l'ai bien compris ...

Vous pouvez utiliser la requête quelque chose comme ci-dessous et agir en conséquence en fonction du nombre ... Si le nombre est> 1, cela signifie que vous avez le col dans cette table, et le nombre = 0, alors vous n'avez pas ce col dans ce table

SELECT count (*)
FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME IN ('Id')
AND TABLE_SCHEMA = 'dbo' and TABLE_NAME = 'UserBase';

Sai
la source
4
Non, vous n'avez pas bien compris. Vérifiez également l' affaire contre les vues INFORMATION_SCHEMA de @AaronBertrand
Kin Shah