Márcio Gomes vs SQL – Fight 2

Se você tem menos de 18 anos, ou sofre de problemas cardíacos, ou não tem certeza sobre suas convicções religiosas, ou não manja de UPDATE, então NÃO PROSSIGA.

Eu e o Márcio Gomes, em uma discussão sobre SQL (o que nunca dá bons resultados), nos deparamos com o bendito do ‘WHERE 1=1’, que em um momento de insanidade transformamos em ‘WHERE SQRT(SQUARE(1 + 1)) * 3 = 6’. Para testar o fruto desta capacidade criativa, optamos por comparar planos de execução, de uma query sem WHERE, outra com o ‘WHERE 1=1’ e outra com ‘WHERE SQRT(SQUARE(1 + 1)) * 3 = 6’.

Veja como ‘WHERE SQRT(SQUARE(1 + 1)) * 3 = 6’ tem menor custo que as outras consultas: #sqn

Enganamos o plano de execução do SQL? Onde está seu deus agora?

Para quem duvida, segue o script:

CREATE TABLE T (ID INT NOT NULL, TXT CHAR(32) NOT NULL)

;WITH CTE (ID) AS (
SELECT 1
UNION ALL
SELECT ID + 1 FROM CTE WHERE ID < 100000
)

INSERT INTO T
SELECT ID, REPLICATE('0', 32) FROM CTE
OPTION (MAXRECURSION 0)

UPDATE T SET ID = 1
UPDATE T SET ID = 1 WHERE 1 = 1
UPDATE T SET ID = 1 WHERE SQRT(SQUARE(1 + 1)) * 3 = 6

Márcio Gomes vs SQL – Fight 1

Olá pessoas,

A partir desta semana, o Márcio Gomes terá um espaço para expressar todo seu amor ao SQL, então boa diversão!

SQL Go To Hell… PLZ!

por Márcio Gomes

Antes de tudo eu gostaria de agradecer ao Paulo pelo espaço cedido no SQL FROM HELL, não existe lugar melhor na internet para eu expressar meus sentimentos em relação ao SQL.

Que eu ODEIO SQL todo mundo sabe, no entanto o que pouca gente sabe é que o SQL ME ODEIA! Sim… essa relação de guerra e paz começou em 2002 e segue até hoje e posso garantir com todas as letras, o SQL não perde uma única chance de me sacanear, quando tudo parece que vai dar certo de repente algo sobrenatural acontece e somente recorrendo à ajuda do Paulo combinado com duas velas pretas na encruzilhada as coisas voltam ao normal.

Hoje como é a estreia dos relatos sobre a guerra cotidiana tentando sobreviver aos ataques terroristas do SQL Server deixo um print screen mostrando que o .NET Framework concorda comigo.

Exatamente isto que você leu meu caro amigo “NO BANDO DE DADOS” (sic), o SQL é uma espécie de clã, horda, bando de dados armados que criam batalhas digitais que nos post futuros serão descritas aqui no SQL FROM HELL!

Um belo exemplo de uma Query From Hell

E ai pessoas, fazendo um pingback de um link que o @zavaschi passou ontem no twitter (RT original do @dsfnet) que realmente mostra um belo exemplo de uma Query From Hell:

http://thedailywtf.com/Articles/The-Query-of-Despair.aspx

Se alguem quiser encaminhar outros exemplos para publicar no blog, fiquem a vontade!

Query From Hell? – SQL Server Spatial Data e um cubo 3D

Quando lançaram a feature de Spatial Data do SQL Server fiquei impressionado com as possibilidades de trabalhar com tipos geométricos e geográficos no SQL Server, principalmente pelo fato de que naquele ano, eu estava na equipe de desenvolvimento de um projeto de geolocalização e roteirização, onde usávamos bastante features como Spatial Data e aplicações com mapas (Web e Mobile).

