Capa do Ebook gratuito SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

Novo curso

26 páginas

Notas de compatibilidade entre MySQL, PostgreSQL e SQL Server nas funções mais usadas

Capítulo 21

Tempo estimado de leitura: 0 minutos

+ Exercício

Quando você escreve SQL para análise de dados no dia a dia, a maior fonte de atrito entre bancos não costuma ser o “conceito” (filtrar, agregar, juntar), e sim detalhes de compatibilidade: nomes de funções, tipos retornados, comportamento com nulos, concatenação, conversões implícitas, limites/paginação, e funções utilitárias que aparecem o tempo todo em relatórios. Este capítulo é um guia prático de compatibilidade entre MySQL, PostgreSQL e SQL Server nas funções mais usadas, com foco em como escrever queries portáveis e como adaptar rapidamente quando a portabilidade não é possível.

O que “compatibilidade” significa na prática

Compatibilidade aqui não é “o SQL roda igual em qualquer lugar”, e sim: (1) a função existe com o mesmo nome; (2) aceita os mesmos tipos; (3) retorna o mesmo tipo; (4) tem o mesmo comportamento em casos-limite (nulos, strings vazias, collation, timezone); (5) a sintaxe é idêntica. Em análise de dados, pequenas diferenças podem mudar um relatório: uma concatenação que vira NULL, um arredondamento diferente, uma conversão que falha, ou uma função de data que interpreta timezone de forma distinta.

Uma forma útil de pensar é separar em três camadas: (a) SQL “padrão” (ANSI) que tende a ser mais portável; (b) funções com equivalentes diretos (mudam nome/sintaxe); (c) funções sem equivalente direto (exigem reescrita).

Mapa rápido: funções utilitárias mais comuns e equivalentes

Use este mapa como referência rápida. Nos tópicos seguintes, você verá exemplos e armadilhas.

  • Coalescência de nulos: COALESCE (MySQL/PostgreSQL/SQL Server)
  • Condicional: CASE WHEN (todos); IIF (SQL Server); IF (MySQL)
  • Conversão/cast: CAST (todos); CONVERT (SQL Server); ::tipo (PostgreSQL)
  • Arredondamento: ROUND (todos); CEILING/FLOOR (todos, com pequenas diferenças de tipo)
  • Concatenação: CONCAT (todos); operador || (PostgreSQL); operador + (SQL Server, com cuidado)
  • Comprimento de string: LENGTH (MySQL/PostgreSQL); LEN (SQL Server)
  • Substring: SUBSTRING (todos); SUBSTR (MySQL/PostgreSQL)
  • Posição: POSITION (PostgreSQL); INSTR (MySQL); CHARINDEX (SQL Server)
  • Data/hora atual: CURRENT_DATE/CURRENT_TIMESTAMP (PostgreSQL); CURDATE()/NOW() (MySQL); GETDATE()/SYSDATETIME() (SQL Server)
  • Diferença entre datas: DATEDIFF (MySQL/SQL Server, mas semântica diferente); em PostgreSQL usa-se subtração e/ou AGE/DATE_PART
  • Paginação: LIMIT/OFFSET (MySQL/PostgreSQL); OFFSET ... FETCH (SQL Server)
  • Top N: LIMIT (MySQL/PostgreSQL); TOP (SQL Server)
  • Agregação de strings: STRING_AGG (PostgreSQL/SQL Server); GROUP_CONCAT (MySQL)

COALESCE e nulos: o “padrão” que salva portabilidade

COALESCE(a, b, c) retorna o primeiro valor não nulo. É padrão e funciona nos três bancos. Em relatórios, ele aparece para substituir nulos por zero, por “Sem categoria”, ou para escolher entre colunas alternativas.

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

Exemplo prático

SELECT  customer_id,        COALESCE(segment, 'Sem segmento') AS segment,        COALESCE(revenue, 0) AS revenue FROM customers;

Armadilha comum: tipo de retorno. Se você mistura tipos (ex.: número e texto), cada banco pode tentar converter de forma diferente. Regra prática: garanta tipos compatíveis, usando CAST quando necessário.

SELECT COALESCE(CAST(score AS DECIMAL(10,2)), 0.00) AS score FROM customer_scores;

CASE WHEN vs IF/IIF: escolha o que é mais portável

CASE WHEN é o caminho mais compatível. MySQL tem IF(cond, a, b) e SQL Server tem IIF(cond, a, b), mas eles não são universais.

Padrão recomendado (portável)

SELECT  order_id,        CASE          WHEN status = 'paid' THEN 1          WHEN status = 'canceled' THEN 0          ELSE NULL        END AS is_paid FROM orders;

