Quem migra do SQL Server para o PostgreSQL leva um susto inicial: a maioria das funções existe, mas com nome diferente, parâmetros invertidos ou sintaxe ligeiramente distinta. Este guia cobre os principais casos.

Funções de data e hora

A maior diferença começa aqui. O SQL Server usa FORMAT e DATEADD/DATEDIFF para quase tudo; o PostgreSQL preferere TO_CHAR e aritmética direta com INTERVAL.

Data atual

SQL ServerPostgreSQL
GETDATE()NOW() ou CURRENT_TIMESTAMP
GETUTCDATE()NOW() AT TIME ZONE 'UTC'
SYSDATETIME()CLOCK_TIMESTAMP()

Formatação de datas

No SQL Server, FORMAT(date, 'dd/MM/yyyy') usa o formato .NET. No PostgreSQL, TO_CHAR usa máscaras próprias:

-- SQL Server
SELECT FORMAT(GETDATE(), 'dd/MM/yyyy')        -- '28/05/2026'
SELECT FORMAT(GETDATE(), 'yyyy-MM-dd HH:mm')  -- '2026-05-28 14:30'
 
-- PostgreSQL equivalente
SELECT TO_CHAR(NOW(), 'DD/MM/YYYY')           -- '28/05/2026'
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI')   -- '2026-05-28 14:30'

Máscaras mais usadas no TO_CHAR:

Máscara PGResultadoEquivalente .NET
YYYY2026yyyy
MM05MM
DD28dd
HH2414HH
MI30mm
SS45ss
DAYWEDNESDAYdddd
MonthMayMMMM

Adicionar e subtrair datas

O PostgreSQL troca DATEADD por aritmética com INTERVAL:

-- SQL Server
SELECT DATEADD(day,   7,  GETDATE())
SELECT DATEADD(month, 1,  GETDATE())
SELECT DATEADD(year, -1,  GETDATE())
 
-- PostgreSQL equivalente
SELECT NOW() + INTERVAL '7 days'
SELECT NOW() + INTERVAL '1 month'
SELECT NOW() - INTERVAL '1 year'

Diferença entre datas

DATEDIFF não existe no PostgreSQL. Para dias, subtração direta funciona; para outras unidades, use EXTRACT ou DATE_PART:

-- SQL Server
SELECT DATEDIFF(day,   '2026-01-01', GETDATE())
SELECT DATEDIFF(month, '2026-01-01', GETDATE())
 
-- PostgreSQL equivalente
SELECT CURRENT_DATE - '2026-01-01'::date                   -- dias (retorna INTEGER)
SELECT EXTRACT(YEAR FROM AGE(NOW(), '2026-01-01'::date)) * 12
     + EXTRACT(MONTH FROM AGE(NOW(), '2026-01-01'::date))  -- meses

Extrair partes de uma data

-- SQL Server
SELECT YEAR(GETDATE()), MONTH(GETDATE()), DAY(GETDATE())
 
-- PostgreSQL: duas opções equivalentes
SELECT EXTRACT(YEAR  FROM NOW()),
       EXTRACT(MONTH FROM NOW()),
       EXTRACT(DAY   FROM NOW())
 
SELECT DATE_PART('year',  NOW()),
       DATE_PART('month', NOW()),
       DATE_PART('day',   NOW())

Outras funções de data

SQL ServerPostgreSQL
EOMONTH(date)DATE_TRUNC('month', date) + INTERVAL '1 month' - INTERVAL '1 day'
DATEFROMPARTS(y, m, d)MAKE_DATE(y, m, d)
CAST('2026-05-28' AS DATE)'2026-05-28'::date ou CAST('2026-05-28' AS DATE)
ISDATE(str)sem equivalente direto; use TO_DATE com tratamento de erro

Funções de string

Comprimento e posição

SQL ServerPostgreSQL
LEN(str)LENGTH(str) ou CHAR_LENGTH(str)
CHARINDEX(busca, str)POSITION(busca IN str) ou STRPOS(str, busca)
PATINDEX('%pat%', str)sem equivalente direto; use regex com ~

Atenção na ordem dos parâmetros: CHARINDEX(busca, str) inverte em relação ao STRPOS(str, busca).

