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.
- 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 é oGO. - A tradução das consultas que você já sabe:
LIMIT×TOPeOFFSET … FETCH, aspas × colchetes, numeração automática, textos, datas, booleanos. MERGE(o UPSERT do SQL Server) eOUTPUT(oRETURNINGdele).- Os tipos de dados mais usados e as mensagens de erro mais comuns, e o que cada uma quer dizer.
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 faz | projeto aberto, domínio público | Microsoft |
| Como funciona | o banco é um arquivo; o programa vai junto com o seu aplicativo | um servidor: um programa sempre ligado, e os clientes se conectam a ele pela rede (porta 1433 no exemplo da Microsoft) |
| Dialeto | SQL do SQLite | T-SQL (Transact-SQL) |
| Programa de terminal | sqlite3 | sqlcmd |
| Programa gráfico | vá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ção | O 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, osqlcmdpede 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
sqlcmdexigem 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".
| Tarefa | sqlite3 | sqlcmd |
|---|---|---|
| Abrir | sqlite3 loja.db | sqlcmd -S servidor -U usuario (e depois USE loja;, ou a opção -d loja) |
| Rodar o que foi digitado | o ; no fim | GO numa linha sozinha |
| Rodar um arquivo | .read arquivo.sql | sqlcmd … -i arquivo.sql |
| Uma consulta e sair | sqlite3 loja.db "SELECT …" | sqlcmd … -Q "SELECT …" |
| Sair | .quit | QUIT 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
SELECTcom número fixo (TOP 3funciona), mas a Microsoft recomenda usá-los sempre, porque emINSERT,UPDATE,MERGEeDELETEeles são obrigatórios. - ORDER BY preco DESC
- Sem
ORDER BY, oTOPdevolve 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: semORDER BY, não háOFFSET. - FETCH NEXT 3 ROWS ONLY
- "Traga as próximas 3 linhas, só".
NEXTeFIRSTsão sinônimos, assim comoROWeROWS.
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
INSERTque 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:
| Tarefa | SQLite | SQL Server |
|---|---|---|
| Tamanho do texto | length(t) | LEN(t): atenção, não conta os espaços do fim (para contar bytes, DATALENGTH) |
| Pedaço do texto | substr(t, início, tamanho) | SUBSTRING(t, início, tamanho) |
| Posição de um trecho | instr(t, trecho) | CHARINDEX(trecho, t): a ordem dos argumentos é invertida |
| Valor no lugar de NULL | ifnull(x, y) ou coalesce(x, y) | ISNULL(x, y) ou COALESCE(x, y) |
| Juntar os textos de um grupo | group_concat(t, ', ') ou string_agg(t, ', ') | STRING_AGG(t, ', ') (desde o SQL Server 2017) |
| Tirar espaços das pontas | trim(t) | TRIM(t) (desde o SQL Server 2017) |
| Maiúsculas | upper(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;
| Tarefa | SQLite | SQL Server |
|---|---|---|
| Agora (data e hora) | datetime('now') | GETDATE(), SYSDATETIME() ou CURRENT_TIMESTAMP |
| Somar dias | date(d, '+7 days') | DATEADD(day, 7, d) |
| Dias entre duas datas | julianday(b) - julianday(a) | DATEDIFF(day, a, b) |
| Ano de uma data | strftime('%Y', d) | YEAR(d) ou DATEPART(year, d) |
| Data no formato brasileiro | strftime('%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
ONde um JOIN. - WHEN MATCHED THEN UPDATE …
- "Quando achou par": atualiza.
- WHEN NOT MATCHED THEN INSERT …
- "Quando não achou": insere. O
idfica de fora porque, comIDENTITY, quem escolhe o número é o banco. - ; no fim
- Obrigatório. A documentação diz que o
MERGEexige 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:
| Coluna | No SQLite | No SQL Server | Por quê |
|---|---|---|---|
id | INTEGER PRIMARY KEY | INT IDENTITY(1,1) PRIMARY KEY | numeração automática |
nome | TEXT | NVARCHAR(100) | texto com acentos, até 100 caracteres (o N é de Unicode) |
descricao longa | TEXT | NVARCHAR(MAX) | texto sem limite prático |
preco | REAL | DECIMAL(10,2) | dinheiro: número exato com 2 casas, sem as "caudas" do ponto flutuante |
data | TEXT (ISO) | DATE | tipo de data de verdade |
| data e hora | TEXT | DATETIME2 | o tipo moderno de data e hora |
vip | INTEGER (0 ou 1) | BIT | 0, 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 / ISNULL | COALESCE(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 / TOP | não há forma comum | o SQLite não tem OFFSET … FETCH; aqui não tem jeito, é traduzir |
"nome" / [nome] | nomes simples, minúsculos, sem espaço e sem palavra reservada | não precisam de aspas em nenhum banco |
colunas soltas no GROUP BY | toda coluna do SELECT no GROUP BY ou numa agregação | o 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).
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;.
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.
-- 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.
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.
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.
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.
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
| Tarefa | SQLite | SQL Server (T-SQL) |
|---|---|---|
| Os 3 primeiros | … ORDER BY x LIMIT 3 | SELECT TOP (3) … ORDER BY x |
| Pular 3, mostrar 3 | LIMIT 3 OFFSET 3 | ORDER BY x OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY |
| Nome especial | "order" (ou [order]) | [order] (ou "order") |
| Numeração automática | INTEGER PRIMARY KEY | INT IDENTITY(1,1) PRIMARY KEY |
| Juntar textos | a || b, CONCAT(a, b) | a + b, CONCAT(a, b), a || b (2025) |
| Tamanho | length(t) | LEN(t) (sem os espaços do fim) |
| Trocar NULL | ifnull, coalesce | ISNULL, COALESCE |
| Agora | datetime('now') | GETDATE(), SYSDATETIME() |
| Somar dias | date(d, '+7 days') | DATEADD(day, 7, d) |
| Diferença em dias | julianday(b) - julianday(a) | DATEDIFF(day, a, b) |
| Booleano | INTEGER 0/1 | BIT 0/1 |
| Insere ou atualiza | INSERT … ON CONFLICT … DO UPDATE | MERGE … WHEN MATCHED … WHEN NOT MATCHED …; |
| Ver o que mudou | … RETURNING id | OUTPUT INSERTED.id (antes do VALUES) |
| Apagar tabela se existir | DROP TABLE IF EXISTS t | DROP TABLE IF EXISTS t (desde o 2016) |
| Rodar no terminal | ; | GO |
| 1 / 0 | NULL | erro 8134 |
| Erro do SQL Server | Texto (modelo da documentação) | No SQLite seria |
|---|---|---|
| 102 | Incorrect syntax near '%.*ls'. | near "…": syntax error |
| 208 | Invalid object name '%.*ls'. | no such table |
| 207 | Invalid column name '%.*ls'. | no such column |
| 209 | Ambiguous column name '%.*ls'. | ambiguous column name |
| 2627 | Violation of %ls constraint '%.*ls'. Cannot insert duplicate key … | UNIQUE constraint failed |
| 515 | Cannot insert the value NULL into column '%.*ls', table '%.*ls'; column does not allow nulls. … | NOT NULL constraint failed |
| 547 | The %ls statement conflicted with the %ls constraint … | FOREIGN KEY constraint failed |
| 8120 | Column '…' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. | (aceita calado) |
| 8134 | Divide by zero error encountered. | (dá NULL) |
| 544 | Cannot insert explicit value for identity column … | (permitido) |
| 10713 | A 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":
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: digiteGOnuma linha sozinha.GOnão é SQL, é um comando dosqlcmde do SSMS. LIMITviraSELECT TOP (n);LIMIT … OFFSETviraORDER BY … OFFSET … ROWS FETCH NEXT … ROWS ONLY.- Nomes especiais vão entre
[colchetes]; texto, entre aspas simples (comN'…'para acentos). A collation padrão em pt-BR ignora maiúsculas. IDENTITY(1,1)numera;+ouCONCATjuntam textos;LEN,ISNULL,GETDATE,DATEADD,DATEDIFFsão os nomes de lá.- O UPSERT é
MERGE(com;obrigatório), e oRETURNINGé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).