SQL no SQL Server

A Ana conseguiu um estágio numa empresa de Teresina, e o sistema de lá guarda tudo no SQL Server, o banco de dados da Microsoft. Ela sabe SQL, aprendeu com o SQLite neste guia, e quer saber o que muda. A resposta: quase tudo o que ela sabe vale igual (SELECT, WHERE, JOIN, GROUP BY…), mas o SQL Server tem o seu próprio dialeto, o T-SQL (Transact-SQL), com outras palavras para algumas tarefas do dia a dia: TOP no lugar de LIMIT, + para juntar textos, GETDATE() para a data de hoje, MERGE para o "insere ou atualiza". Nesta aula ela põe cada consulta do SQLite ao lado da versão do SQL Server.

Você vai aprender
  • O que é o SQL Server, quais edições são gratuitas e onde ele roda.
  • Como subir um SQL Server de teste num contêiner e conversar com ele pelo sqlcmd, o programa de terminal da Microsoft, e o que é o GO.
  • A tradução das consultas que você já sabe: LIMIT × TOP e OFFSET … FETCH, aspas × colchetes, numeração automática, textos, datas, booleanos.
  • MERGE (o UPSERT do SQL Server) e OUTPUT (o RETURNING dele).
  • Os tipos de dados mais usados e as mensagens de erro mais comuns, e o que cada uma quer dizer.
O que roda de verdade nesta página

Os quadros "Resultado" rodam o SQLite de verdade, no seu navegador, como em todo o guia. Os trechos do SQL Server não foram rodados: eles aparecem num quadro de código sem resultado, marcados como ilustrativos, e cada palavra, regra e mensagem de erro citada foi conferida na documentação oficial da Microsoft (learn.microsoft.com). Das mensagens de erro, a documentação dá o modelo (por exemplo, Invalid object name '%.*ls'.); nesta página o %.*ls aparece preenchido com o nome do exemplo.


A ideia: o mesmo idioma, outro sotaque

Se você aprendeu a dirigir num carro, consegue dirigir outro: o volante, os pedais e as marchas estão no mesmo lugar. Só alguns botões mudam de posição, e o farol de um carro liga de um jeito diferente do outro. Com os bancos de dados é igual. O SQL padrão é o volante e os pedais; cada banco tem os seus botões. A aula As Diferenças entre os Bancos mostra o panorama geral. Esta aqui é o manual do "carro" SQL Server.

SQLite (este guia)SQL Server
Quem fazprojeto aberto, domínio públicoMicrosoft
Como funcionao banco é um arquivo; o programa vai junto com o seu aplicativoum servidor: um programa sempre ligado, e os clientes se conectam a ele pela rede (porta 1433 no exemplo da Microsoft)
DialetoSQL do SQLiteT-SQL (Transact-SQL)
Programa de terminalsqlite3sqlcmd
Programa gráficovários (DB Browser for SQLite…)SSMS (SQL Server Management Studio), para Windows

1. As edições e onde ele roda

A versão atual é o SQL Server 2025 (a documentação o chama de 17.x). Para estudar, duas edições não custam nada:

EdiçãoO que a Microsoft diz dela
Express"O banco de entrada, gratuito, ideal para aprender e criar aplicativos de desktop e pequenos servidores." No SQL Server 2025, cada banco pode ter até 50 GB.
Developer (Enterprise Developer e Standard Developer)Todos os recursos das edições pagas, mas licenciada só para desenvolvimento e teste, não para produção.

O SQL Server roda no Windows, no Linux e em contêineres (Docker). Também existe na nuvem da Microsoft, como Azure SQL. O T-SQL desta aula vale para todos esses lugares, com poucas exceções.


2. Instalar e abrir o cliente: o sqlcmd

Um SQL Server de teste num contêiner

O jeito mais rápido de ter um SQL Server para treinar, em qualquer sistema com Docker, é o contêiner oficial. O comando abaixo é o do guia de início rápido da Microsoft (ilustrativo, não rodado aqui):

docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=<senha>" \
   -p 1433:1433 --name sql1 --hostname sql1 \
   -d \
   mcr.microsoft.com/mssql/server:2025-latest
ACCEPT_EULA=Y
Você aceita a licença de uso (EULA). Sem isso, o contêiner não sobe.
MSSQL_SA_PASSWORD=<senha>
A senha do administrador, o usuário sa. A regra padrão: pelo menos 8 caracteres, com três destes quatro grupos: maiúsculas, minúsculas, números e símbolos. Senha fraca faz o contêiner parar.
-p 1433:1433
Abre a porta 1433, a do SQL Server, para os clientes se conectarem.
mssql/server:2025-latest
A imagem do SQL Server 2025. Por padrão, ela cria a edição Developer.

