SQL (ANSI)ProgramaciónDesarrollador de Bases de Datos

En el contexto de la valoración del inventario y la asignación de costos, ¿cómo implementarías un estricto algoritmo de asignación FIFO (Primero en entrar, primero en salir) utilizando solo SQL ANSI para emparejar cada transacción de venta saliente con lotes de compra entrantes específicos, calculando la base de costo exacta para cada unidad vendida?

Supere entrevistas con el asistente de IA Hintsage

Respuesta a la pregunta

Historia de la pregunta

La contabilidad del inventario requiere un seguimiento preciso de los costos a medida que los bienes fluyen a través de un almacén. FIFO (Primero en entrar, primero en salir) asume que los artículos comprados más antiguos se venden primero, lo cual es crucial para productos perecederos o entornos inflacionarios. A diferencia del costo promedio, FIFO exige emparejar cada venta con lotes de compra históricos específicos, creando un desafío de relación de muchos a muchos que precede a los estándares modernos de SQL.

El problema

Dadas dos tablas—purchases (lot_id, cantidad, costo_unitario, recibido_en) y sales (sale_id, cantidad, vendido_en)—debemos asignar cada cantidad de venta al inventario no vendido más antiguo disponible. Esto crea tres complejidades: una sola venta puede consumir múltiples lotes parciales, un solo lote puede abarcar varias ventas y la asignación debe respetar el orden cronológico sin bucles procedimentales. Los enfoques tradicionales de JOIN fallan porque no pueden rastrear el estado de agotamiento de los lotes individuales a través de las filas.

La solución

Usa funciones de ventana para calcular sumas acumulativas, transformando cantidades discretas en intervalos contiguos. Convierte las compras en rangos acumulativos [prior_cumulative+1, current_cumulative] y las ventas en rangos similares. Un JOIN sobre intervalos superpuestos identifica qué lotes alimentan qué ventas. La longitud de la intersección multiplicada por el costo_unitario del lote produce la base de costos. Este enfoque de teoría de conjuntos evita la recursión y opera totalmente dentro de SQL ANSI.

CON compra_acumulada COMO ( SELECCIONAR lot_id, costo_unitario, recibido_en, SUMA(cantidad) SOBRE (ORDENAR POR recibido_en, lot_id FILAS SIN LÍMITES ANTES) - cantidad COMO cum_start, SUMA(cantidad) SOBRE (ORDENAR POR recibido_en, lot_id FILAS SIN LÍMITES ANTES) COMO cum_end DE compras ), ventas_acumuladas COMO ( SELECCIONAR sale_id, vendido_en, SUMA(cantidad) SOBRE (ORDENAR POR vendido_en, sale_id FILAS SIN LÍMITES ANTES) - cantidad COMO cum_start, SUMA(cantidad) SOBRE (ORDENAR POR vendido_en, sale_id FILAS SIN LÍMITES ANTES) COMO cum_end DE ventas ) SELECCIONAR s.sale_id, p.lot_id, p.costo_unitario, MENOR(s.cum_end, p.cum_end) - MAYOR(s.cum_start, p.cum_start) COMO cantidad_asignada, (MENOR(s.cum_end, p.cum_end) - MAYOR(s.cum_start, p.cum_start)) * p.costo_unitario COMO costo_asignado DE ventas_acumuladas s JOIN compra_acumulada p EN s.cum_start < p.cum_end Y s.cum_end > p.cum_start ORDENAR POR s.sale_id, p.recibido_en;

Situación de la vida real

Un distribuidor farmacéutico rastrea lotes de medicamentos con precios mayoristas variables debido a fluctuaciones de proveedores. Las regulaciones de la FDA requieren una trazabilidad exacta de costos para cada píldora vendida, lo que requiere atribución de costos por unidad en lugar de costo promedio. El almacén procesa miles de transacciones diarias a través de cientos de SKUs, con lotes de compra que llegan en intervalos y precios impredecibles.

