2010-06-20 28 views
6

Esta es una pregunta de eficiencia de SQL.Subconsultas de SQL: ¿hay una manera mejor?

Hace un tiempo tuve que escribir una colección de consultas para extraer datos de un sistema ERP. La mayoría de estos fueron bastante simples, pero uno de ellos resultó en una consulta bastante ineficiente y me ha molestado desde entonces, ya que tiene que haber una mejor manera.

El problema no es complejo. Tienes filas de datos de ventas. En cada fila tiene cantidad, precio de venta y el código del vendedor, entre otra información.

La comisión se paga según una escala progresiva escalonada. Cuanto más vendan, mejor será la comisión. Los pasos pueden ser 1000, 10000, 10000 $, etc. El problema del mundo real es más complejo, pero eso es esencialmente así.

La única manera que he encontrado para hacer esto era para hacer algo como esto (obviamente no es el verdadero consulta)

select qty, price, salesman, 
    (select top 1 percentage from comissions 
    where comisiones.salesman = saleslines.salesman 
    and saleslines.qty > comisiones.qty 
    order by comissiones.qty desc 
) percentage 
from saleslines 

esto da lugar a la comisión correcta, pero es terriblemente pesado.

¿Hay una mejor manera de hacerlo? No estoy buscando a alguien para reescribir mi sql, más 'eche un vistazo como consultas foobar' y puedo tomarlo desde allí.

La estructura de comisiones de la vida real se puede especificar para diferentes vendedores, artículos y clientes, e incluso para fechas de venta. También cambia de vez en cuando, por lo que todo tiene que ser impulsado por los datos en las tablas ... es decir, no puedo poner rangos fijos en el sql. La consulta actual devuelve unas 3-400000 filas y tarda alrededor de 20-30 segundos. Afortunadamente, solo se usa mensualmente pero la lentitud me molesta un poco.

Esto está en mssql.

Ian

edición:

que debería haber dado un ejemplo más complejo desde el principio. Ahora me doy cuenta de que a mi ejemplo inicial le faltan algunos elementos esenciales de la complejidad, disculpas a todos.

Esto puede captar mejor que

select client-code, product, product-family, qty, price, discount, salesman, 
    (select top 1 percentage from comissions 
     where comisiones.salesman = saleslines.salesman 
     and saleslines.qty > comisiones.qty 
     and [ 
      a collection of conditions which may or may not apply: 
      Exclude rows if the salesman has offered discounts above max discounts 
       which appear in each row in the commissions table 
      There may be a special scale for the product family 
      There may be a special scale for the product 
      There may be a special scale for the client 

      A few more cases 
      ] 
     order by [ 
      The user can control the order though a table 
      which can prioritize by client, family or product 
      It normally goes from most to least specific. 
      ] 
    ) percentage 
    from saleslines 

no hace falta decir la consulta real no es fácil de seguir. Solo para hacer la vida más interesante, su denominación es multilingüe.

Por lo tanto, para cada fila de línea de venta, la comisión puede ser diferente.

Puede parecer demasiado complejo, pero si piensas en cómo pagarías una comisión, tiene sentido. No quiere pagar a alguien por vender cosas con grandes descuentos, también quiere poder ofrecer a un cliente en particular un descuento en un producto en particular si compra unidades X. El vendedor debería ganar más si vende más.

En todo lo anterior, excluyo las ofertas especiales con fecha limitada.

Creo que las particiones pueden ser la solución, pero necesito explorar esto más a fondo, ya que no sé nada sobre las particiones. Me ha dado algunas ideas.

+0

si su única carrera mensual, yo no me preocuparía por eso. Parece que tiene que hacer muchos cálculos en varias tablas grandes. Si tuviera que ejecutarse con más frecuencia, es posible que desee construir el conjunto de resultados de forma incremental utilizando una estrategia diferente. –

+0

A veces es mejor medir realmente el trabajo e introducir una mejor infraestructura, como índices o discos más rápidos, etc. Debe perfilar la consulta y ver que los bits sean pesados. –

+0

¿Qué versión de SQL Server estás usando? –

Respuesta

3

Si está utilizando una versión de SQL Server que admite expresiones de tabla común, como SQL Server 2005 y versiones posteriores, una solución más eficiente podría ser:

With RankedCommissions As 
    (
    Select SL.qty, SL.price, SL.salesman, C.percentage 
     , Row_Number() Over (Partition By SL.salesman Order By C.Qty Desc) As CommissionRank 
    From SalesLines As SL 
     Join Commissions As C 
      On SL.salesman = C.salesman 
       And SL.qty > C.qty 
    ) 
Select qtr, price, salesman, percentage 
From RankedCommissions 
Where CommissionRank = 1 

Si usted necesita para tener en cuenta la posibilidad de que no hay valores Comisiones para un vendedor dado que el SalesLine.Qty> Commission.Qty, entonces usted podría hacer algo como:

With RankedCommissions As 
    (
    Select SL.qty, SL.price, SL.salesman, C.percentage 
     , Row_Number() Over (Partition By SL.salesman Order By C.Qty Desc) As CommissionRank 
    From SalesLines As SL 
     Join Commissions As C 
      On SL.salesman = C.salesman 
       And SL.qty > C.qty 
    ) 
Select SL.qtr, SL.price, SL.salesman, RC.percentage 
From SalesLines As SL 
    Left Join RankedCommissions As RC 
     On RC.salesman = SL.salesman 
      And RC.CommissionRank = 1 
+0

No * necesita * tener soporte para expresiones de tabla común para usar este enfoque. También podría escribir fácilmente el CTE como una vista en línea. La función 'ROW_NUMBER', sin embargo, requiere SQL Server 2005 o superior. –

+0

Mi consulta original se escribió para sql2000 pero el db migró a sql2008 recientemente, que es lo que provocó mi interés en aprovechar la nueva funcionalidad. Thomas, parece una solución. Sin embargo, es un enfoque muy diferente, así que tendré que sentarme un rato con esto y ver cómo puedo usarlo. Como la solución. Gracias -Ian – Ian

+0

@Ian - Comprender. CTE y funciones de clasificación como Row_Number son nuevas características increíblemente potentes en 2005/2008. Ayudan a simplificar una serie de consultas problemáticas como la que usted presentó. El verdadero truco en mi solución es el uso de la cláusula Partition By que ordena los valores por vendedor (¿vendedor?). – Thomas

0
select 
    qty, price, salesman, 
    max(percentage) 
from saleslines 
    inner join comissions on commisions.salesman = saleslines.salesman and 
      saleslines.qty > comissions.qty 
group by 
    qty, price, salesman 
+0

-1, tu código da el porcentaje más alto; la consulta original proporciona el porcentaje de la cantidad más alta. – Gabe

+0

INNER JOIN tiene una condición (saleslines.qty> comissions.qty) que filtra todas las líneas de comisión con una cantidad mayor que la cantidad de la línea de venta. El máximo en las filas restantes proporciona el porcentaje de la cantidad más alta (no el porcentaje más alto). – potatopeelings

+0

o así creo :-) – potatopeelings

Cuestiones relacionadas