O sqlcmd

O sqlcmd faz para o SQL Server o que o sqlite3 faz para o SQLite: você digita SQL e vê o resultado no terminal. Hoje há duas versões, e a documentação recomenda a nova, escrita em Go, que se instala sozinha: winget install sqlcmd no Windows, brew install sqlcmd no macOS, ou apt-get install sqlcmd no Debian/Ubuntu, depois de cadastrar o repositório da Microsoft. Para conectar:

sqlcmd -S localhost -U sa
-S localhost
O servidor (server) a que se conectar: aqui, o da própria máquina.
-U sa
O usuário (user). Sem -P, o sqlcmd pede a senha na hora, sem mostrá-la na tela, que é o mais seguro.
-C (se precisar)
"Confiar no certificado do servidor". As versões recentes do sqlcmd exigem conexão criptografada; num servidor de teste com certificado próprio, a conexão pode falhar sem essa opção. A documentação também cita -No, que torna a criptografia opcional.

O prompt 1> e o GO

Conectado, o sqlcmd mostra o prompt 1>. E aqui está a primeira grande diferença para o sqlite3: o ponto e vírgula não faz a consulta rodar. Você escreve um ou mais comandos e, numa linha sozinha, digita GO. Só então o sqlcmd manda tudo para o servidor:

USE loja;
SELECT TOP (3) nome, preco FROM produtos ORDER BY preco DESC;
GO

O conjunto de comandos enviado de uma vez se chama lote (batch). A documentação deixa claro: "GO não é um comando do Transact-SQL; é um comando reconhecido pelo sqlcmd" e pelo editor do SSMS. O servidor nunca vê a palavra GO. Para sair, digite QUIT (ou EXIT). Para rodar uma consulta só e sair, existe a opção -Q: sqlcmd -S localhost -U sa -Q "SELECT @@VERSION".

Tarefasqlite3sqlcmd
Abrirsqlite3 loja.dbsqlcmd -S servidor -U usuario (e depois USE loja;, ou a opção -d loja)
Rodar o que foi digitadoo ; no fimGO numa linha sozinha
Rodar um arquivo.read arquivo.sqlsqlcmd … -i arquivo.sql
Uma consulta e sairsqlite3 loja.db "SELECT …"sqlcmd … -Q "SELECT …"
Sair.quitQUIT ou EXIT

No Windows, muita gente usa o SSMS (SQL Server Management Studio), um programa gráfico gratuito da Microsoft; a versão atual, o SSMS 22, é instalada pelo instalador do Visual Studio. No editor do SSMS o GO também vale.

E no SQLite? Aqui há uma curiosidade. O GO não é SQL, então no editor do guia ele dá erro:

SELECT nome FROM clientes WHERE id = 1
GO

Mas o programa sqlite3, no terminal, aceita: a documentação dele diz que a palavra GO (ou uma barra /) numa linha sozinha também termina um comando, por compatibilidade com o SQL Server e o Oracle. A Ana testou, digitando a consulta sem ; e depois GO:

$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> SELECT nome FROM clientes WHERE id = 1
   ...> GO
José Ribamar Silva
sqlite> .quit

É o programa de terminal que trata o GO, como no SQL Server; o banco (o motor do SQLite, que o editor usa) nunca o vê.


3. Limitar linhas: LIMIT × TOP e OFFSET … FETCH

Os 3 produtos mais caros. No SQLite (aula LIMIT e OFFSET):

SELECT nome, preco
FROM produtos
ORDER BY preco DESC
LIMIT 3;

No SQL Server, o LIMIT não existe. A palavra é TOP, e ela vem logo depois do SELECT, não no fim:

SELECT TOP (3) nome, preco
FROM produtos
ORDER BY preco DESC;
TOP (3)
"Os 3 primeiros". Os parênteses são opcionais num SELECT com número fixo (TOP 3 funciona), mas a Microsoft recomenda usá-los sempre, porque em INSERT, UPDATE, MERGE e DELETE eles são obrigatórios.
ORDER BY preco DESC
Sem ORDER BY, o TOP devolve 3 linhas "em ordem indefinida", diz a documentação: quaisquer 3. Use sempre os dois juntos.