Armadilha: em SQL Server, IIF pode avaliar ambos os ramos em alguns cenários (dependendo de conversões), o que pode causar erro em conversões inválidas. CASE costuma ser mais previsível para evitar conversões forçadas.

CAST, CONVERT e conversões implícitas

CAST(expr AS tipo) é padrão e funciona nos três. SQL Server também tem CONVERT(tipo, expr). PostgreSQL tem a sintaxe expr::tipo, útil, mas menos portável.

Exemplo: converter texto para data

Quando datas chegam como texto (ex.: '2026-01-09'), a conversão pode falhar se o formato não for aceito.

-- Portável (desde que o formato seja aceito pelo banco) SELECT CAST(order_date_text AS DATE) AS order_date FROM raw_orders;

SQL Server: formatos dependem de configurações e podem ser ambíguos. Em cenários reais, prefira formatos ISO (YYYY-MM-DD) e, quando necessário, use CONVERT com estilo (não-portável, mas controlado).

-- SQL Server (exemplo de controle de estilo) SELECT CONVERT(date, order_date_text, 23) AS order_date FROM raw_orders;

PostgreSQL: quando o texto não está em formato padrão, é comum usar TO_DATE (não existe igual nos três).

-- PostgreSQL SELECT TO_DATE(order_date_text, 'DD/MM/YYYY') AS order_date FROM raw_orders;

Arredondamento e tipos numéricos: ROUND, CEILING, FLOOR

ROUND, CEILING e FLOOR existem nos três. O cuidado aqui é com o tipo retornado e com inteiros vs decimais.

Exemplo: padronizar casas decimais em métricas

SELECT  product_id,        ROUND(avg_ticket, 2) AS avg_ticket_2d FROM product_kpis;

Armadilha: em SQL Server, operações entre inteiros podem resultar em inteiro (divisão inteira), e o arredondamento não “recupera” o que já foi truncado. Garanta decimal antes de dividir.

-- SQL Server: evitar divisão inteira SELECT  ROUND(1.0 * revenue / NULLIF(orders, 0), 2) AS avg_ticket FROM kpis;

Nota: NULLIF(x, 0) é padrão e funciona nos três; ele evita divisão por zero retornando NULL quando o denominador é 0.

Concatenação de strings: CONCAT vs operadores

Concatenação é uma das maiores fontes de diferença.

  • PostgreSQL: operador || é comum e idiomático.
  • SQL Server: operador + concatena, mas pode virar NULL se um lado for NULL (dependendo de configurações) e pode tentar somar se tipos forem numéricos.
  • MySQL: operador || pode ser tratado como OR lógico dependendo do modo SQL; por isso, não é recomendado para concatenação.

Para portabilidade, prefira CONCAT(a, b, c), que existe nos três.

Exemplo portável

SELECT  CONCAT(first_name, ' ', last_name) AS full_name FROM customers;

Armadilha com NULL: em alguns bancos, CONCAT trata NULL como string vazia; em outros, pode variar por versão/configuração. Se você precisa de comportamento consistente, use COALESCE explicitamente.

SELECT CONCAT(COALESCE(first_name, ''), ' ', COALESCE(last_name, '')) AS full_name FROM customers;

Comprimento e substring: LENGTH/LEN e SUBSTRING

Para comprimento:

  • MySQL/PostgreSQL: LENGTH(texto)
  • SQL Server: LEN(texto) (observação: LEN não conta espaços à direita)

Para substring, SUBSTRING(texto, inicio, tamanho) existe nos três e é uma boa escolha para portabilidade.

Exemplo: extrair DDD de um telefone

SELECT SUBSTRING(phone, 1, 2) AS ddd FROM customers;

Armadilha: indexação começa em 1 nos três para SUBSTRING, mas funções alternativas (como SUBSTR) podem ter nuances. Se o campo pode ser menor que o esperado, combine com validação de tamanho.

SELECT  CASE WHEN LENGTH(phone) >= 2 THEN SUBSTRING(phone, 1, 2) ELSE NULL END AS ddd FROM customers;

Nota: a versão acima usa LENGTH; em SQL Server, troque por LEN.

Buscar posição de texto: INSTR, CHARINDEX, POSITION

Encontrar a posição de um caractere/subtexto muda bastante:

  • MySQL: INSTR(texto, subtexto)
  • SQL Server: CHARINDEX(subtexto, texto)
  • PostgreSQL: POSITION(subtexto IN texto)

Exemplo: achar o “@” em e-mails

-- MySQL SELECT INSTR(email, '@') AS at_pos FROM customers;
-- SQL Server SELECT CHARINDEX('@', email) AS at_pos FROM customers;
-- PostgreSQL SELECT POSITION('@' IN email) AS at_pos FROM customers;