-- SQL Server
SELECT CHARINDEX('lo', 'hello world')  -- 4
 
-- PostgreSQL
SELECT POSITION('lo' IN 'hello world')  -- 4
SELECT STRPOS('hello world', 'lo')      -- 4

Substrings

-- SQL Server
SELECT SUBSTRING('hello world', 7, 5)   -- 'world'
SELECT LEFT('hello', 3)                 -- 'hel'
SELECT RIGHT('hello', 3)                -- 'llo'
 
-- PostgreSQL (SUBSTRING e LEFT/RIGHT são iguais)
SELECT SUBSTRING('hello world' FROM 7 FOR 5)  -- 'world'
SELECT SUBSTR('hello world', 7, 5)            -- 'world' (sintaxe alternativa)
SELECT LEFT('hello', 3)                       -- 'hel' (igual)
SELECT RIGHT('hello', 3)                      -- 'llo' (igual)

Substituição dentro de strings

-- SQL Server: STUFF(str, inicio, qtd_chars, substituicao)
SELECT STUFF('hello world', 7, 5, 'there')   -- 'hello there'
 
-- PostgreSQL: OVERLAY
SELECT OVERLAY('hello world' PLACING 'there' FROM 7 FOR 5)  -- 'hello there'
 
-- REPLACE funciona igual nos dois
SELECT REPLACE('hello world', 'world', 'there')

Repetição e outras

SQL ServerPostgreSQL
REPLICATE(str, n)REPEAT(str, n)
REVERSE(str)REVERSE(str) (igual)
LTRIM(str) / RTRIM(str)igual
TRIM(str)igual (disponível no SQL Server 2017+ e PG 9.1+)
UPPER(str) / LOWER(str)igual
SPACE(n)REPEAT(' ', n)
STR(num, tamanho, decimais)TO_CHAR(num, 'formato')

Concatenação

No PostgreSQL, o operador + não concatena strings. Use || ou CONCAT:

-- SQL Server
SELECT 'hello' + ' ' + 'world'
 
-- PostgreSQL
SELECT 'hello' || ' ' || 'world'
SELECT CONCAT('hello', ' ', 'world')   -- também funciona, ignora NULLs

LIKE e busca case-insensitive

No SQL Server, LIKE é case-insensitive por padrão (depende do collation). No PostgreSQL, LIKE é case-sensitive. Use ILIKE para ignorar maiúsculas:

-- PostgreSQL
SELECT * FROM clientes WHERE nome LIKE 'JOAO%'    -- case-sensitive
SELECT * FROM clientes WHERE nome ILIKE 'joao%'   -- case-insensitive

Conversão de tipos

O PostgreSQL oferece um atalho de cast com :: que o SQL Server não tem:

-- SQL Server
SELECT CAST(42 AS VARCHAR)
SELECT CONVERT(VARCHAR, GETDATE(), 103)   -- com código de formato
 
-- PostgreSQL
SELECT CAST(42 AS VARCHAR)               -- mesma sintaxe CAST
SELECT 42::TEXT                          -- atalho com ::
SELECT NOW()::DATE                       -- casting de timestamp para date
SELECT TO_CHAR(NOW(), 'DD/MM/YYYY')      -- equivalente ao CONVERT com código

TRY_CAST e TRY_CONVERT

O PostgreSQL não tem equivalente nativo. A alternativa é uma função com tratamento de exceção ou regexp para validar antes de converter:

-- SQL Server
SELECT TRY_CAST('abc' AS INT)   -- retorna NULL sem erro
 
-- PostgreSQL: sem equivalente direto
-- Opção 1: regexp antes de converter
SELECT CASE WHEN '123' ~ '^[0-9]+$' THEN '123'::INT ELSE NULL END
 
-- Opção 2: função customizada com EXCEPTION
CREATE OR REPLACE FUNCTION try_cast_int(p_text TEXT)
RETURNS INT AS $$
BEGIN
  RETURN p_text::INT;
EXCEPTION WHEN OTHERS THEN
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

Formatação de números