O TOP tem dois extras que o LIMIT não tem: TOP (10) PERCENT (os primeiros 10% das linhas) e WITH TIES (inclui também as linhas empatadas com a última, o que pode trazer mais linhas do que o número pedido).

Pular linhas: OFFSET … FETCH

Para a "página 2" (pular 3, mostrar 3), o SQLite usa LIMIT 3 OFFSET 3:

SELECT nome, preco
FROM produtos
ORDER BY preco DESC
LIMIT 3 OFFSET 3;

O SQL Server (desde a versão 2012) usa a forma do padrão SQL, que fica dentro do ORDER BY:

SELECT nome, preco
FROM produtos
ORDER BY preco DESC
OFFSET 3 ROWS
FETCH NEXT 3 ROWS ONLY;
OFFSET 3 ROWS
Pula 3 linhas. Faz parte do ORDER BY: sem ORDER BY, não há OFFSET.
FETCH NEXT 3 ROWS ONLY
"Traga as próximas 3 linhas, só". NEXT e FIRST são sinônimos, assim como ROW e ROWS.

A documentação recomenda OFFSET … FETCH (e não TOP) para paginar, e avisa que os dois não podem ser misturados na mesma consulta. O SQLite não conhece nenhuma das duas formas:

SELECT TOP (3) nome
FROM produtos;

no such function: TOP: com o parêntese logo depois, o SQLite achou que TOP era uma função. (Sem os parênteses, SELECT TOP 3 nome, a mensagem é near "3": syntax error.) E o OFFSET 3 ROWS:

SELECT nome
FROM produtos
ORDER BY preco DESC
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;

Experimente: a Ana copiou esta consulta do sistema do estágio (é T-SQL) e quer rodá-la no SQLite: os 5 clientes mais novos (nascimento mais recente). Traduza o TOP (5) para o jeito do SQLite.

SELECT TOP (5) nome, nascimento
FROM clientes
ORDER BY nascimento DESC;
SELECT nome, nascimento
FROM clientes
ORDER BY nascimento DESC
LIMIT 5;

4. Nomes entre colchetes, e maiúsculas que não importam

No SQL Server, um nome que não seria aceito sozinho (uma palavra reservada como order, um nome com espaço) vai entre colchetes: [order], [nome do cliente]. As aspas duplas também servem, com a configuração QUOTED_IDENTIFIER ligada, que é o padrão da maioria das conexões. Você vai ver colchetes em todo lugar, porque o SSMS os põe sozinho nos scripts que gera.

O SQLite aceita as duas formas, justamente para ser compatível:

SELECT [nome], "cidade"
FROM clientes
WHERE id = 1;

Nos dois bancos, texto vai entre aspas simples: 'Teresina'. No SQL Server, um texto com caracteres fora do inglês (acentos, emojis) costuma ir com um N na frente, N'Conceição': a documentação explica que sem o prefixo N o texto é convertido para a página de código padrão do banco, que pode não reconhecer alguns caracteres.

Maiúsculas e minúsculas: a collation

No SQL Server, se 'ana' é igual a 'ANA' depende da collation (as regras de comparação de texto), escolhida na instalação conforme o idioma do sistema. Para um Windows em português do Brasil, a tabela da Microsoft indica a collation Latin1_General_CI_AS. O CI quer dizer case-insensitive: maiúsculas e minúsculas são iguais, tanto no = quanto no LIKE. (O AS quer dizer accent-sensitive: 'Jose' e 'José' continuam diferentes.) Por isso o SQL Server não precisa do ILIKE do PostgreSQL.

No SQLite é outra história: o = diferencia maiúsculas, e o LIKE ignora maiúsculas só nas letras sem acento (aula LIKE):

SELECT nome FROM clientes WHERE nome LIKE 'jos%';
SELECT nome FROM clientes WHERE nome LIKE 'JOSÉ%';

'jos%' achou o José; 'JOSÉ%' não achou ninguém, porque o SQLite não sabe que É e é são a mesma letra. Num SQL Server com collation CI, as duas achariam. O LIKE do SQL Server também tem um curinga a mais: [ ], "um caractere qualquer deste conjunto", como em LIKE '[A-C]%' (nomes que começam com A, B ou C), e [^ ], "qualquer caractere fora do conjunto".


5. Numeração automática: IDENTITY

No SQLite, uma coluna INTEGER PRIMARY KEY se numera sozinha (aula AUTOINCREMENT). No SQL Server, a numeração é a propriedade IDENTITY:

CREATE TABLE categorias (
    id        INT IDENTITY(1, 1) PRIMARY KEY,
    nome      NVARCHAR(50) NOT NULL,
    descricao NVARCHAR(200)
);
IDENTITY(1, 1)
Começa em 1 (a "semente", seed) e soma 1 a cada linha (o incremento). Sem os números, o padrão é (1, 1).
Buracos na numeração
A documentação avisa que podem aparecer: um INSERT que falha ou é desfeito "gasta" o número, e um reinício do servidor pode pular valores. Nunca conte com ids sem buracos, em nenhum banco.

Para saber o id que acabou de ser criado, o SQL Server tem a função SCOPE_IDENTITY() e a cláusula OUTPUT (seção 11). E inserir um id escolhido por você numa coluna IDENTITY dá erro, a não ser que você ligue antes SET IDENTITY_INSERT categorias ON:

Cannot insert explicit value for identity column in table 'categorias' when IDENTITY_INSERT is set to OFF.

"Não é possível inserir um valor explícito na coluna de identidade da tabela 'categorias' quando IDENTITY_INSERT está desligado." No SQLite, isso é permitido: INSERT INTO categorias (id, nome) VALUES (20, 'Pet') funciona, se o 20 estiver livre.


6. Textos: + × ||, LEN × length, ISNULL × IFNULL

Para juntar textos, o SQLite usa ||. O SQL Server usa, desde sempre, o +, e a função CONCAT(); o || só chegou no SQL Server 2025. Os três tratam o NULL de jeitos diferentes. Primeiro, o SQLite, com o cliente 7, que não tem email:

SELECT nome || ' - ' || email AS com_barras,
       CONCAT(nome, ' - ', email) AS com_concat
FROM clientes
WHERE id IN (6, 7);

Com ||, um pedaço NULL deixa tudo NULL; com CONCAT(), o NULL vira texto vazio. A versão do SQL Server:

SELECT nome + ' - ' + email AS com_mais,
       CONCAT(nome, ' - ', email) AS com_concat
FROM clientes
WHERE id IN (6, 7);

No SQL Server, + com NULL dá NULL (o padrão da sessão; a configuração CONCAT_NULL_YIELDS_NULL muda isso, mas não mexa nela). O || do SQL Server 2025, diz a documentação, sempre dá NULL se algum pedaço for NULL, qualquer que seja a configuração. E o CONCAT() trata o NULL como texto vazio, como no SQLite.

Cuidado com o caminho inverso: no SQLite, + é sempre soma de números. Juntar textos com + não dá erro, dá zero:

SELECT 'Ana' + ' Lima' AS tentativa;

As funções de texto que mais mudam de nome:

TarefaSQLiteSQL Server
Tamanho do textolength(t)LEN(t): atenção, não conta os espaços do fim (para contar bytes, DATALENGTH)
Pedaço do textosubstr(t, início, tamanho)SUBSTRING(t, início, tamanho)
Posição de um trechoinstr(t, trecho)CHARINDEX(trecho, t): a ordem dos argumentos é invertida
Valor no lugar de NULLifnull(x, y) ou coalesce(x, y)ISNULL(x, y) ou COALESCE(x, y)
Juntar os textos de um grupogroup_concat(t, ', ') ou string_agg(t, ', ')STRING_AGG(t, ', ') (desde o SQL Server 2017)
Tirar espaços das pontastrim(t)TRIM(t) (desde o SQL Server 2017)
Maiúsculasupper(t)UPPER(t)
SELECT length('Ana ')                AS tamanho,
       instr('José Ribamar', 'Rib')   AS posicao,
       substr('José Ribamar', 6, 3)   AS pedaco,
       coalesce(NULL, '(sem email)')  AS troca_null;

Repare no tamanho: o SQLite contou 4, porque 'Ana ' tem um espaço no fim. O LEN('Ana ') do SQL Server daria 3. Já COALESCE é a escolha que funciona nos dois (e em todos os outros bancos): prefira-o ao ISNULL e ao ifnull.


7. Datas

O SQLite guarda datas como texto ('2024-01-05') e faz as contas com funções como date(), julianday() e strftime() (aula Funções de Data). O SQL Server tem tipos próprios para datas, como date (só a data) e datetime2 (data e hora), e outras funções. O prazo de entrega de 7 dias e o ano do pedido 1, no SQLite:

SELECT id, data,
       date(data, '+7 days')                 AS prazo,
       strftime('%Y', data)                  AS ano,
       julianday(entregue_em) - julianday(data) AS dias
FROM pedidos
WHERE id = 1;

O mesmo, no SQL Server:

SELECT id, data,
       DATEADD(day, 7, data)              AS prazo,
       YEAR(data)                         AS ano,
       DATEDIFF(day, data, entregue_em)   AS dias,
       CONVERT(varchar(10), data, 103)    AS data_br
FROM pedidos
WHERE id = 1;
TarefaSQLiteSQL Server
Agora (data e hora)datetime('now')GETDATE(), SYSDATETIME() ou CURRENT_TIMESTAMP
Somar diasdate(d, '+7 days')DATEADD(day, 7, d)
Dias entre duas datasjulianday(b) - julianday(a)DATEDIFF(day, a, b)
Ano de uma datastrftime('%Y', d)YEAR(d) ou DATEPART(year, d)
Data no formato brasileirostrftime('%d/%m/%Y', d)CONVERT(varchar(10), d, 103) (o estilo 103 é dd/MM/yyyy) ou FORMAT(d, 'dd/MM/yyyy')

Um detalhe do DATEDIFF: a documentação diz que ele conta quantas fronteiras da unidade foram cruzadas entre as duas datas. DATEDIFF(year, '2024-12-31', '2025-01-01') dá 1, embora só tenha passado um dia. As funções de data do SQLite não existem lá, e vice-versa: GETDATE() no SQLite dá no such function: GETDATE.


8. Verdadeiro e falso: bit

A coluna vip da Loja Aurora guarda 0 ou 1. No SQL Server, o tipo certo para isso é bit, que a documentação define como "um inteiro que pode valer 1, 0 ou NULL", usado para guardar valores booleanos. Os textos 'TRUE' e 'FALSE' podem ser convertidos para 1 e 0. Na consulta, compare com o número: WHERE vip = 1, que funciona igual no SQLite:

SELECT COUNT(*) AS vips
FROM clientes
WHERE vip = 1;

9. Divisão de inteiros

Aqui os dois bancos concordam: inteiro dividido por inteiro dá inteiro, e a parte quebrada é cortada. A documentação do SQL Server diz exatamente isso. Para ter casas decimais, um dos lados precisa ser decimal:

SELECT 7 / 2   AS inteiros,
       7 / 2.0 AS com_decimal,
       1 / 0   AS por_zero;

A diferença está na última coluna: no SQLite, dividir por zero dá NULL, sem erro. No SQL Server, dá o erro 8134: Divide by zero error encountered. ("encontrado erro de divisão por zero").


10. Inserir ou atualizar: MERGE

A Ana quer renomear a categoria 8 para "Jardim e Quintal", e criá-la se não existir. No SQLite, é o INSERT … ON CONFLICT … DO UPDATE:

INSERT INTO categorias (id, nome, descricao)
VALUES (8, 'Jardim e Quintal', 'Ferramentas e artigos de jardinagem')
ON CONFLICT (id) DO UPDATE SET nome = excluded.nome;
SELECT * FROM categorias WHERE id = 8;

O SQL Server não tem ON CONFLICT. A ferramenta dele é o MERGE ("mesclar"): você junta a tabela de destino com uma "origem" (aqui, uma linha escrita na hora com VALUES) e diz o que fazer quando há par e quando não há:

MERGE categorias AS destino
USING (VALUES (8, N'Jardim e Quintal', N'Ferramentas e artigos de jardinagem'))
      AS origem (id, nome, descricao)
ON destino.id = origem.id
WHEN MATCHED THEN
    UPDATE SET nome = origem.nome
WHEN NOT MATCHED THEN
    INSERT (nome, descricao) VALUES (origem.nome, origem.descricao);
MERGE categorias AS destino
A tabela que vai mudar.
USING (VALUES (…)) AS origem (id, nome, descricao)
De onde vêm os dados novos: uma tabela de uma linha só, com nomes para as colunas.
ON destino.id = origem.id
Como saber se a linha já existe: igual ao ON de um JOIN.
WHEN MATCHED THEN UPDATE …
"Quando achou par": atualiza.
WHEN NOT MATCHED THEN INSERT …
"Quando não achou": insere. O id fica de fora porque, com IDENTITY, quem escolhe o número é o banco.
; no fim
Obrigatório. A documentação diz que o MERGE exige o ponto e vírgula, e sem ele dá o erro 10713: A MERGE statement must be terminated by a semi-colon (;).

