1

Tengo que calcular el costo promedio de un articulo en base al inventario actual que tengo y las ultimas compras de dicho articulo(para cada articulo y son miles).

Tengo este Query que me da las ultimas 10 compras de cada articulo donde puedo ver el costo:

SELECT rs.Mov,rs.MovID,rs.Almacen,rs.Proveedor,rs.Fechaemision,rs.articulo,rs.Cantidad,rs.Costo,rs.DescuentoLinea,rs.DescuentoGlobal,rs.Moneda,rs.TipoCambio,
Total'Costo con descuento'
    FROM (
        SELECT b.Mov,b.MovID,b.Almacen,b.FechaEmision,b.Proveedor,a.Articulo,a.Cantidad,a.costo,a.DescuentoLinea,b.DescuentoGlobal,b.Moneda,b.TipoCambio,
         (a.Costo-(a.Costo*(isnull(a.DescuentoLinea,0)/100)))-(a.Costo*(isnull(b.DescuentoGlobal,0)/100))'Total'
         ,Rank() 
          over (Partition BY a.articulo
                ORDER BY a.articulo desc,b.fechaemision DESC ) AS Rank
        FROM CompraD a
        Left Join Compra b on a.ID=b.ID
        Left join Art c on a.Articulo=c.Articulo and b.Proveedor=c.Proveedor
        where a.Articulo like 'LN%' and b.Mov like 'Entrada%' and b.Estatus='Concluido' and b.Proveedor=c.Proveedor AND b.Almacen='LOPEZMATEO'
        ) rs 
        WHERE Rank <= 10
        order by Articulo asc, FechaEmision desc

Este es un ejemplo del resultado que me da : introducir la descripción de la imagen aquí

Ok, ahora tengo la consulta que me da el inventario total:

select Articulo,SUM(Disponible)'Disponible' from ArtDisponible 
where Articulo like 'LN%' and Almacen in (select Almacen from Alm where Grupo='Ventas') and Disponible>0
Group by Articulo
Order by Articulo asc

y este es el resultado que me da: introducir la descripción de la imagen aquí

Ahora, como ejemplo usaremos el articulo LN000001 que tiene 30 de Inventario global, lo que quiero es igualar la cantidad de la ultima compra con el inventario de esta manera:

Mi inventario global es de 30 articulos, mi ultima compra es de 10 articulos, la penultima compra es de 4 articulo, y la antepenultima compra (3er registro) es de 20 articulos, quiero que me tome los 10 articulos de la ultima compra (1er registro), los 4 de la penultima (2do registro) y solo 16 de los 20 de la antepenultima compra.

espero haberme explicado, llevo 3 días tratando con esto y no se me ha ocurrido como hacerlo aun. muchas gracias.

Lobos
  • 14,463
  • 3
  • 9
  • 28
