Perguntei justamente por ver que é o banco mais comum aqui.
É possível resolver apenas com SQL, mas não sei se a query fica muito mais limpa do que a original.
(Na minha opinião fica).
É importante entender que, em SQL, você pode sempre aninhar o resultado de um SELECT no FROM de outro SELECT.
Isso permite queries complexas em blocos.
No seu caso a primeira coisa que faria, seria transformar o seu critério (periodo) em uma “tabela virtual”
SELECT '2012-03-06 00:00:00' dt_ini, '2012-03-06 00:00:00' dt_fim
Com isso poderá aproveitar esse filtro em outros trechos.
A segunda parte seria agrupar quantidade por produto:
SELECT
i1.id_produto,
sum(i1.quantidade) qt_vendida
FROM
itens_da_venda i1
INNER JOIN vendas v1
ON i1.id_venda = v1.id_venda
INNER JOIN ( SELECT '2012-03-06 00:00:00' dt_ini, '2012-03-06 00:00:00' dt_fim ) as periodo
ON v1.dt_venda BETWEEN periodo.dt_ini AND periodo.dt_fim
GROUP BY
i1.id_produto,
periodo.dt_ini,
periodo.dt_fim
Repare que o período faz um JOIN com sua tabela de vendas, obtendo o mesmo resultado de um filtro no WHERE.
Até aí sem vantagem nenhuma nessa abordagem, mas agora vamos adicionar o total geral de produtos:
SELECT
i1.id_produto,
sum(i1.quantidade) qt_vendida,
(
SELECT SUM( quantidade )
FROM
itens_da_venda i2
INNER JOIN vendas v2
ON i2.id_venda = v2.id_venda
WHERE
v2.dt_venda BETWEEN periodo.dt_ini AND periodo.dt_fim
) qt_total
FROM
itens_da_venda i1
INNER JOIN vendas v1
ON i1.id_venda = v1.id_venda
INNER JOIN ( SELECT '2012-03-06 00:00:00' dt_ini, '2012-03-06 00:00:00' dt_fim ) as periodo
ON v1.dt_venda BETWEEN periodo.dt_ini AND periodo.dt_fim
GROUP BY
i1.id_produto,
periodo.dt_ini,
periodo.dt_fim
Aqui já começa a aparecer vantagem para aquela “tabela virtual” de período.
Não precisamos repetir as datas para esse total.
Com todos os valores discretos no resultado, aninhamos novamente tudo isso, para gerar os valores que queremos:
SELECT
sub.id_produto,
sub.qt_vendida / sub.qt_total * 100 as porcentagem,
sub.qt_vendida,
sub.qt_total
FROM
(
SELECT
i1.id_produto,
sum(i1.quantidade) qt_vendida,
(
SELECT SUM( quantidade )
FROM
itens_da_venda i2
INNER JOIN vendas v2
ON i2.id_venda = v2.id_venda
WHERE
v2.dt_venda BETWEEN periodo.dt_ini AND periodo.dt_fim
) qt_total
FROM
itens_da_venda i1
INNER JOIN vendas v1
ON i1.id_venda = v1.id_venda
INNER JOIN ( SELECT '2012-03-06 00:00:00' dt_ini, '2012-03-06 00:00:00' dt_fim ) as periodo
ON v1.dt_venda BETWEEN periodo.dt_ini AND periodo.dt_fim
GROUP BY
i1.id_produto,
periodo.dt_ini,
periodo.dt_fim
) sub
ORDER BY
porcentagem
E com isso obtemos o mesmo resultado da sua query original.
Não sei dizer em termos de performance qual das duas roda melhor, seria interessante avaliar.
Minhas ferramentas para mysql aqui são bem limitadas.
(Se alguém tiver uma bom client para sugerir, agradeço)
Sugiro fazer um teste algumas centenas de milhares de registros e ver como cada uma se comporta.
Desculpe, não me expressei direito na resposta.
Eu quis dizer que você consegue o efeito nos outros bancos, mas não exatamente com a mesma sintaxe.
Citei 2 recursos que podem ajudar muito nesse tipo de query mais complexa.
CTE (ou WITH queries ) : Permitem criar uma espécie view antes do select principal.
Não sei se a sintaxe é exatamente assim para todos os bancos, mas no SQL SERVER, por exemplo, essa query acima poderia ser escrita assim:
WITH
vendas_periodo AS (
SELECT id_produto, SUM(quantidade) qt_vendida
FROM
vendas v
INNER JOIN itens_da_venda iv
ON v.id_venda = iv.id_venda
WHERE
v.dt_venda BETWEEN '2012-03-06 00:00:00' AND '2012-03-06 23:59:59'
GROUP BY
id_produto
),
venda_total AS (
SELECT SUM( qt_vendida ) qt_total from vendas_periodo
)
SELECT
vp.id_produto,
vp.qt_vendida / vt.qt_total * 100 as porcentagem,
vp.qt_vendida,
vt.qt_total
FROM
vendas_periodo vp,
venda_total vt
ORDER BY
porcentagem
A separação com WITH permite separar blocos lógicos da query e mesmo reaproveitar esses bloco em outros “WITH”
(Repare que venda_total utiliza vendas_periodo )
Windows Function: Permitem que você utilize diferentes funções de agregação com o mesmo conjunto de dados em agrupamentos diferentes.
A query em questão, por exemplo, poderia ficar (exemplo para Sql Server também):
SELECT
sub.id_produto,
sub.qt_vendida / sub.qt_total * 100 as porcentagem,
sub.qt_vendida,
sub.qt_total
FROM
(
SELECT
i1.id_produto,
SUM(i1.quantidade) qt_vendida,
SUM(SUM(i1.quantidade)) over () qt_total
FROM
itens_da_venda i1
INNER JOIN vendas v1
ON i1.id_venda = v1.id_venda
WHERE
v1.dt_venda BETWEEN '2012-03-06 00:00:00' AND '2012-03-06 00:00:00'
GROUP BY
i1.id_produto
) sub
ORDER BY
porcentagem
Que na minha opinião ficou a mais limpa das três opções.