Num sistema com muitos usuários ao mesmo tempo, a documentação sugere a dica WITH (HOLDLOCK) (MERGE categorias WITH (HOLDLOCK) AS destino …) para evitar que duas pessoas insiram a mesma chave ao mesmo tempo. Transações e travas são assunto do Nível 3 (Transações).


11. Ver o que mudou: RETURNING × OUTPUT

O SQLite (desde a versão 3.35) tem o RETURNING, que devolve as linhas que um INSERT, UPDATE ou DELETE mexeu. Ótimo para saber o id que acabou de ser criado:

INSERT INTO categorias (nome, descricao)
VALUES ('Pet', 'Produtos para animais')
RETURNING id, nome;

No SQL Server, a cláusula é OUTPUT, com dois "prefixos": INSERTED (os valores depois da mudança) e DELETED (os valores de antes). E ela fica num lugar diferente: no INSERT, antes do VALUES:

INSERT INTO categorias (nome, descricao)
OUTPUT INSERTED.id, INSERTED.nome
VALUES (N'Pet', N'Produtos para animais');

UPDATE produtos
SET preco = preco * 1.1
OUTPUT DELETED.preco AS preco_antigo, INSERTED.preco AS preco_novo
WHERE id = 1;

O UPDATE mostra uma vantagem do OUTPUT: o preço antigo e o novo na mesma linha. No SQLite, o RETURNING só mostra o valor novo:

UPDATE produtos
SET preco = preco * 1.1
WHERE id = 1
RETURNING id, nome, preco;

12. Os tipos de dados

O SQLite é flexível com tipos (aula Os Tipos no SQLite). O SQL Server é rigoroso: cada coluna tem um tipo exato, e um valor que não cabe dá erro. A Loja Aurora no SQL Server ficaria mais ou menos assim:

ColunaNo SQLiteNo SQL ServerPor quê
idINTEGER PRIMARY KEYINT IDENTITY(1,1) PRIMARY KEYnumeração automática
nomeTEXTNVARCHAR(100)texto com acentos, até 100 caracteres (o N é de Unicode)
descricao longaTEXTNVARCHAR(MAX)texto sem limite prático
precoREALDECIMAL(10,2)dinheiro: número exato com 2 casas, sem as "caudas" do ponto flutuante
dataTEXT (ISO)DATEtipo de data de verdade
data e horaTEXTDATETIME2o tipo moderno de data e hora
vipINTEGER (0 ou 1)BIT0, 1 ou NULL

Converter um tipo no outro: CAST(x AS INT) funciona nos dois. O SQL Server tem também CONVERT(tipo, x, estilo) e TRY_CAST, que devolve NULL em vez de dar erro quando a conversão falha.


13. Duas regras mais rígidas

O GROUP BY não aceita coluna solta

Na aula Agrupando por Mais de uma Coluna, você viu que o SQLite aceita, calado, uma coluna que não está no GROUP BY nem numa função de agregação, e mostra o valor de uma linha qualquer:

SELECT estado, cidade, COUNT(*) AS clientes
FROM clientes
GROUP BY estado;

O SQL Server recusa, com o erro 8120:

Column 'clientes.cidade' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

"A coluna 'clientes.cidade' é inválida na lista do SELECT porque não está numa função de agregação nem na cláusula GROUP BY." É um erro que ajuda: no SQLite, o resultado acima diz que Teresina tem 14 clientes, o que é falso.

Tipos não se misturam à força

No SQLite, '12' + 1 dá 13, e um texto qualquer vira 0 numa soma. No SQL Server, uma conversão impossível é erro, o 245: Conversion failed when converting the varchar value 'abc' to data type int. ("a conversão falhou ao converter o valor varchar 'abc' para o tipo int").


Formas que funcionam nos dois

Se você escreve SQL que precisa rodar no SQLite e no SQL Server, algumas escolhas evitam a tradução:

Em vez de…Use…Por quê
ifnull / ISNULLCOALESCE(x, y)padrão SQL, existe nos dois
|| / +CONCAT(a, b, c)existe nos dois (no SQLite desde a 3.44), e trata NULL igual
LIMIT / TOPnão há forma comumo SQLite não tem OFFSET … FETCH; aqui não tem jeito, é traduzir
"nome" / [nome]nomes simples, minúsculos, sem espaço e sem palavra reservadanão precisam de aspas em nenhum banco
colunas soltas no GROUP BYtoda coluna do SELECT no GROUP BY ou numa agregaçãoo SQLite aceita, o SQL Server não
CAST(x AS INTEGER)CAST(x AS INT)o SQLite entende INT como inteiro, e o SQL Server só conhece INT