Estratégia de portabilidade: quando você precisa de lógica baseada em posição, considere se dá para reescrever com LIKE e SUBSTRING de forma mais simples, ou encapsular em uma camada (view) específica por banco.

Paginação e Top N: LIMIT/OFFSET vs TOP vs OFFSET/FETCH

Em relatórios, é comum pegar “os 100 primeiros” ou paginar resultados.

  • MySQL/PostgreSQL: LIMIT n e OFFSET m
  • SQL Server: TOP (n) ou ORDER BY ... OFFSET m ROWS FETCH NEXT n ROWS ONLY

Top N (mais simples)

-- MySQL / PostgreSQL SELECT  product_id, revenue FROM product_kpis ORDER BY revenue DESC LIMIT 10;
-- SQL Server SELECT TOP (10)  product_id, revenue FROM product_kpis ORDER BY revenue DESC;

Paginação (página 3 com 50 linhas por página)

-- MySQL / PostgreSQL SELECT  order_id, customer_id, created_at FROM orders ORDER BY created_at DESC LIMIT 50 OFFSET 100;
-- SQL Server SELECT  order_id, customer_id, created_at FROM orders ORDER BY created_at DESC OFFSET 100 ROWS FETCH NEXT 50 ROWS ONLY;

Armadilha: paginação sem ORDER BY não é determinística. Em SQL Server, OFFSET/FETCH exige ORDER BY.

Agregação de strings: GROUP_CONCAT vs STRING_AGG

Listar valores concatenados por grupo é útil para auditoria e explicação de resultados (ex.: listar categorias compradas por cliente). Aqui há divergência:

  • MySQL: GROUP_CONCAT(expr ORDER BY ... SEPARATOR ',')
  • PostgreSQL: STRING_AGG(expr, ',') com ORDER BY dentro da agregação em versões modernas
  • SQL Server: STRING_AGG(expr, ',') (ordenação pode exigir sintaxe adicional dependendo da versão)

Exemplo: listar SKUs por pedido

-- MySQL SELECT  order_id,        GROUP_CONCAT(sku ORDER BY sku SEPARATOR ',') AS skus FROM order_items GROUP BY order_id;
-- PostgreSQL SELECT  order_id,        STRING_AGG(sku, ',' ORDER BY sku) AS skus FROM order_items GROUP BY order_id;
-- SQL Server (ordenação pode variar por versão) SELECT  order_id,        STRING_AGG(sku, ',') AS skus FROM order_items GROUP BY order_id;

Armadilha: limites de tamanho. MySQL tem limite configurável para GROUP_CONCAT. SQL Server pode truncar dependendo do tipo (use tipos grandes como varchar(max) quando necessário). PostgreSQL tende a ser mais flexível, mas o tamanho final ainda depende de memória e do tipo.

Funções de data/hora “atuais”: NOW, GETDATE, CURRENT_TIMESTAMP

Mesmo algo simples como “agora” muda de nome e precisão.

  • MySQL: NOW() (timestamp), CURDATE() (date)
  • PostgreSQL: CURRENT_TIMESTAMP, NOW() (sinônimo), CURRENT_DATE
  • SQL Server: GETDATE() (datetime), SYSDATETIME() (mais precisão)

Exemplo: carimbar extração

-- Portável em PostgreSQL e padrão SQL SELECT CURRENT_TIMESTAMP AS extracted_at;

Armadilha: timezone. PostgreSQL diferencia timestamp com e sem timezone. MySQL e SQL Server têm comportamentos e tipos próprios. Em pipelines analíticos, padronize para UTC na camada de ingestão ou normalize com funções específicas do banco (o que reduz portabilidade).

DATEDIFF e diferença entre datas: semânticas diferentes

DATEDIFF existe em MySQL e SQL Server, mas não é “igual”:

  • MySQL: DATEDIFF(data_fim, data_inicio) retorna diferença em dias (inteiro).
  • SQL Server: DATEDIFF(parte, inicio, fim) retorna contagem de “viradas” da parte (day, month, etc.), o que pode surpreender.
  • PostgreSQL: normalmente subtrai-se datas (date - date) para obter dias, ou usa-se AGE e DATE_PART para partes.

Exemplo: dias desde a compra

-- MySQL SELECT DATEDIFF(CURDATE(), order_date) AS days_since_order FROM orders;
-- SQL Server SELECT DATEDIFF(day, order_date, CAST(GETDATE() AS date)) AS days_since_order FROM orders;
-- PostgreSQL SELECT (CURRENT_DATE - order_date) AS days_since_order FROM orders;

Armadilha no SQL Server: DATEDIFF(day, '2026-01-01 23:59', '2026-01-02 00:01') retorna 1 (virou o dia), mesmo com poucos minutos. Se você precisa de diferença exata em horas/minutos, use a parte apropriada ou trabalhe com intervalos/segundos.

