Ответ
Для хранения денежных сумм необходимо использовать тип данных, который гарантирует точность при арифметических операциях и отсутствие ошибок округления, характерных для чисел с плавающей запятой.
Правильный выбор: DECIMAL/NUMERIC (фиксированная точность).
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
-- DECIMAL(total_digits, decimal_places)
-- total_digits - общее количество знаков (до 38 в PostgreSQL)
-- decimal_places - количество знаков после запятой
price DECIMAL(10, 2) NOT NULL, -- например, 9999999.99
currency CHAR(3) NOT NULL DEFAULT 'USD'
);
-- Все операции будут точными
INSERT INTO products (name, price) VALUES ('Laptop', 1299.99);
UPDATE products SET price = price * 0.9; -- 10% скидка
Почему не FLOAT/REAL/DOUBLE?
Эти типы используют двоичное представление и могут давать ошибки округления при десятичных операциях, что критично для финансов.
-- Опасный пример с FLOAT
SELECT 0.1::FLOAT + 0.2::FLOAT; -- Может вернуть 0.30000000000000004
-- Правильный пример с DECIMAL
SELECT 0.1::DECIMAL + 0.2::DECIMAL; -- Всегда вернет 0.3
Дополнительные практики:
- Храните сумму в минимальных единицах (центы, копейки) как целое число (
INTEGERилиBIGINT). Это полностью исключает проблемы с округлением и упрощает арифметику. Отображение с разделителем — задача приложения.price_amount INTEGER NOT NULL, -- 1299.99 USD хранится как 129999 (центов) price_currency CHAR(3) NOT NULL - Для сложных финансовых систем рассмотрите библиотеки с точной десятичной арифметикой на уровне приложения (например,
BigDecimalв Java,decimalв Python). - Всегда указывайте валюту отдельным полем. Агрегация сумм в разных валютах без конвертации — ошибка.