Por que não consigo usar uma instrução CASE para ver se existe uma coluna e não SELECT nela?

17

Por que algo assim não funciona?

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

Estou apenas verificando se a coluna existe, no entanto, o SQL Server reclama de Somecolnão existir. Existe uma alternativa para isso em uma única declaração?

Carson Reinke
fonte
3
Você tem um exemplo de por que você gostaria de fazer isso? Não consigo entender por que você deseja escrever uma consulta que tente selecionar de uma coluna que pode não existir.
Mark Sinkinson
4
O SQL Server avalia que a sintaxe da sua instrução está correta antes de executá-la. Portanto, todas as colunas referenciadas devem existir nas tabelas, mesmo se estiverem agrupadas em uma CASEinstrução.
Mark Sinkinson
@ MarkSinkinson: Os nomes são verificados após a sintaxe, mas sim, o SQL Server faz isso antes de realmente executar o lote.
Andriy M
1
Selecionar entre INFORMATION_SCHEMApoderia funcionar como uma solução alternativa.
Brilliand
4
@Brilliand sys.columns é muito melhor IMHO.
Aaron Bertrand

Respostas:

43

A consulta a seguir usa a mesma ideia que nesta resposta incrível do ypercube :

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

Funciona assim:

  • Se dbo.Customerstem uma coluna chamada SomeCol, em seguida, SomeColem SomeCol AS MyTestresolverá como dbo.Customers.SomeCol;

  • se a tabela não possuir essa coluna, a referência ainda será válida, porque agora será resolvida como dummy.SomeCol: as dummycolunas podem ser referenciadas nesse contexto.

Você pode especificar várias colunas "sobressalentes" dessa maneira. O truque é não usar o alias da tabela para essas colunas (que é uma prática desaprovada na maioria das situações, mas, nesse caso, a omissão do alias da tabela ajuda a resolver o problema).

Se a tabela for usada em uma junção e a outra tabela tiver sua própria SomeCol, você provavelmente precisará usar a consulta acima como uma tabela derivada antes de usá-la na junção para manter o truque funcionando, algo como isto:

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
fonte
1
Gostaria de saber se o compilador SQL é apenas um pouquinho complicado. Super legal o que você pode fazer.
Max Vernon
9

Uma maneira de fazer isso é verificar a existência das colunas e criar o SQL dinâmico com base na existência ou não dessa coluna.

Sem o SQL dinâmico, o SQL Server tentará avaliar se a coluna existe ou não antes mesmo de executar a declaração, resultando em um erro.

No entanto, isso significa que você terá duas consultas para escrever e potencialmente alterar no futuro. Mas não acredito que você deva realmente direcionar SELECTdeclarações para colunas que podem não 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
fonte
Sim, faz sentido, no entanto, deve estar em uma única declaração. Por fim, estou procurando provavelmente alguma função mágica do sistema que não exista.
Carson Reinke
4

Você pode usar algum XML para consultar colunas que possam estar na tabela.

Crie um XML a partir de todas as colunas por linha em uma aplicação cruzada e extraia o valor usando a values()função

Nesta consulta, o ID é conhecido, portanto, obtenha-o diretamente da tabela. Col1 e Col2 podem estar lá ou não, para obtê-los usando o 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
fonte
-1

Minha abordagem difere apenas ligeiramente das outras. Prefiro usar o sistema para isso e simplesmente obter uma contagem, porque você pode atribuir a contagem de colunas a uma variável na parte superior de uma consulta e, em seguida, optar por continuar ou não com base nisso. A desvantagem disso é ... se você tiver o mesmo nome de coluna em várias tabelas, não tem certeza de que a coluna existe na tabela que você deseja consultar. No entanto, a técnica também funciona em tabelas específicas, pois você só deseja obter uma contagem.

O "problema" de solicitá-lo especificamente é - o problema que você está enfrentando. Em geral, se um valor NULL causar problemas, encontre outra maneira de verificar a existência. Essa é uma maneira de fazer isso sem arriscar perturbar o servidor.

SELECT COUNT(*) FROM sys.columns WHERE sys.columns.name = 'FarmID'
jinzai
fonte
1
Por que não usar o sysobjectstambém na sua consulta para verificar se a tabela específica possui essa coluna?
ypercubeᵀᴹ
Sim ... eu mencionei que isso poderia ser feito ... você pode até fazer o mesmo na tabela específica sobre a qual está consultando ... Eu apenas mostrei o formato geral para usar COUNT porque COUNT não erro quando COUNT é ZERO e ... suponho que devo mencione que você também pode atribuí-lo a uma variável. (por exemplo, SELECT COUNT (*) AS myVarName…)
jinzai
1
Não vejo como isso seria melhor do que a consulta de Mark. SELECT 1 ...também não erro.
ypercubeᵀᴹ
Eu não disse que era melhor, mas é uma maneira muito mais simples de obter o mesmo resultado. SELECT 1 pode não ter erro, mas não é a mesma coisa que COUNT. SELECT retorna ALGO ... mesmo que seja NULL. COUNT apenas precisa retornar um único número. Dessa forma, seria mais rápido e mencionei que a contagem pode ser usada posteriormente.
jinzai
Se você precisar da contagem ok. Mas EXISTS (SELECT ...)geralmente é mais rápido do que (SELECT COUNT(*) ...)o contrário.
ypercubeᵀᴹ
-3

Se eu entendi direito ...

Você pode usar a consulta como abaixo e agir de acordo com a contagem ... Se a contagem for> 1, significa que você tem a coluna nessa tabela e a contagem = 0, então você não tem essa coluna nessa mesa

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

Sai
fonte
4
Não, você não entendeu corretamente. Além disso, verifique O caso contra vistas INFORMATION_SCHEMA de @AaronBertrand
Kin Shah