Quando dá errado

As mensagens do SQL Server vêm em inglês, com um número. No sqlcmd elas aparecem como Msg 208, Level 16, State 1, seguidas do texto. O número é o que você procura na documentação. Os textos abaixo são os modelos da documentação da Microsoft, preenchidos com o exemplo (ilustrativos).

Usou o LIMIT do SQLite no SQL Server (erro 102)
SELECT nome FROM produtos ORDER BY preco DESC LIMIT 3;
-- Incorrect syntax near 'LIMIT'.

"Sintaxe incorreta perto de 'LIMIT'": o SQL Server não conhece a palavra. É a mesma família do near "…": syntax error do SQLite. Como consertar: SELECT TOP (3) nome FROM produtos ORDER BY preco DESC;.

A tabela não existe, ou você está no banco errado (erro 208)
SELECT * FROM cliente;
-- Invalid object name 'cliente'.

"Nome de objeto inválido". Duas causas comuns: o nome está errado (cliente em vez de clientes) ou você esqueceu o USE loja; e está consultando outro banco do servidor (o sqlcmd entra no banco padrão do usuário, que costuma ser o master). No SQLite, o equivalente é no such table: cliente.

Coluna que não existe ou ambígua (erros 207 e 209)
-- Invalid column name 'nmoe'.
-- Ambiguous column name 'id'.

"Nome de coluna inválido" é o no such column do SQLite; "nome de coluna ambíguo" é o ambiguous column name da aula O que é um JOIN. Mesma causa, mesmo conserto.

Chave repetida (erro 2627)

No SQLite, inserir um cliente com um id que já existe dá:

INSERT INTO clientes (id, nome) VALUES (1, 'Teste');

No SQL Server, a mensagem é a 2627, que cita o nome da restrição e o valor repetido:

Violation of PRIMARY KEY constraint 'PK_clientes'. Cannot insert duplicate key in object 'dbo.clientes'. The duplicate key value is (1).

("Violação da restrição PRIMARY KEY… Não é possível inserir chave duplicada… O valor duplicado é (1)".) O nome PK_clientes é ilustrativo: o SQL Server dá um nome à restrição, e você pode escolher o seu ao criar a tabela.

Aspas duplas para texto

No editor do guia, aspas duplas são sempre nome de coluna, e ele explica:

SELECT nome FROM clientes WHERE cidade = "Teresina";

Já o sqlite3 do Debian, no terminal (o da Ana), é mais tolerante: quando não acha uma coluna com aquele nome, ele trata "Teresina" como texto e roda calado:

$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> SELECT nome FROM clientes WHERE cidade = "Teresina" LIMIT 2;
José Ribamar Silva
Maria Clara Mendes
sqlite> .quit

Não confie nisso: no dia em que alguém criar uma coluna chamada Teresina, a consulta muda de sentido sem avisar. E no SQL Server, com QUOTED_IDENTIFIER ligado (o padrão), "Teresina" é lido como nome de coluna e dá o erro 207, Invalid column name 'Teresina'. Texto vai entre aspas simples, em todos.

MERGE sem ponto e vírgula (erro 10713)

Visto na seção 10: A MERGE statement must be terminated by a semi-colon (;). No T-SQL, o ; no fim dos comandos é quase sempre opcional, mas no MERGE ele é obrigatório. Pegue o costume de terminar todo comando com ;, em qualquer banco.

Esqueceu o GO no sqlcmd

Você digita a consulta, aperta Enter, e nada acontece: o sqlcmd só mostra o prompt da linha seguinte, esperando mais linhas. Não é erro, ele está esperando o GO. Digite GO numa linha sozinha.


Referência rápida: SQLite × SQL Server

