Ajuda Simplificar SQL [RESOLVIDO]

Pessoal boa noite, não manjo muito de SQL tem como alguém me ajudar.
Tenho esta SQL:

SELECT 
    -- Primeiro campo
     i3.id_produto
    
    -- calcula porcentagem de produtos vendidos a partir das contas já efetivadas
	 ,((SUM(i3.quantidade)/(SELECT SUM(i1.quantidade) FROM itens_da_venda i1, vendas v1
	     WHERE    (i1.id_venda     =     v1.id_venda )
            AND  (v1.dt_venda    >=    '2013-01-01 00:00:00' 
	         AND   v1.dt_venda    <=    '2013-12-31 23:59:59')
	  ))*100) AS porcentagem
	  
	  -- quantidade do produto vendido
	 ,SUM(i3.quantidade) AS quantidade_vendida
	 
	 -- soma todos todos os produtos vendidos e da a soma
	 ,(SELECT SUM(i2.quantidade) FROM itens_da_venda i2, vendas v2
	     WHERE    (i2.id_venda     =     v2.id_venda )
            AND  (v2.dt_venda    >=    '2013-01-01 00:00:00' 
	         AND   v2.dt_venda    <=    '2013-12-31 23:59:59')
	  ) AS vendas_totais
FROM itens_da_venda i3, vendas v3 
WHERE    (i3.id_venda     =     v3.id_venda )
    AND  (v3.dt_venda    >=    '2013-01-01 00:00:00' 
	 AND   v3.dt_venda    <=    '2013-12-31 23:59:59')
GROUP BY i3.id_produto 
ORDER BY porcentagem

porem está muito suja esta SQL tem coisas repetidas, gostaria de saber se tem como pegar uma parte exemplo:

 -- soma todos todos os produtos vendidos e da a soma
	 ,(SELECT SUM(i2.quantidade) FROM itens_da_venda i2, vendas v2
	     WHERE    (i2.id_venda     =     v2.id_venda )
            AND  (v2.dt_venda    >=    '2013-01-01 00:00:00' 
	         AND   v2.dt_venda    <=    '2013-12-31 23:59:59')
	  ) AS vendas_totais

ai depois como esta parte acima chamasse vendas_totais, eu usaria na sql final só alias

SELECT 
    -- Primeiro campo
     i3.id_produto
    
    -- calcula porcentagem de produtos vendidos a partir das contas já efetivadas
	 ,((SUM(i3.quantidade)/(vendas_totais))*100) AS porcentagem
	  
	  -- quantidade do produto vendido
	 ,SUM(i3.quantidade) AS quantidade_vendida
	 
	 -- soma todos todos os produtos vendidos e da a soma
	 , vendas_totais
FROM itens_da_venda i3, vendas v3 
WHERE    (i3.id_venda     =     v3.id_venda )
    AND  (v3.dt_venda    >=    '2013-01-01 00:00:00' 
	 AND   v3.dt_venda    <=    '2013-12-31 23:59:59')
GROUP BY i3.id_produto 
ORDER BY porcentagem

Que banco de dados está utilizando?

em geral, não é possível a utilização de alias (para fins operacionais) em SELECTs ou WHEREs…

Então, na verdade, 3 dos 4 principais* bancos de dados implementam as CTEs (ou queries WITH) e Windows Functions.

Com esse recurso você pode criar alias para esse tipo de coisa.

Perguntei do banco, justamente pq o MySql é o único que não tem esse recurso.
(E é o banco mais comum aqui no fórum)

  • Chamdo de principais: MySql, Oracle, Sql Server e Posgresql simplesmente por serem os mais comentados por aí.

Então, na verdade, 3 dos 4 principais* bancos de dados implementam as CTEs (ou queries WITH) e Windows Functions.

Com esse recurso você pode criar alias para esse tipo de coisa.

Perguntei do banco, justamente pq o MySql é o único que não tem esse recurso.
(E é o banco mais comum aqui no fórum)

  • Chamdo de principais: MySql, Oracle, Sql Server e Posgresql simplesmente por serem os mais comentados por aí.[/quote]
    no PostgreSQL também não é possível… pelo menos pelos testes que eu fiz… exemplo:

coluna "markup" não existe…
ou também:

Olá luivilella,

Para fazer o que deseja você terá que criar uma stored procedure ou function.

Nela você irá declarar variáveis que receberão o valor de cada grupo, ai no segundo grupo a ser calculado você já poderá utilizar o resultado do grupo anterior com a variável alimentada.

Espero ter lhe sido útil, abraços.

_ _
Fabiano Abreu
Papo Sql - Um blog com tutoriais, dicas e truques sobre SQL

O pessoal me desculpe esqueci de falar o banco… hehe malz…
então estou testando no MySQL.

Fabiano Abreu, isso que eu precisava você tem algum exemplo, eu também vou procurando aqui…
mas isso que você falou é exatamente o que eu previso, jogar o resultado do:

(SELECT SUM(i2.quantidade) FROM itens_da_venda i2, vendas v2
	     WHERE    (i2.id_venda     =     v2.id_venda )
            AND  (v2.dt_venda    >=    '2012-01-01 00:00:00' 
	         AND   v2.dt_venda    <=    '2013-12-31 23:59:59')
	         AND  (v2.id_cliente  >=     0
	         AND   v2.id_cliente  <=     9999999999999999)
	  )

em uma variável pra que possa utiliza-la em outra SQL.

Você pode fazer a sp mais ou menos assim:

[code]declare l_porcentagem decimal(14,2);
declare l_quantidade_vendida int;
declare lvendas_totais int;

set l_porcentagem = (SELECT
– Primeiro campo
i3.id_produto

-- calcula porcentagem de produtos vendidos a partir das contas já efetivadas  
 ,((SUM(i3.quantidade)/(SELECT SUM(i1.quantidade) FROM itens_da_venda i1, vendas v1  
     WHERE    (i1.id_venda     =     v1.id_venda )  
        AND  (v1.dt_venda    >=    '2013-01-01 00:00:00'   
         AND   v1.dt_venda    <=    '2013-12-31 23:59:59')  
  ))*100)) ...

– aqui você já tem condições de usar o l_porcentagem para fazer algum tipo de calculo.

[/code]

Espero ter lhe sido útil, abraços.

Obs: Não testei os códigos acima.

_ _
Fabiano Abreu
Papo Sql - Um blog com tutoriais, dicas e truques sobre SQL

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.

/o/ AbelBueno Muito Top!!!
Vou pesquisar mais sobre o assunto, muito obrigado por esclarecer estas coisas!