Explicaremos de forma sucinta o funcionamento dos recursos “join” e “prefetch” do DBIx::Class, uma implementação de Object-Relational Mapping — ORM para Perl. Assumimos que o leitor tem alguma noção de SQL e Perl.

Estrutura de Exemplo

Para demonstrar os problemas e fazer operações sobre eles usaremos a seguinte estrutura de banco de dados:

author: id, name
book: id, name
author_book: author_id, book_id

Os relacionamentos são:

author -- has many -- author_book
book -- has many -- author_book

author_book -- belongs_to -- author
author_book -- belongs_to -- book

E como um atalho, podemos escrever:

author -- many to many -- book
book -- many to many -- author

Considerando os relacionamentos e o DBIx::Class, podemos usar as seguintes estruturas, onde (como era esperado) $author representa um autor e $book, um livro:

$author->author_book # retorna todos os registros de author_book relacionadas ao autor em questão
$book->author_book # retornaria todos os registros de author_book relacionadas ao livro em questão

$author->books # retorna todos os livros do autor em questão
$book->authors # retorna todos os autores do livro em questão

 

O Problema

O problema que queremos mostrar gira em torno

  1. da quantidade de dados requisitados ao banco de dados, e
  2. da quantidade de requisições feitas ao banco de dados

Devemos estar atentos para o código SQL gerado pelas implementações de ORM que usamos, para que não sobrecarreguemos sem querer nossas aplicações.

JOINs em SQL

Um ponto importante para ser entendido é o dos JOINs, em SQL.

JOINs têm grande utilidade para 1) filtrar dados em uma tabela considerando dados de outra tabela que lhes são relacionados, e/ou 2) efetivamente trazer mais dados do banco de dados em uma única requisição.

Por exemplo, para o caso de querermos pegar do banco apenas os autores que têm livros cujos nomes contenham uma determinada string, temos duas opções:

  1. Fazer o filtro e trazer apenas os dados dos autores
  2. Fazer o filtro e trazer, junto aos dados dos autores, também os dados das tabelas relacionadas (por exemplo, os dados nas tabelas com as quais o filtro foi feito)

Para o primeiro caso (trazer apenas os dados dos autores), teríamos:

SELECT me.* FROM author me
JOIN author_book ab ON me.id = ab.author_id
JOIN book b ON ab.book_id = b.id
WHERE b.name LIKE '%alguma_string%'

Para o segundo caso (trazer mais dados além dos dos autores):

SELECT me.*, ab.*, b.* FROM author me
JOIN author_book ab ON me.id = ab.author_id
JOIN book b ON ab.book_id = b.id
WHERE b.name LIKE '%alguma_string%'

O detalhe está nos asteriscos entre o SELECT e o FROM. Com eles, podemos indicar se trazemos dados de uma, de duas ou das três tabelas em questão.

DBIx::Class -> join e prefetch

Voltando ao DBIx::Class, a primeira consulta seria feita com o uso do recurso “join”;

$author_resultset->search(
    undef,
    { join => { author_book => 'book' } }
);

a segunda seria feita usando o recurso “prefetch”.

$author_resultset->search(
    undef,
    { prefetch => { author_book => 'book' } }
);

Para os casos onde não vamos efetivamente usar os dados das tabelas relacionadas (no caso, author_book e book), não há porque usar o prefetch (seria até ruim usá-lo), pois apenas estaríamos fazendo com que mais dados (que não seriam usados) fossem processados pelo banco de dados e transferidos a quem fez a requisição.

No entanto, para os casos onde vamos efetivamente utilizar dados de tabelas relacionadas, é importante atentar para a consulta.

Suponhamos um caso no qual queremos mostrar o nome de cada autor e também o nome de seus livros. Este caso mostra bem uma situação na qual é relevante os dados das tabelas relacionadas: é uma situação de prefetch. Ao usar o atributo “join” neste caso, tem-se mais duas consultas ao banco de dados para cada livro de cada autor.

Consideremos o seguinte código

for $author (@authors) {
    for $author_book ($author->author_book) {
        print $author->name . ' ' . $author_book->book->name . "\n";
    }
}

Se o array @authors for obtido com uma consulta que usa apenas o recurso “join”, teremos os seguintes outputs e consultas no banco de dados:

$ DBIC_TRACE=1 perl main.pl
SELECT me.id, me.name FROM author me:
SELECT me.author_id, me.book_id FROM author_book me WHERE ( me.author_id = ? ): ‘1’
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘1’
Author 1 Book 1
SELECT me.author_id, me.book_id FROM author_book me WHERE ( me.author_id = ? ): ‘2’
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘2’
Author 2 Book 2
SELECT me.author_id, me.book_id FROM author_book me WHERE ( me.author_id = ? ): ‘3’
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘3’
Author 3 Book 3
SELECT me.author_id, me.book_id FROM author_book me WHERE ( me.author_id = ? ): ‘4’
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘4’
Author 4 Book 4
SELECT me.author_id, me.book_id FROM author_book me WHERE ( me.author_id = ? ): ‘5’
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘5’
Author 5 Book 5
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘6’
Author 5 Book 6
SELECT me.id, me.name FROM book me WHERE ( me.id = ? ): ‘7’
Author 5 Book 7

Se aquela consulta usar o recurso “prefetch”, teremos:

$ DBIC_TRACE=1 perl main.pl
SELECT me.id, me.name, author_book.author_id, author_book.book_id, book.id, book.name
FROM author me
LEFT JOIN author_book author_book ON author_book.author_id = me.id
LEFT JOIN book book ON book.id = author_book.book_id ORDER BY author_book.author_id:
Author 1 Book 1
Author 2 Book 2
Author 3 Book 3
Author 4 Book 4
Author 5 Book 5
Author 5 Book 6
Author 5 Book 7

Como foi demonstrado, o segundo caso realiza apenas uma consulta, não importando quantos autores temos ou quantos livros têm cada um deles. No primeiro caso, o número de consultas adicionais cresce se tivermos mais autores e livros.

Sem ver o código SQL gerado, “join” e “prefetch” parecem realizar a mesma funcionalidade, mas, olhando melhor, vemos que foram implementados para objetivos diferentes.