Só que desde aquela época, via que além das propriedades X e Y, existia a propriedade Z, que não afetava em nada os métodos para calculo de distancia e outros. Pois, logo que existe X Y Z, seria possível gravar dados geométricos não somente 2D no banco de dados, mas também 3D… Mas não, a propriedade Z realmente não permitia isso.

Para matar minha vontade de gerar “algo em 3D” no SQL Server, peguei uma biblioteca de uma aplicação Windows Forms do site (Visual C# Kicks – http://www.vcskicks.com), adequei a biblioteca para ser SAFE no SQL Server e trabalhar com Spatial Data, assim criei um pequeno monstro:

Infelizmente não consegui implementar 100% o algoritmo de rotação do objeto 3D que funcionou perfeitamente na aplicação Windows (http://www.vcskicks.com/3d-graphics-improved.php). Quem quiser conhecer a biblioteca modificada, pode conferi-la abaixo:

Continuar lendo

Uma imaginação bem fértil para usar LIKE

O que Joãozinho faz quando ele não quer usar o IN e tem uma imaginação bem fértil para usar LIKE?

Ele torna um simples IN:

SELECT * FROM Clientes
WHERE Codigo IN (12, 34, 19)

Em um LIKE “bem” elaborado:

SELECT * FROM Clientes
WHERE ' 12, 34, 19,' LIKE '% ' + CAST(Codigo AS VARCHAR) + ',%'

Agora que Joãozinho esta feliz, vamos ver o plano de execução?

Como Joãozinho conseguiu transformar um simples “clustered index seek” em um “clustered index scan” com custo aproximadamente 99x maior que a consulta com IN, digamos que esta na hora do Joãozinho parar de inventar moda… 🙂

Query 4 Fun – SQL Calendar!!!

Eu sou aquele tipo de idealista que acredita que é possível fazer qualquer coisa, até mesmo sem a ferramenta certa e que o único limite que existe é o “ambiente”, no caso da informática, o hardware. Gosto de afirmar isso aos meus alunos e companheiros, e como desafio, gosto de propor a criação de algo inusitado (utilizando os recursos que estão aprendendo), provando assim que não sou o único louco deste mundo!

Para demonstrar o que seria este algo inusitado, meu exemplo de como utilizar CTE, PIVOT e funções de data para criar algo para o qual o SQL “não nasceu”, exemplo:

DECLARE @Mes INT
,@Ano INT

SELECT @Mes = 8
,@Ano = 2010

;WITH CTE (Data) AS
(
  SELECT DATEADD(year, @Ano - 1900, DATEADD(month, @Mes - 1, 0))
  UNION ALL
  SELECT Data+1
  FROM CTE
  WHERE MONTH(Data + 1) = @Mes
)
SELECT
  [1] AS [Domingo]
, [2] AS [Segunda-Feira]
, [3] AS [Terça-Feira]
, [4] AS [Quarta-Feira]
, [5] AS [Quinta-Feira]
, [6] AS [Sexta-Feira]
, [7] AS [Sábado]
FROM (
	SELECT DAY(Data) AS Dia, DATEPART(weekday, Data) DiaSemana, DATEPART(week, Data) Semana
	FROM CTE
) AS Datas
PIVOT
(
	MAX(Dia) FOR DiaSemana
	IN ([1], [2], [3], [4], [5], [6], [7])
) AS A

E como resultado, o SQL Calendar:
SQL Calendar

Query From Hell – Como 100 é menor que 11???

Você sabia que 100 é maior que 10, mas 100 é menor que 11, e mesmo que 1000 seja maior que 100, 1000 ainda será menor que 99? Não entendeu? Acha que estou ficando louco, então veja bem isso:
Comparação de texto Comparação de valores numéricos

Não se trata de um bug, pois o algoritmo de comparação de textos compara caracteres por caracteres iniciando pela esquerda, isso quer dizer que “ANA BEATRIZ” é menor que “ANA PAULA” visto que “B” é menor que “P” (a mesma comparação utilizada pelo ORDER BY).

O fato do texto “10” ser menor que “9” pode soar meio estranho, mas o primeiro caractere dos dois textos diz que “1” é menor que “9”, logo o texto “10” é menor que “9”, mesmo que o valor numérico 10 seja menor que o valor numérico 9.

Para demonstrar melhor este fato, segue um esboço de como funciona o algoritmo de comparação de texto:

Muitos problemas podem surgir quando trabalhamos com campos do tipo texto que deveriam ser tratado como numéricos, como no caso da utilização da funções MIN/MAX ou até mesmo no ORDER BY, que pode ter resultados bem diferentes da comparação feita com valores numéricos:

Query From Hell? – Comparando a estrutura de tabelas diferentes

Boa noite pessoal,

Finalmente tive tempo para respirar e escrever um artigo neste novo ano. Para este artigo, trago algo que normalmente me deparo quando se trabalha com muitas versões de bancos de dados, que é comparar a estrutura das tabelas entre versões de banco de dados ou até mesmo quando existem tabelas semelhantes dentro de um mesmo banco de dados. Desta forma, preparei algumas consultas que relacionam as colunas de tabelas semelhantes que estejam ou não no mesmo banco de dados, mas o conceito é facilmente aplicado para outros tipos de objetos como tabelas, chaves, índices e relacionamentos.

Para situações mais complexas recomendo a utilização de ferramentas como o Visual Studio Database Edition ou o RedGate SQL Compare e Data Compare. Mas como nem sempre é possível contar com estes recursos por não serem ferramentas gratuitas, sempre é bom ter algumas consultas para situações críticas ou verificações mais urgentes.

1. Colunas de uma tabela

Para identificar as colunas de uma tabela, é possível utilizar tanto INFORMATION_SCHEMA.COLUMNS ou sys.columns ou até mesmo sys.syscolumns, conforme abaixo:

SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE [TABLE_NAME] = 'nome da tabela'
SELECT *
FROM sys.columns
WHERE [object_id] = OBJECT_ID('nome da tabela')
SELECT *
FROM sys.syscolumns
WHERE [id] = OBJECT_ID('nome da tabela')

2. Colunas diferentes entre duas tabelas

Nesta primeira consulta teremos como resultado as colunas diferentes entre duas tabelas, utilizando ‘FULL OUTER JOIN’ (ver artigo), tratando as diferenças no momento da seleção:

USE [AdventureWorks]
GO
SELECT
 ISNULL(A.COLUMN_NAME, B.COLUMN_NAME)
 AS [Tabela]
,CASE WHEN A.COLUMN_NAME IS NULL
 THEN 'SOMENTE EM ' + B.TABLE_NAME
 WHEN B.COLUMN_NAME IS NULL
 THEN 'SOMENTE EM ' + A.TABLE_NAME
 ELSE 'OK' END
 AS [Resultado]
FROM
(
 SELECT COLUMN_NAME, TABLE_NAME
 FROM INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'Address'

) A
FULL OUTER JOIN
(
 SELECT COLUMN_NAME, TABLE_NAME
 FROM INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'AddressType'

) B
ON A.COLUMN_NAME = B.COLUMN_NAME
GO

3. Colunas diferentes quando se utiliza schemas

Quando se utiliza a view sys.columns, informe dentro da função OBJECT_ID o nome do schema seguido do nome da tabela. No exemplo, é utilizado o schema ‘Person’:

USE [AdventureWorks]
GO
SELECT
 ISNULL(A.[name], B.[name])
 AS [Coluna]
,CASE WHEN A.[name] IS NULL
 THEN 'SOMENTE EM ' + B.[table]
 WHEN B.[name] IS NULL
  THEN 'SOMENTE EM ' + A.[table]
 ELSE 'OK' END
 AS [Resultado]
FROM
(
 SELECT [name]
 ,'[Person].[Address]' as [table]
 FROM sys.columns
 WHERE [object_id] = OBJECT_ID('[Person].[Address]')
) A
FULL JOIN
(
 SELECT [name]
 ,'[Person].[AddressType]' as [table]
 FROM sys.columns
 WHERE [object_id] = OBJECT_ID('[Person].[AddressType]')
) B
ON A.[name] = B.[name]
GO

Quando se utiliza INFORMATION_SCHEMA.COLUMNS, é necessário especificar ‘TABLE_SCHEMA’:

USE [AdventureWorks]
GO
SELECT
 ISNULL(A.COLUMN_NAME, B.COLUMN_NAME)
 AS [Tabela]
,CASE WHEN A.COLUMN_NAME IS NULL
 THEN 'SOMENTE EM ' + B.TABLE_NAME
 WHEN B.COLUMN_NAME IS NULL
 THEN 'SOMENTE EM ' + A.TABLE_NAME
 ELSE 'OK' END
 AS [Resultado]
FROM
(
 SELECT COLUMN_NAME
 ,TABLE_SCHEMA + '.' + TABLE_NAME AS TABLE_NAME
 FROM INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'Address'
 AND TABLE_SCHEMA = 'Person'
) A
FULL OUTER JOIN
(
 SELECT COLUMN_NAME
 ,TABLE_SCHEMA + '.' + TABLE_NAME AS TABLE_NAME
 FROM INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'AddressType'
 AND TABLE_SCHEMA = 'Person'
) B
ON A.COLUMN_NAME = B.COLUMN_NAME
GO

4. Colunas diferentes de tabelas de bancos de dados diferentes

A consulta para a situação mais comum no meu dia-a-dia, dois bancos de dados de versões diferentes. Observe que nos exemplos que é necessário especificar o banco de dados das views.

Utilizando sys.columns:

USE [master]
GO
SELECT
 ISNULL(A.[name], B.[name])
 AS [Coluna]
,CASE WHEN A.[name] IS NULL
 THEN 'SOMENTE EM ' + B.[table]
 WHEN B.[name] IS NULL
  THEN 'SOMENTE EM ' + A.[table]
 ELSE 'OK' END
 AS [Resultado]
FROM
(
 SELECT [name]
 ,'[AdventureWorks2008].[Person].[Address]' as [table]
 FROM [AdventureWorks2008].sys.columns
 WHERE [object_id] = OBJECT_ID('[AdventureWorks2008].[Person].[Address]')
) A
FULL JOIN
(
 SELECT [name]
 ,'[AdventureWorks].[Person].[Address]' as [table]
 FROM [AdventureWorks].sys.columns
 WHERE [object_id] = OBJECT_ID('[AdventureWorks].[Person].[Address]')
) B
ON A.[name] = B.[name]
GO

Utilizando INFORMATION_SCHEMA:

USE [master]
GO
SELECT
 ISNULL(A.COLUMN_NAME, B.COLUMN_NAME)
 AS [Tabela]
,CASE WHEN A.COLUMN_NAME IS NULL
 THEN 'SOMENTE EM ' + B.TABLE_NAME
 WHEN B.COLUMN_NAME IS NULL
 THEN 'SOMENTE EM ' + A.TABLE_NAME
 ELSE 'OK' END
 AS [Resultado]
FROM
(
 SELECT COLUMN_NAME
 , TABLE_CATALOG + '.' + TABLE_SCHEMA + '.' + TABLE_NAME AS TABLE_NAME
 FROM [AdventureWorks2008].INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'Address'
 AND TABLE_SCHEMA = 'Person'
) A
FULL OUTER JOIN
(
 SELECT COLUMN_NAME
 , TABLE_CATALOG + '.' + TABLE_SCHEMA + '.' + TABLE_NAME AS TABLE_NAME
 FROM [AdventureWorks].INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'Address'
 AND TABLE_SCHEMA = 'Person'
) B
ON A.COLUMN_NAME = B.COLUMN_NAME
GO

Eu tinha preparado algumas consultas mais complexas para comparação e merge de bancos, mas creio que somente com o conceito utilizado das consultas acima, seja possível desenvolver consultas para outros tipos de comparação.

Espero que tenham gostado, até o próximo artigo!