AlCast
  • 11
  • 1
  • Mira [ask] para que tu pregunta sea mejor recibida. También, aprovecha y haz el [tour] para entender mejor cómo funcionamos y de paso obtener tu primera [medalla](https://es.stackoverflow.com/help/badges)! y no vas a poder.. sql es para hacer consutas, y vos necesitas un proceso completo para esto – gbianchi Aug 17 '20 at 16:44
  • Gracias por tus comentarios, tratare de redactar mejor mis preguntas futuras, respecto a lo que no es posible hacerlo en SQL, no se podrá hacer con un cursos o creando un método? – AlCast Aug 17 '20 at 16:51
  • Tenes que crear todo un proceso para hacerlo.. eso fue lo que quise decirte.. no se puede hacer solo con consultas... – gbianchi Aug 17 '20 at 17:12
  • @gbianchi, en versiones relativamente recientes de SQL Server (diría que de 2005/2008 para acá), [si que puede hacerse](https://es.stackoverflow.com/a/382644/21). – jachguate Aug 18 '20 at 01:55

1 Answers1

2

Puedes valerte de las funciones de ventana, para realizar el cálculo que deseas.

De la manera que primero se me ocurre es la realización de una suma de la cantidad comprada, tanto hasta la fila anterior como hasta la fila actual para, comparando ese valor con el total disponible, calcular cual es el remanente de esa compra (suponiendo que, sin un control de lotes, despachan siempre primero lo de las compras más antiguas, que ya es mucho suponer, pero es lo que entiendo que quieres lograr.

Mi solución está basada en el uso de CTE y funciones de ventana, y la voy explicando paso a paso.

  • Los CTE Compra, CompraDet, ArtDisponible simulan tus tablas originales, no he incluido todas sus columnas para que quede clara cual es la idea de la solución, sin todo el ruido extra, ni he seguido necesariamente sus nombres, ya que tu pregunta no incluye su estructura ni datos en formato de texto, por lo que he seguido mi propio estilo. Lo que si he dejado es la columna Estatus para mostrar a donde trasladar los filtros que tienes actualmente en las consultas.
  • El CTE Disponible hace el cálculo del total disponible por artículo
  • el CTE CompraOrdenada simula tu primera consulta, obteniendo las compras y ordenandolas. Además, realiza dos cálculos adicionales:
    • La columna CompraAcumuladaAnterior tiene el acumulado de las compras hasta antes de la compra de la fila actual. La primera compra de cada artículo tendrá NULL en esta columa.
    • La columna CompraAcumulada tiene el acumulado de las compras incluyendo la compra de la fila actual.
  • Finalmente, se encuentra la consulta final, que tira de las anteriores. Hago un left join entre CompraOrdenada y Disponible. Acá, el cálculo del saldo disponible de cada compra es trivial, gracias a que en cada fila tenemos el acumulado de las compras hasta antes y después de la compra que representa la fila, lo que queda en la columna DisponibleDeLaCompra:
    • Si el acumulado de las compras, incluyendo la compra actual, es menor o igual al disponible del artículo, sabemos que toda la cantidad comprada se encuentra disponible, por lo que devolvemos ese valor.
    • Si no, pero el acumulado de las compras hasta antes de la compra actual es menor o igual al disponible, sabemos que solo parte de la compra se encuentra disponible y podemos calcularla sacando la diferencia del disponible total del producto menos el acumulado de compras hasta antes de la compra actual.
    • Si no se cumple ninguna de las anteriores, los productos comprados ya se han agotado y el disponible es cero.

En código:

with
Compra as (
select *
  from (values (1, cast('20200601' as date), 'Concluido')
             , (2, '20200617', 'Concluido')
             , (3, '20200630', 'Cancelado')
             , (4, '20200718', 'Concluido')
             , (5, '20200726', 'Concluido')
       ) q1 (idCompra, FechaEmision, Estatus)
)
,
CompraDet as (
select *
  from (values (1, 'LN000001', 50, 3496.1)
             , (1, 'LN000002', 10, 121.12)
             , (2, 'LN000001', 20, 3546.85)
             , (2, 'LN000002', 4, 144.12)
             , (3, 'LN000001', 17, 3217.14)
             , (3, 'LN000002', 24, 119.54)
             , (4, 'LN000001', 4, 3546.85)
             , (4, 'LN000002', 37, 114.54)
             , (5, 'LN000001', 10, 3546.85)
             , (5, 'LN000002', 11, 119.31)
       ) q1 (idCompra, Articulo, Cantidad, Costo)
)
,
ArtDisponible as (
select *
  from (values ('LN000001', 'AA11', 10)
             , ('LN000001', 'AA22', 12)
             , ('LN000001', 'CC18', 8)
             , ('LN000002', 'AA11', 54)
       ) q1 (Articulo, Ubicacion, Disponible)
)
,
Disponible as (
select Articulo, sum(Disponible) DisponibleTotal
  from ArtDisponible
 group by Articulo 
)
,
CompraOrdenada as (
select   a.Articulo
       , a.idCompra
       , b.FechaEmision
       , a.Cantidad
       , a.Costo
       , rank() over (partition by a.Articulo order by b.FechaEmision desc, a.idCompra desc) Orden
       , sum(a.Cantidad) over (partition by a.Articulo order by b.FechaEmision desc, a.idCompra desc rows between unbounded preceding and 1 preceding) CompraAcumuladaAnterior
       , sum(a.Cantidad) over (partition by a.Articulo order by b.FechaEmision desc, a.idCompra desc rows between unbounded preceding and current row) CompraAcumulada 
  from CompraDet a
       inner join Compra b on b.idCompra = a.idCompra
 where b.Estatus = 'Concluido'
)
select   c.Articulo
       , c.idCompra
       , c.FechaEmision
       , c.Cantidad
       , c.Costo
       , c.Orden
       , case 
           when c.CompraAcumulada <= d.DisponibleTotal then c.Cantidad
           when coalesce(c.CompraAcumuladaAnterior, 0) <= d.DisponibleTotal then d.DisponibleTotal - coalesce(c.CompraAcumuladaAnterior, 0) 
           else 0
         end DisponibleDeLaCompra
  from CompraOrdenada c
       left join Disponible d on d.Articulo = c.Articulo
 where c.Articulo = 'LN000001'
 order by c.Articulo, c.Orden;

Lo que me entrega el siguiente resultado:

Articulo  idCompra   FechaEmision  Cantidad  Costo    Orden   DisponibleDeLaCompra
LN000001  5          2020-07-26    10        3546.85  1       10
LN000001  4          2020-07-18    4         3546.85  2       4
LN000001  2          2020-06-17    20        3546.85  3       16
LN000001  1          2020-06-01    50        3496.10  4       0
jachguate
  • 25,659
  • 7
  • 35
  • 61
  • Realmente interesante. Debe ser muy complejo de depurar (y ni hablar de visualizar los tiempos). Yo preferiria el proceso, pero la solucion esta muy buena. – gbianchi Aug 18 '20 at 02:26
  • Con respecto del tiempo de ejecución, en mi experiencia suelen ser tiempos más que aceptables, de hecho, mejores que los que generalmente se logran utilizando otras técnicas, pues esta aprovecha las excelentes capacidades del motor para trabajar con grandes volúmenes de información. Sobre la depuración, llevas mucha razón en que puede ser algo complejo, acotaría que lo es para quién no tiene clara la idea de lo que se busca. He utilizado la técnica por años en aplicaciones que están en producción y darle mantenimiento a este tipo de consultas me ha resultado no solo cómo sino sencillo. – jachguate Aug 18 '20 at 04:42