JavaScript Avançado

Boas Práticas de PostgreSQL Avançado com Node.js: Transactions, CTEs e Window Functions para Times Ágeis

7 min de leitura

Boas Práticas de PostgreSQL Avançado com Node.js: Transactions, CTEs e Window Functions para Times Ágeis

Transactions com PostgreSQL e Node.js As transações são fundamentais para garantir a integridade dos dados em operações críticas. No PostgreSQL com Node.js (usando a biblioteca ), uma transação agrupa múltiplas queries em uma unidade atômica: ou todas executam com sucesso ou nenhuma é confirmada. Isso é especialmente importante em cenários como transferências bancárias ou atualizações de inventário. O padrão , operações, ou é essencial. Se qualquer query falhar, o desfaz tudo, mantendo consistência. Use níveis de isolamento apropriados (READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE) conforme sua necessidade de segurança versus performance. CTEs (Common Table Expressions): Queries Complexas Simplificadas As CTEs, também chamadas de "WITH clauses", permitem criar queries reutilizáveis e legíveis. São especialmente úteis para quebrar lógica complexa em partes menores e hierárquicas. No Node.js, você escreve a CTE em SQL puro e reutiliza facilmente. WITH vendedortotais AS ( SELECT vendedorid, SUM(valor) as totalvendas, COUNT() as qtdvendas FROM pedidos WHERE DATETRUNC('month', datapedido) = $1::date GROUP BY vendedorid ), topvendedores

<h2>Transactions com PostgreSQL e Node.js</h2>

<p>As transações são fundamentais para garantir a integridade dos dados em operações críticas. No PostgreSQL com Node.js (usando a biblioteca <code>pg</code>), uma transação agrupa múltiplas queries em uma unidade atômica: ou todas executam com sucesso ou nenhuma é confirmada. Isso é especialmente importante em cenários como transferências bancárias ou atualizações de inventário.</p>

