Как можно округлить столбец в одном запросе SQL без изменения общей суммы? - PullRequest
3 голосов
/ 17 сентября 2009

У меня есть таблица, определенная так:

create table #tbFoo
(bar float)

И я ищу способ округлить каждое значение, содержащееся в столбце столбца, без изменения общей суммы (которая, как известно, является целым числом или очень близка к целому числу из-за точности числа с плавающей запятой).

Округление каждого значения до ближайшего целого не будет работать (например, 1,5; 1,5 будет округлено до 1; 1 или 2; 2)

Это довольно легко сделать, используя несколько запросов (например, сохранение исходной суммы, округление, вычисление новой суммы и обновление столько строк, сколько необходимо для возврата к исходной сумме), но это не очень элегантное решение .

Есть ли способ сделать это с помощью одного запроса SQL?


Я использую SQL Server 2008, поэтому приветствуются решения, использующие преимущества этого конкретного поставщика.


Редактировать: Я ищу запрос, минимизирующий разницу между старыми значениями и новыми. Другими словами, значение никогда не должно округляться, если большее значение было округлено в меньшую сторону, и наоборот

Ответы [ 3 ]

3 голосов
/ 17 сентября 2009

Обновление:

См. Это решение объяснено более подробно в статье в моем блоге:


Вам необходимо сохранить совокупное смещение для каждого значения:

1.2   (1 + 0.0)  ~ 1    1   1.2   +0.2
1.2   (1 + 0.2)  ~ 1    2   2.4   +0.4
1.2   (1 + 0.4)  ~ 1    3   3.6   +0.6
1.2   (1 + 0.6)  ~ 2    5   4.8   -0.2
1.2   (1 - 0.2)  ~ 1    6   6.0   0.0

Это легко сделать в MySQL, но в SQL Server вам придется написать курсор или использовать кумулятивные подвыборы (которые менее эффективны).

Обновление:

В приведенном ниже запросе выбирается разница между суммами значений и округленных до ближайшего меньшего целого числа.

Это дает нам число (N) значений, которые мы должны округлить.

Затем мы упорядочиваем значения по их дробной части (те, которые ближе к их потолку, идут первыми) и округляют первое N вверх, остальные вниз.

SELECT  value,
        FLOOR(value) + CASE WHEN ROW_NUMBER() OVER (ORDER BY value - FLOOR(value) DESC) <= cs THEN 1 ELSE 0 END AS nvalue
FROM    (
        SELECT  cs, value
        FROM    (
                SELECT  SUM(value) - SUM(FLOOR(value)) AS cs
                FROM    @mytable
                ) c
        CROSS JOIN
                @mytable
        ) q

Вот скрипт для тестовых данных:

SET NOCOUNT ON
GO
SELECT  RAND(0.20090917)
DECLARE @mytable TABLE (value FLOAT NOT NULL)
DECLARE @cnt INT;
SET @cnt = 0;
WHILE @cnt < 100
BEGIN
        INSERT
        INTO    @mytable
        VALUES  (FLOOR(RAND() * 100) / 10)
        SET @cnt = @cnt + 1
END

INSERT
INTO    @mytable
SELECT  600 - SUM(value)
FROM    @mytable
1 голос
/ 17 сентября 2009

Если у вас есть список из n значений, элементы которых являются точными только с точностью до целого значения (+ -0,5), то любая сумма этих элементов будет иметь накопительную ошибку или + - (n * 0,5). Если у вас есть 6 элементов в списке, которые должны добавить до некоторого числа, то ваш худший сценарий - если вы просто добавите целочисленные значения, у вас будет 3.

Если вы нашли способ показать 10.2 как 11, чтобы заставить сумму работать, вы изменили точность этого элемента с + -0.5 до + -0.8, что нелогично при просмотре целых чисел?

Одним из возможных решений, о котором следует подумать, является округление вашего числа только во время отображения (с использованием какой-либо строки формата в выходных данных), а не на этапе поиска. Каждое число будет максимально приближено к фактическому значению, но сумма будет также более правильной.

Пример: если у вас есть 3 значения по 1/3 каждое, отображаемые в виде целых процентов, то вы должны показывать 33, 33 и 33. Чтобы сделать что-то еще, создайте предел погрешности, превышающий + -0,5 для любой индивидуальной ценности. Ваш итог все равно должен отображаться как 100%, потому что это наилучшее возможное значение (в отличие от работы с суммами уже округленных значений)

Кроме того, имейте в виду, что, используя float, вы уже ввели ограничение на вашу точность, потому что у вас нет способа точно представить 0.1. Подробнее об этом читайте Что должен знать каждый компьютерщик об арифметике с плавающей точкой

0 голосов
/ 17 сентября 2009

Сначала получите разницу между округленной суммой и фактической суммой и количеством записей:

declare @Sum float, @RoundedSum float, @Cnt int

select @Sum = sum(bar), @RoundedSum = sum(round(bar)), @Cnt = count(*)
from #tbFoo

Затем вы округляете разницу равномерно по всем значениям перед округлением:

declare @Offset float

set @Offset = (@Sum - @RoundedSum) / @Cnt

select bar = round(bar + @Offset)
from #tbFoo
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...