Uso de Consultas F e Q no Django ORM, SQL Nativo, Transações e Locks

No ORM do Django, as consultas padrão não suportam passagme de parâmetros diretamente para comparações entre campos e limitam-se a condições lógicas do tipo "e". Para superar essas limitações, utilizamos as classes F e Q.

A classe F permite referenciar valores de campos do banco de dados em consultas, tratando-os como variáveis. A classe Q possibilita a construção de consultas complexas com operadores lógicos como "ou" (|), "e" (&) e "não" (~Q).

Exemplo de Modelo e Dados

from django.db import models

class Produto(models.Model):
    id = models.AutoField(primary_key=True)
    nome = models.CharField(max_length=50)
    preco = models.DecimalField(max_digits=10, decimal_places=2)
    comentarios = models.IntegerField(default=0)
    visualizacoes = models.IntegerField(default=0)

Utilizando a Classe F

Para comparar campos do banco de dados, usamos F para passar o nome do campo como parâmetro. Isso é útil para consultas condicionais ou atualizações baseadas em outros campos.

from django.db.models import F

# Consulta: produtos onde comentários são maiores que visualizações
resultado_f1 = Produto.objects.filter(comentarios__gt=F('visualizacoes'))

# Atualização: incrementar o número de comentários em 1 para todos os produtos
Produto.objects.update(comentarios=F('comentarios') + 1)

# Renomear campos em anotações
resultado_f2 = Produto.objects.filter(nome__icontains='Django').annotate(
    nome_customizado=F('nome')
).values('id', 'nome_customizado')

Utilizando a Classe Q

Para combinar múltiplas condições com operadores lógicos, usamos Q. Isso permite consultas do tipo "ou" ou "não", que não são possíveis com filtros padrão.

from django.db.models import Q

# Condição "e" padrão (sem Q)
resultado_e = Produto.objects.filter(preco__lt=100, comentarios__gt=5)

# Condição "ou" com Q
resultado_ou = Produto.objects.filter(Q(preco__lt=100) | Q(comentarios__gt=5))

# Condição "não" com ~Q
resultado_nao = Produto.objects.filter(Q(preco__lt=100) & ~Q(visualizacoes__gt=50))

# Combinação complexa
resultado_complexo = Produto.objects.filter(
    Q(preco__lt=100) | (~Q(comentarios__gt=20) & Q(nome__startswith='Py'))
)

Uso Avançado de Q

Q permite construir consultas dinamicamente, por exemplo, com base em parâmetros de entrada.

def buscar_produtos(request):
    preco_maximo = request.GET.get('preco')
    nome_filtro = request.GET.get('nome')
    
    consulta = Q()
    if preco_maximo:
        consulta &= Q(preco__lte=float(preco_maximo))
    if nome_filtro:
        consulta &= Q(nome__icontains=nome_filtro)
    
    return Produto.objects.filter(consulta)

SQL Nativo no Django

Para situações que exigem SQL puro, o Django oferece duas abordagens principais. É importante usar parâmetros para evitar injeção de SQL.

1. Usando o Método raw()

resultado_raw = Produto.objects.raw('SELECT * FROM app_produto WHERE preco > %s', [50.0])
for item in resultado_raw:
    print(item.nome, item.preco)

2. Usando o Módulo connections

from django.db import connections

with connections['default'].cursor() as cursor:
    cursor.execute('UPDATE app_produto SET preco = preco * 1.1 WHERE id = %s', [1])
    cursor.execute('SELECT nome, preco FROM app_produto WHERE visualizacoes > %s', [100])
    linhas = cursor.fetchall()  # fetchone(), fetchmany(n)

Transações no Django

Transações garantem a atomicidade de operações, essenciais para manter a consistência dos dados.

Craacterísticas ACID

  • Atomicidade: Todas as operações de uma transação são executadas ou nenhuma é.
  • Consistência: O banco de dados permanece em um estado válido após a transação.
  • Isolamento: Transações concorrentes não interferem entre si.
  • Durabilidade: Alterações confirmadas são permanentes.

Uso Básico de Transações

from django.db import transaction

# Transação em um bloco de contexto
try:
    with transaction.atomic():
        Produto.objects.create(nome='Item A', preco=10.0)
        Produto.objects.create(nome='Item B', preco='texto_inválido')  # Isto causa um erro
except Exception:
    print('Transação revertida devido a erro.')

Locks e Concorrência

O Django suporta locks para controle de concorrência, como o select_for_update, que bloqueia registros durante uma transação.

from django.db import transaction

with transaction.atomic():
    produto = Produto.objects.select_for_update().get(id=1)
    produto.visualizacoes += 1
    produto.save()

Configurações Avançadas de Transações

Para habilitar transações globalmente nas configurações do Django:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'meubanco',
        'ATOMIC_REQUESTS': True  # Habilita transações automaticamente para cada requisição
    }
}

Para desativar transações em uma view específica, use o decorador non_atomic_requests:

from django.db import transaction
from django.utils.decorators import method_decorator
from rest_framework.views import APIView

@method_decorator(transaction.non_atomic_requests, name='dispatch')
class MinhaView(APIView):
    def get(self, request):
        # Operações que não precisam de transação
        return Response({'status': 'ok'})

Tags: Django ORM SQL Transações Locks

Publicado em 6-21 19:46