¿Por qué no puedo usar una declaración CASE para ver si existe una columna y no seleccionarla?

17

¿Por qué algo como esto no funciona?

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

Solo estoy verificando si la columna existe, sin embargo, SQL Server se queja de que Somecolno existe. ¿Hay una alternativa a esto en una sola declaración?

Carson Reinke
fuente
3
¿Tienes un ejemplo de por qué querrías hacer esto? No puedo entender por qué querrías escribir una consulta que intente seleccionar de una columna que podría no existir.
Mark Sinkinson
44
SQL Server evalúa que la sintaxis de su declaración es correcta antes de ejecutarla. Por lo tanto, todas las columnas a las que se hace referencia deben existir en las tablas, incluso si están incluidas en una CASEdeclaración.
Mark Sinkinson
@ MarkSinkinson: los nombres se verifican después de la sintaxis, pero sí, SQL Server lo hace antes de ejecutar el lote.
Andriy M
1
Seleccionar desde INFORMATION_SCHEMApodría funcionar como una solución alternativa.
Brilliand
44
@Brilliand sys.columns es mucho mejor en mi humilde opinión.
Aaron Bertrand

Respuestas:

43

La siguiente consulta utiliza la misma idea que en esta sorprendente respuesta de ypercube :

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

Funciona así:

  • si dbo.Customerstiene una columna llamada SomeCol, a continuación, SomeColen la SomeCol AS MyTestresolverá como dbo.Customers.SomeCol;

  • si la tabla no tiene esa columna, la referencia seguirá siendo válida, porque ahora se resolverá como dummy.SomeCol: las dummycolumnas pueden ser referenciadas en ese contexto.

Puede especificar múltiples columnas "de repuesto" de esa manera. El truco es no usar el alias de la tabla para tales columnas (que es una práctica mal vista en la mayoría de las situaciones, pero en este caso omitir el alias de la tabla lo ayuda a resolver el problema).

Si la tabla se usa en una combinación y la otra tabla tiene la suya SomeCol, probablemente necesitará usar la consulta anterior como una tabla derivada antes de usarla en la combinación para mantener el truco funcionando, algo como esto:

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
fuente
1
Me pregunto si el compilador de SQL es un poco complicado. Súper genial lo que puedes hacer.
Max Vernon
9

Una forma de hacerlo es verificar la existencia de columnas, luego construir el SQL dinámico en función de si esa columna existe o no.

Sin Dynamic SQL, SQL Server intentará evaluar si la columna existe o no incluso antes de ejecutar la declaración, lo que da como resultado un error.

Sin embargo, significa que tendrá 2 consultas para escribir y potencialmente alterar en el futuro. Pero no creo que realmente deba apuntar a SELECTdeclaraciones en columnas que pueden no existir.

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
fuente
Sí, tiene sentido, sin embargo, debe estar en una sola declaración. En última instancia, estoy buscando probablemente alguna función del sistema mágico que no existe.
Carson Reinke
4

Puede utilizar algunos XML para consultar columnas que podrían estar en la tabla.

Cree un XML a partir de todas las columnas por fila en una aplicación cruzada y extraiga el valor utilizando la values()función.

En esta consulta se conoce el ID, así que obténgalo directamente de la tabla. Col1 y Col2 pueden estar allí o no, así que consígalos usando el 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)

Violín de SQL

Mikael Eriksson
fuente
-1

Mi enfoque difiere solo ligeramente de los demás. Prefiero usar el sistema para esto y simplemente obtener un recuento porque puede asignar el recuento de columnas a una variable en la parte superior de una consulta y luego elegir continuar o no según eso. La desventaja de esto es ... si tiene el mismo nombre de columna en varias tablas, no está seguro de que la columna exista en la tabla que desea consultar. Sin embargo, la técnica también funciona en tablas particulares, ya que solo busca obtener un recuento.

El 'problema' de pedirlo específicamente es: el problema que está experimentando. En general, si un valor NULL le causa problemas ... encuentre otra forma de verificar la existencia. Esta es una forma de hacerlo sin arriesgarse a molestar al servidor.

SELECT COUNT(*) FROM sys.columns WHERE sys.columns.name = 'FarmID'
jinzai
fuente
1
¿Por qué no usar el sysobjectstambién en su consulta para verificar si la tabla específica tiene esa columna?
ypercubeᵀᴹ
Sí ... mencioné que podría hacerse ... incluso podría hacer lo mismo en la tabla particular sobre la que está preguntando ... Acabo de mostrar el formato general para usar COUNT porque COUNT no se produce un error cuando COUNT es CERO y ... supongo que debería mencione que también puede asignarlo a una variable. (por ejemplo, SELECT COUNT (*) AS myVarName ...)
jinzai
1
No puedo ver cómo esto sería mejor que la consulta de Mark. SELECT 1 ...tampoco error.
ypercubeᵀᴹ
No dije que fuera mejor, pero es una forma mucho más simple de lograr el mismo resultado. SELECCIONAR 1 puede no ser un error, pero no es lo mismo que COUNT. SELECT devuelve ALGO ... incluso si es NULL. COUNT solo tiene que devolver un solo número. De esta manera sería más rápido y mencioné que el conteo se puede usar más tarde.
jinzai
Si necesitas el conteo ok. Pero EXISTS (SELECT ...)generalmente es más rápido que (SELECT COUNT(*) ...), no al revés.
ypercubeᵀᴹ
-3

Si lo entendí correctamente ...

Puede usar la consulta como se muestra a continuación y actuar en función del recuento ... Si el recuento es> 1, significa que tiene la columna en esa tabla, y la cuenta = 0, entonces no tiene esa columna en ese mesa

SELECCIONE recuento (*)
DESDE INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME IN ('Id')
AND TABLE_SCHEMA = 'dbo' y TABLE_NAME = 'UserBase';

Sai
fuente
44
No, no entendiste correctamente. También verifique el caso contra las vistas de INFORMATION_SCHEMA de @AaronBertrand
Kin Shah