TarefaSQLiteSQL Server (T-SQL)
Os 3 primeiros… ORDER BY x LIMIT 3SELECT TOP (3) … ORDER BY x
Pular 3, mostrar 3LIMIT 3 OFFSET 3ORDER BY x OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY
Nome especial"order" (ou [order])[order] (ou "order")
Numeração automáticaINTEGER PRIMARY KEYINT IDENTITY(1,1) PRIMARY KEY
Juntar textosa || b, CONCAT(a, b)a + b, CONCAT(a, b), a || b (2025)
Tamanholength(t)LEN(t) (sem os espaços do fim)
Trocar NULLifnull, coalesceISNULL, COALESCE
Agoradatetime('now')GETDATE(), SYSDATETIME()
Somar diasdate(d, '+7 days')DATEADD(day, 7, d)
Diferença em diasjulianday(b) - julianday(a)DATEDIFF(day, a, b)
BooleanoINTEGER 0/1BIT 0/1
Insere ou atualizaINSERT … ON CONFLICT … DO UPDATEMERGE … WHEN MATCHED … WHEN NOT MATCHED …;
Ver o que mudou… RETURNING idOUTPUT INSERTED.id (antes do VALUES)
Apagar tabela se existirDROP TABLE IF EXISTS tDROP TABLE IF EXISTS t (desde o 2016)
Rodar no terminal;GO
1 / 0NULLerro 8134
Erro do SQL ServerTexto (modelo da documentação)No SQLite seria
102Incorrect syntax near '%.*ls'.near "…": syntax error
208Invalid object name '%.*ls'.no such table
207Invalid column name '%.*ls'.no such column
209Ambiguous column name '%.*ls'.ambiguous column name
2627Violation of %ls constraint '%.*ls'. Cannot insert duplicate key …UNIQUE constraint failed
515Cannot insert the value NULL into column '%.*ls', table '%.*ls'; column does not allow nulls. …NOT NULL constraint failed
547The %ls statement conflicted with the %ls constraint …FOREIGN KEY constraint failed
8120Column '…' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.(aceita calado)
8134Divide by zero error encountered.(dá NULL)
544Cannot insert explicit value for identity column …(permitido)
10713A MERGE statement must be terminated by a semi-colon (;).(não há MERGE)

Exercícios

1. O limite do SQL Server

Complete a versão T-SQL de "os 10 produtos mais caros":

SELECT nome, preco FROM produtos ORDER BY preco DESC;

2. Rodar no sqlcmd

Depois de digitar a consulta no sqlcmd, o que se escreve numa linha sozinha para ela rodar?

3. No SQL Server, SELECT nome + ' - ' + email FROM clientes WHERE id = 7; (o cliente 7 não tem email) devolve…

4. Qual é o equivalente T-SQL do RETURNING id num INSERT?

5. Num SQL Server instalado num Windows em português do Brasil, com a collation padrão, WHERE nome LIKE 'JOSÉ%' acha o "José Ribamar Silva"?

Missão final: traduza do T-SQL para o SQLite

Missão: esta consulta veio do sistema do estágio, em T-SQL: mostra o nome e o tamanho do nome dos clientes sem email, com "(sem email)" no lugar do NULL. No SQLite ela dá erro. Traduza as duas funções do SQL Server (LEN e ISNULL) para funções que o SQLite conhece.

SELECT nome, LEN(nome) AS tamanho, ISNULL(email, '(sem email)') AS email
FROM clientes
WHERE email IS NULL;
SELECT nome, length(nome) AS tamanho, coalesce(email, '(sem email)') AS email
FROM clientes
WHERE email IS NULL;

Se você usou coalesce, a sua consulta agora roda no SQLite e no SQL Server (lá, só troque length por LEN). Para ver como o mesmo tipo de tradução funciona com o banco aberto mais famoso entre os programadores, siga para SQL no PostgreSQL.


Resumo

  • O SQL Server é o banco da Microsoft, e o dialeto dele é o T-SQL. As edições Express e Developer são gratuitas; ele roda no Windows, no Linux e em contêineres.
  • No sqlcmd, o ; não executa: digite GO numa linha sozinha. GO não é SQL, é um comando do sqlcmd e do SSMS.
  • LIMIT vira SELECT TOP (n); LIMIT … OFFSET vira ORDER BY … OFFSET … ROWS FETCH NEXT … ROWS ONLY.
  • Nomes especiais vão entre [colchetes]; texto, entre aspas simples (com N'…' para acentos). A collation padrão em pt-BR ignora maiúsculas.
  • IDENTITY(1,1) numera; + ou CONCAT juntam textos; LEN, ISNULL, GETDATE, DATEADD, DATEDIFF são os nomes de lá.
  • O UPSERT é MERGE (com ; obrigatório), e o RETURNING é OUTPUT INSERTED.… / DELETED.….
  • O SQL Server é mais rígido: recusa coluna solta no GROUP BY (erro 8120), divisão por zero (8134) e conversões impossíveis (245).