El enfoque inicial utilizaba un CURSOR en un procedimiento almacenado, iterando las ventas secuencialmente y decrementando los saldos de los lotes fila por fila. Aunque funcionalmente correcto, este método causaba una grave contención de bloqueos durante las horas pico, ya que mantenía bloqueos en las tablas de inventario durante períodos prolongados. Además, la lógica procedimental fallaba en las pruebas de cumplimiento de ACID bajo operaciones concurrentes de INSERT, resultando en lecturas fantasmas y doble gasto de lotes de inventario.

El equipo consideró brevemente usar disparadores para mantener una tabla de saldo en ejecución que se actualizara automáticamente con cada venta. Sin embargo, esto introdujo errores de tabla mutante en Oracle y una gestión de restricciones diferibles compleja en PostgreSQL, cascando latencia en el sistema OLTP. El enfoque del disparador también complicó las auditorías al oscurecer la lógica exacta de asignación dentro de los metadatos de la base de datos en lugar de un código de consulta explícito.

La solución elegida implementó el método de superposición de intervalos utilizando funciones de ventana para precalcular los límites acumulativos. Esto permitió que el optimizador de la base de datos utilizara uniones de orden y combinación en lugar de uniones en bucle anidado, reduciendo el cálculo de costos para un informe de venta de 10,000 unidades de 45 segundos a 200 milisegundos. El resultado permitió la generación de informes en tiempo real del costo de bienes vendidos durante el cierre financiero de fin de mes sin bloquear transacciones de inventario, logrando un cumplimiento total con el aislamiento SERIALIZABLE.

Lo que a menudo los candidatos pasan por alto

¿Cómo manejas el caso extremo donde los eventos de compra y venta comparten la misma marca de tiempo, asegurando un orden FIFO determinista?

Los candidatos a menudo suponen que ORDER BY vendido_en es suficiente. Sin embargo, cuando las marcas de tiempo colisionan, el orden de asignación se vuelve no determinista y puede variar entre ejecuciones de consulta. La solución requiere una columna de desempate—típicamente la clave primaria o una secuencia de auto-incremento—dentro de la cláusula ORDER BY de la función de ventana. Sin este estricto orden, dos ventas que ocurren simultáneamente podrían consumir incorrectamente la misma cantidad de lote dos veces debido a condiciones de carrera en el plan de ejecución del optimizador de consultas, violando la integridad del inventario.

¿Por qué utilizar FLOAT o DOUBLE PRECISION para columnas de cantidad corrompe los resultados de la asignación FIFO?

Muchos candidatos utilizan tipos de punto flotante para cálculos monetarios o de cantidad, sin saber que el punto flotante IEEE 754 no puede representar con precisión fracciones decimales como 0.1. Esta imprecisión causa errores de suma acumulativa que se acumulan a miles de filas, resultando en un lote que se espera contenga exactamente 100 unidades registrando como 99.999999 o 100.000001. En consecuencia, las matemáticas de superposición de intervalos o bien pierden superposiciones válidas o crean asignaciones negativas fantasma. La solución exige tipos DECIMAL o NUMERIC con precisión explícita para todas las columnas de cantidad y costo para asegurar aritmética exacta de enteros y prevenir discrepancias financieras.

¿Cómo corriges los errores de redondeo acumulados cuando una venta consume centavos fraccionarios en múltiples lotes con diferentes costos unitarios?

Cuando una venta se divide en tres lotes a precios de $0.33, $0.33 y $0.34, el redondeo ingenuo de cada artículo puede hacer que la suma de los costos asignados se desvíe del valor total esperado de la venta por un centavo. Los candidatos a menudo calculan cantidad_asignada * costo_unitario directamente sin considerar el contexto de redondeo o los restos residuales. La solución robusta aplica redondeo de banquero (redondear a la par) o preserva los valores no redondeados en una subconsulta, y luego aplica un algoritmo de corrección en la consulta externa. Este ajuste añade la diferencia residual a la línea de asignación más grande, obligando a la suma a coincidir exactamente con el valor total de la venta mientras mantiene la precisión de la auditoría.