<pre><code class="language-javascript">const { Pool } = require(&#039;pg&#039;);

const pool = new Pool({

connectionString: &#039;postgresql://user:password@localhost:5432/mydb&#039;

});

async function transferirFundos(deContaId, paraContaId, valor) {

const client = await pool.connect();

try {

await client.query(&#039;BEGIN&#039;);

// Debita da primeira conta

await client.query(

&#039;UPDATE contas SET saldo = saldo - $1 WHERE id = $2&#039;,

[valor, deContaId]

);

// Credita na segunda conta

await client.query(

&#039;UPDATE contas SET saldo = saldo + $1 WHERE id = $2&#039;,

[valor, paraContaId]

);

await client.query(&#039;COMMIT&#039;);

console.log(&#039;Transferência realizada com sucesso&#039;);

} catch (erro) {

await client.query(&#039;ROLLBACK&#039;);

console.error(&#039;Transação revertida:&#039;, erro);

throw erro;

} finally {

client.release();

}

}</code></pre>

<p>O padrão <code>BEGIN</code>, operações, <code>COMMIT</code> ou <code>ROLLBACK</code> é essencial. Se qualquer query falhar, o <code>ROLLBACK</code> desfaz tudo, mantendo consistência. Use níveis de isolamento apropriados (READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE) conforme sua necessidade de segurança versus performance.</p>

<h2>CTEs (Common Table Expressions): Queries Complexas Simplificadas</h2>

<p>As CTEs, também chamadas de &quot;WITH clauses&quot;, permitem criar queries reutilizáveis e legíveis. São especialmente úteis para quebrar lógica complexa em partes menores e hierárquicas. No Node.js, você escreve a CTE em SQL puro e reutiliza facilmente.</p>

<pre><code class="language-javascript">async function obterVendedoresTopComDetalhes(mesano) {

const query = `

WITH vendedor_totais AS (

SELECT

vendedor_id,

SUM(valor) as total_vendas,

COUNT(*) as qtd_vendas

FROM pedidos

WHERE DATE_TRUNC(&#039;month&#039;, data_pedido) = $1::date

GROUP BY vendedor_id

),

top_vendedores AS (

SELECT *

FROM vendedor_totais

ORDER BY total_vendas DESC

LIMIT 5

)

SELECT

v.id,

v.nome,

tv.total_vendas,

tv.qtd_vendas,

ROUND(tv.total_vendas / tv.qtd_vendas::numeric, 2) as ticket_medio

FROM top_vendedores tv

JOIN vendedores v ON v.id = tv.vendedor_id

ORDER BY tv.total_vendas DESC

`;

const result = await pool.query(query, [${mesano}-01]);

return result.rows;

}

// Uso

obterVendedoresTopComDetalhes(&#039;2024-01&#039;).then(console.log);</code></pre>

<p>CTEs recursivas também são poderosas para estruturas hierárquicas (árvores organizacionais, categorias aninhadas). A vantagem é clareza: você lê a query de cima para baixo, entendendo cada etapa do processamento antes de aplicar a lógica final.</p>

<h2>Window Functions: Análise de Dados Sem Agregação</h2>

<p>Window functions realizam cálculos sobre um conjunto de linhas relacionadas, mantendo cada linha original no resultado. Diferem de agregações comuns porque não colapsam grupos. São ideais para rankings, totais acumulados, mudanças percentuais e comparações ano-a-ano.</p>

<pre><code class="language-javascript">async function obterVendasComRanking() {

const query = `

SELECT

data_venda,

vendedor_id,

valor,

RANK() OVER (PARTITION BY DATE(data_venda) ORDER BY valor DESC) as ranking_diario,

SUM(valor) OVER (

PARTITION BY vendedor_id

ORDER BY data_venda

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

) as total_acumulado,

LAG(valor) OVER (PARTITION BY vendedor_id ORDER BY data_venda) as venda_anterior,

ROUND(

((valor - LAG(valor) OVER (PARTITION BY vendedor_id ORDER BY data_venda))

/ LAG(valor) OVER (PARTITION BY vendedor_id ORDER BY data_venda) * 100)::numeric,

2

) as variacao_percentual,

PERCENT_RANK() OVER (ORDER BY valor) as percentil_valor

FROM vendas

WHERE data_venda &gt;= CURRENT_DATE - INTERVAL &#039;30 days&#039;

ORDER BY data_venda DESC, ranking_diario ASC;

`;

const result = await pool.query(query);

return result.rows;

}

// Retorna rankings, totais acumulados, variações — tudo em uma passada

obterVendasComRanking().then(dados =&gt; {

dados.forEach(row =&gt; {

console.log(${row.data_venda}: Vendedor ${row.vendedor_id} - Valor: ${row.valor}, Ranking: ${row.ranking_diario}, Acumulado: ${row.total_acumulado});

});

});</code></pre>

<p>As cláusulas principais são: <code>PARTITION BY</code> (agrupa logicamente), <code>ORDER BY</code> (ordena dentro da partição), <code>ROWS/RANGE</code> (define o frame). <code>RANK()</code> gera ranking com empates, <code>ROW_NUMBER()</code> enumera, <code>LAG()/LEAD()</code> acessa valores de linhas anteriores/posteriores, e funções como <code>SUM()</code> acumulam dentro do frame definido.</p>

<h2>Combinando Tudo: Um Exemplo Real</h2>

<pre><code class="language-javascript">async function relatorioVendasCompleto() {

const query = `

WITH mes_atual AS (

SELECT

vendedor_id,

SUM(valor) as total,

COUNT(*) as qtd,

DATE_TRUNC(&#039;month&#039;, data_venda)::date as mes

FROM vendas

WHERE DATE_TRUNC(&#039;month&#039;, data_venda) = DATE_TRUNC(&#039;month&#039;, CURRENT_DATE)

GROUP BY vendedor_id, DATE_TRUNC(&#039;month&#039;, data_venda)

),

mes_anterior AS (

SELECT

vendedor_id,

SUM(valor) as total_ant

FROM vendas

WHERE DATE_TRUNC(&#039;month&#039;, data_venda) = DATE_TRUNC(&#039;month&#039;, CURRENT_DATE - INTERVAL &#039;1 month&#039;)

GROUP BY vendedor_id

)

SELECT

ma.vendedor_id,

v.nome,

ma.total,

ma.qtd,

COALESCE(mp.total_ant, 0) as total_mes_anterior,

ROUND(((ma.total - COALESCE(mp.total_ant, 0)) / COALESCE(mp.total_ant, 1) * 100)::numeric, 2) as crescimento_percentual,

RANK() OVER (ORDER BY ma.total DESC) as ranking_geral,

ROUND(ma.total / SUM(ma.total) OVER () * 100, 2) as percentual_total

FROM mes_atual ma

JOIN mes_anterior mp ON ma.vendedor_id = mp.vendedor_id

JOIN vendedores v ON v.id = ma.vendedor_id

ORDER BY ranking_geral;

`;

const client = await pool.connect();

try {

await client.query(&#039;BEGIN ISOLATION LEVEL READ COMMITTED&#039;);

const result = await client.query(query);

await client.query(&#039;COMMIT&#039;);

return result.rows;

} catch (erro) {

await client.query(&#039;ROLLBACK&#039;);

throw erro;

} finally {

client.release();

}

}</code></pre>

<p>Este exemplo combina CTEs para encapsular lógica de períodos, window functions para rankings e percentuais, e transações para garantir consistência.</p>

<h2>Conclusão</h2>

<p>Dominando <strong>transações</strong>, você garante integridade em operações críticas. As <strong>CTEs</strong> tornam suas queries legíveis e reutilizáveis, facilitando manutenção e debug. As <strong>window functions</strong> permitem análises sofisticadas mantendo contexto de linhas individuais. Estes três pilares formam a base do PostgreSQL avançado; pratique-os em cenários reais como relatórios financeiros, dashboards e processamentos em lote. Lembre-se: código claro é código que escala.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.postgresql.org/docs/current/tutorial-transactions.html" target="_blank" rel="noopener noreferrer">PostgreSQL Official Documentation - Transactions</a></li>

<li><a href="https://www.postgresql.org/docs/current/queries-with.html" target="_blank" rel="noopener noreferrer">PostgreSQL WITH Clauses (CTEs)</a></li>

<li><a href="https://www.postgresql.org/docs/current/functions-window.html" target="_blank" rel="noopener noreferrer">PostgreSQL Window Functions</a></li>

<li><a href="https://node-postgres.com/" target="_blank" rel="noopener noreferrer">Node.js pg Library Documentation</a></li>

<li><a href="https://www.postgresql.org/docs/current/sql-select.html#SQL-WINDOW" target="_blank" rel="noopener noreferrer">High Performance PostgreSQL - Window Functions Guide</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Gerenciamento de Estado Avançado: Zustand, Jotai e Recoil Comparados na Prática
Gerenciamento de Estado Avançado: Zustand, Jotai e Recoil Comparados na Prática

Introdução ao Gerenciamento de Estado Moderno O gerenciamento de estado é um...

O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript
O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript

Template Literal Types em TypeScript Template Literal Types permitem criar ti...

O que Todo Dev Deve Saber sobre SharedArrayBuffer e Atomics: Memória Compartilhada entre Workers
O que Todo Dev Deve Saber sobre SharedArrayBuffer e Atomics: Memória Compartilhada entre Workers

SharedArrayBuffer: O Que É e Por Que Usar SharedArrayBuffer é um objeto JavaS...