Passo a passo prático: como adaptar uma query “multi-banco” com funções comuns

A seguir, um procedimento prático para pegar uma query típica de relatório e torná-la compatível (ou facilmente adaptável) entre MySQL, PostgreSQL e SQL Server, focando nas funções mais usadas.

Passo 1: liste as funções e sintaxes não-portáveis

Pegue sua query e marque itens como: LIMIT, TOP, GROUP_CONCAT, LEN, GETDATE, operador de concatenação, casts específicos, e funções de posição.

Passo 2: substitua por equivalentes padrão quando existir

Troque IF/IIF por CASE, troque concatenação por CONCAT, troque conversões por CAST (quando possível), e use COALESCE/NULLIF para controlar nulos e divisões.

Passo 3: isole o que inevitavelmente muda por banco

Alguns itens quase sempre mudam: paginação (LIMIT vs TOP vs OFFSET/FETCH), agregação de strings (GROUP_CONCAT vs STRING_AGG), e funções específicas de parsing de data. Para esses, mantenha “blocos” alternativos, ou crie views por banco com a mesma interface.

Exemplo completo: relatório simples com pontos de compatibilidade

Objetivo: listar clientes com um “nome completo”, dias desde o cadastro, e uma lista de tags/categorias associadas (string agregada), retornando apenas os 20 mais recentes.

Versão MySQL

SELECT  c.customer_id,  CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS full_name,  DATEDIFF(CURDATE(), c.created_at) AS days_since_signup,  GROUP_CONCAT(ct.tag ORDER BY ct.tag SEPARATOR ',') AS tags FROM customers c LEFT JOIN customer_tags ct   ON ct.customer_id = c.customer_id GROUP BY c.customer_id, c.first_name, c.last_name, c.created_at ORDER BY c.created_at DESC LIMIT 20;

Versão PostgreSQL

SELECT  c.customer_id,  CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS full_name,  (CURRENT_DATE - c.created_at::date) AS days_since_signup,  STRING_AGG(ct.tag, ',' ORDER BY ct.tag) AS tags FROM customers c LEFT JOIN customer_tags ct   ON ct.customer_id = c.customer_id GROUP BY c.customer_id, c.first_name, c.last_name, c.created_at ORDER BY c.created_at DESC LIMIT 20;

Versão SQL Server

SELECT TOP (20)  c.customer_id,  CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS full_name,  DATEDIFF(day, CAST(c.created_at AS date), CAST(GETDATE() AS date)) AS days_since_signup,  STRING_AGG(ct.tag, ',') AS tags FROM customers c LEFT JOIN customer_tags ct   ON ct.customer_id = c.customer_id GROUP BY c.customer_id, c.first_name, c.last_name, c.created_at ORDER BY c.created_at DESC;

O que observar no exemplo:

  • Concatenação: padronizada com CONCAT + COALESCE.
  • Dias desde cadastro: MySQL usa DATEDIFF simples; PostgreSQL usa subtração de datas; SQL Server usa DATEDIFF(day, ...) com casts para date.
  • Agregação de tags: MySQL usa GROUP_CONCAT; PostgreSQL/SQL Server usam STRING_AGG.
  • Top N: MySQL/PostgreSQL usam LIMIT; SQL Server usa TOP.

Checklist de compatibilidade para o dia a dia

  • Prefira COALESCE, NULLIF, CASE e CAST para máxima portabilidade.
  • Evite operador de concatenação (|| ou +) quando a query precisa rodar em mais de um banco; use CONCAT.
  • Para paginação e Top N, aceite que a sintaxe muda e mantenha variantes por banco.
  • Para agregação de strings, planeje a troca GROUP_CONCATSTRING_AGG e valide limites de tamanho.
  • Em diferenças de datas, valide a semântica (dias “exatos” vs “viradas” de unidade) e padronize casts para date quando o objetivo é contar dias de calendário.
  • Em SQL Server, cuidado com divisão inteira; force decimal antes de dividir.
  • Em SQL Server, LEN ignora espaços à direita; se isso importa, teste com dados reais e considere alternativas.

Agora responda o exercício sobre o conteúdo:

Ao adaptar uma query para rodar em MySQL, PostgreSQL e SQL Server, qual escolha tende a ser mais portável para construir uma lógica condicional sem depender de funções específicas de cada banco?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

CASE WHEN é a forma mais compatível entre MySQL, PostgreSQL e SQL Server. IF e IIF existem apenas em bancos específicos e podem trazer diferenças de comportamento (como avaliação de ramos e conversões).

Próximo capitúlo

Projeto aplicado com vendas: receita, ticket médio, mix de produtos e sazonalidade

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.