Pregunta

Tengo un producto que cuesta 4 € y necesito dividir este dinero para 3 departamentos. En la segunda columna, necesito obtener el número de filas para este producto y dividir para el número de departamentos.

Mi consulta:

select
   department, totalvalue,
   (totalvalue / (select count(*) from departments d2 where d2.department = p.product))
       dividedvalue
from products p, departments d
where d.department = p.department

Department  Total Value  Divided Value
----------  -----------  -------------
A           4            1.3333333
B           4            1.3333333
C           4            1.3333333

Pero cuando sumo los valores, obtengo 3.999999. Por supuesto, con cientos de filas obtengo grandes diferencias ... ¿Hay alguna posibilidad de definir 2 números decimales y el último valor redondo? (Mis resultados serían 1.33 1.33 1.34) Quiero decir, ¿alguna forma de ajustar la última fila?

¿Fue útil?

Solución

Con seis decimales de precisión, necesitaría unas 5,000 transacciones para notar una diferencia de un centavo, si redondee el número final a dos decimales. Aumentar el número de decimales a un nivel aceptable eliminaría la mayoría de los problemas, es decir, utilizando 9 decimales que necesitaría alrededor de 5,000,000 de transacciones para notar una diferencia de un centavo.

Otros consejos

Para manejar esto, para cada fila tendría que hacer lo siguiente:

  • Realizar la división
  • Redondea el resultado al número apropiado de centavos
  • Suma la diferencia entre la cantidad redondeada y el resultado de la operación de división
  • Cuando la suma de las diferencias excede el lugar decimal más bajo (en este caso, 0.01), agregue esa cantidad a los resultados de la próxima operación de división (después del redondeo).

Esto distribuirá cantidades fraccionarias de manera uniforme en las filas. Desafortunadamente, no hay una manera fácil de hacer esto en SQL con consultas simples; Probablemente sea mejor realizar esto en código de procedimiento.

En cuanto a lo importante que es, cuando se trata de aplicaciones e instituciones financieras, cosas como esta son muy importantes, incluso si es solo por un centavo, e incluso si solo puede ocurrir cada X número de registros; Por lo general, los usuarios quieren ver los valores que se atan al centavo (o lo que sea que sea su unidad de moneda) exactamente.

Lo más importante, no desea permitir una exploit como "Superman III" o "Espacio de oficina" que se produzca.

Tal vez puedas hacer una fila de Forth que sea total: suma (A, B, C). Pero depende de lo que desee hacer, si necesita un valor exacto, puede mantener fracciones, de lo contrario, truncar y no le importa la pérdida virtual

También se puede hacer simplemente agregando la diferencia de redondeo de un valor particular al siguiente número a redondear (antes del redondeo). De esta manera, la pila permanece siempre del mismo tamaño.

Aquí hay una implementación de TSQL (Microsoft SQL Server) del algoritmo proporcionado por Martín:

-- Set parameters.
DECLARE @departments INTEGER = 3;
DECLARE @totalvalue DECIMAL(19, 7) = 4.0;

WITH
CTE1 AS
(
    -- Create the data upon which to perform the calculation.
    SELECT

        1 AS Department
        , @totalvalue AS [Total Value]
        , CAST(@totalvalue / @departments AS DECIMAL(19, 7)) AS [Divided Value]
        , CAST(ROUND(@totalvalue / @departments, 2) AS DECIMAL(19, 7)) AS [Rounded Value]

    UNION ALL

    SELECT

        CTE1.Department + 1
        , CTE1.[Total Value]
        , CTE1.[Divided Value]
        , CTE1.[Rounded Value]

    FROM

        CTE1

    WHERE

        Department < @departments
),

CTE2 AS
(
    -- Perform the calculation for each row.
    SELECT

        Department
        , [Total Value]
        , [Divided Value]
        , [Rounded Value]
        , CAST([Divided Value] - [Rounded Value] AS DECIMAL(19, 7)) AS [Rounding Difference]
        , [Rounded Value] AS [Calculated Value]

    FROM

        CTE1

    WHERE

        Department = 1

    UNION ALL

    SELECT

        CTE1.Department
        , CTE1.[Total Value]
        , CTE1.[Divided Value]
        , CTE1.[Rounded Value]
        , CAST(CTE1.[Divided Value] + CTE2.[Rounding Difference] - ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))
        , CAST(ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))

    FROM

        CTE2

            INNER JOIN CTE1
                ON CTE1.Department = CTE2.Department + 1

)

-- Display the results with totals.
SELECT 

    Department
    , [Total Value]
    , [Divided Value]
    , [Rounded Value]
    , [Rounding Difference]
    , [Calculated Value]

FROM 

    CTE2

UNION ALL

SELECT

    NULL
    , NULL
    , SUM([Divided Value])
    , SUM([Rounded Value])
    , NULL
    , SUM([Calculated Value])

FROM

    CTE2

;

Producción:

enter image description here

Puede enchufar los números que desee en la parte superior. No estoy seguro de si hay una prueba matemática para este algoritmo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top