-- SQL Server
SELECT FORMAT(1234567.89, 'N2')   -- '1,234,567.89'
SELECT FORMAT(0.45, 'P0')         -- '45%'
 
-- PostgreSQL com TO_CHAR
SELECT TO_CHAR(1234567.89, 'FM999,999,990.00')  -- '1,234,567.89'
SELECT TO_CHAR(0.45, 'FM990.00%')               -- '45.00%'

Máscaras numéricas do TO_CHAR:

MáscaraSignificado
9dígito, suprime zero à esquerda
0dígito, mantém zero à esquerda
FMremove espaços extras do resultado
.separador decimal
,separador de milhar
%multiplica por 100 e adiciona %

Condicionais

SQL ServerPostgreSQL
IIF(cond, verdadeiro, falso)CASE WHEN cond THEN verdadeiro ELSE falso END
CHOOSE(idx, val1, val2, ...)sem equivalente; use CASE
NULLIF(a, b)NULLIF(a, b) (igual)
ISNULL(a, b)COALESCE(a, b)
COALESCE(a, b, ...)COALESCE(a, b, ...) (igual)
-- SQL Server
SELECT IIF(saldo > 0, 'positivo', 'negativo')
 
-- PostgreSQL
SELECT CASE WHEN saldo > 0 THEN 'positivo' ELSE 'negativo' END

Paginação

-- SQL Server
SELECT TOP 10 * FROM pedidos ORDER BY data DESC
 
SELECT * FROM pedidos
ORDER BY data DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
 
-- PostgreSQL
SELECT * FROM pedidos ORDER BY data DESC LIMIT 10
 
SELECT * FROM pedidos
ORDER BY data DESC
LIMIT 10 OFFSET 20

Agrupamento e agregação

A maioria funciona igual. As diferenças ficam nas funções estatísticas:

SQL ServerPostgreSQL
STDEV(col)STDDEV(col)
VAR(col)VARIANCE(col)
STRING_AGG(col, sep)STRING_AGG(col, sep) (igual, PG 9.0+)
COUNT_BIG(*)sem equivalente; COUNT(*) já retorna BIGINT no PG

Tabelas temporárias e variáveis

-- SQL Server
SELECT * INTO #temp FROM pedidos WHERE status = 'pendente'
CREATE TABLE #temp (id INT, nome VARCHAR(100))
DECLARE @variavel INT = 10
 
-- PostgreSQL
CREATE TEMP TABLE temp AS SELECT * FROM pedidos WHERE status = 'pendente'
CREATE TEMPORARY TABLE temp (id INT, nome VARCHAR(100))
-- variáveis só existem dentro de funções/procedures com DO $$ ... $$

Outros elementos de sintaxe

SQL ServerPostgreSQLObservação
PRINT 'mensagem'RAISE NOTICE 'mensagem'para debug em scripts
IDENTITY(1,1)SERIAL ou GENERATED ALWAYS AS IDENTITYauto-increment
@@IDENTITY / SCOPE_IDENTITY()RETURNING id / lastval()retornar id inserido
WITH (NOLOCK)sem equivalente diretoPG usa MVCC; considere READ UNCOMMITTED
GOnão existeseparador de batches; não é necessário no PG
SET NOCOUNT ONnão existePG não exibe “x rows affected” por padrão em clients
[nome com espaço]"nome com espaço"identificadores com espaço usam aspas duplas

Identidade gerada

-- SQL Server
CREATE TABLE pedidos (
  id INT IDENTITY(1,1) PRIMARY KEY,
  descricao VARCHAR(200)
)
 
-- PostgreSQL opção 1: SERIAL (legado, ainda muito usado)
CREATE TABLE pedidos (
  id SERIAL PRIMARY KEY,
  descricao TEXT
)
 
-- PostgreSQL opção 2: padrão SQL moderno (PG 10+)
CREATE TABLE pedidos (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  descricao TEXT
)

Retornar o ID inserido

-- SQL Server
INSERT INTO pedidos (descricao) VALUES ('teste')
SELECT SCOPE_IDENTITY()
 
-- PostgreSQL: RETURNING é mais limpo
INSERT INTO pedidos (descricao) VALUES ('teste')
RETURNING id

Conexões

Referências