PostgreSQL: ¿Qué tipo de datos debe usarse para la moneda?

128

Parece que Moneyse desaconseja el tipo como se describe aquí

Mi aplicación necesita almacenar moneda, ¿qué tipo de datos debo usar? Numérico, dinero o flotante?

soñador
fuente
77
Si ha leído todo el hilo, Numeric es el camino a seguir.
razpeitia
Para cualquier persona que trabaje con varias monedas y se preocupe por almacenar códigos de moneda además de los montos, es posible que desee ver el modelo de moneda en la base de datos (SO) e ISO 4217 (Wikipedia). La respuesta corta es que necesitarás dos columnas.
Fabien Snauwaert
No use dinero
a_horse_with_no_name

Respuestas:

90

Numérico con precisión forzada de 2 unidades. Nunca use flotante o flotante como tipo de datos para representar la moneda porque si lo hace, la gente no estará contenta cuando la cifra del resultado financiero del informe financiero sea incorrecta en + o - unos pocos dólares.

El tipo de dinero solo se deja por razones históricas por lo que puedo decir.

Chris Farmiloe
fuente
9
No es por eso que evitas el punto flotante. Incluso Numeric tendrá errores de redondeo si divide por algo que no se divide en una potencia de diez, sin importar la precisión que use. (La precisión de 2 es una mala idea de todos modos ... revise los documentos.)
Doradus
66
Si desea admitir monedas arbitrarias, nunca haga una escala igual a 2 (en términos postgresql, la precisión es un número de todos los dígitos, por ejemplo, en 121.121 es igual a 6). Hay monedas, como el Dinar de Bahrein, donde 1000 subunidades equivalen a una unidad, y hay monedas que no tienen subunidades en absoluto.
Nikolay Arhipov
@NikolayArhipov buen punto, por lo que el máximo es en realidadscale - precision
Konrad
1
numeric(3,2)podrá almacenar max9.99 3-2 = 1
Konrad
55
¡No hagas esto! A menos que planee multiplicar por 100 antes de cargar cualquier valor en otros idiomas y luego hacer cálculos matemáticos con enteros, obtendrá resultados incorrectos. Almacene las cosas en centavos (la unidad monetaria más pequeña con la que está tratando) y ahórrese algunas molestias. Muy mala respuesta en muchos casos.
Avamander
111

Su fuente no es de ninguna manera oficial. Data de 2011 y ni siquiera reconozco a los autores. Si el tipo de dinero fuera oficialmente "desalentado", PostgreSQL lo diría en el manual, lo cual no es así .

Para obtener una fuente más oficial , lea este hilo en pgsql-general (¡solo de esta semana!) , Con declaraciones de desarrolladores principales que incluyen a D'Arcy JM Cain (autor original del tipo de dinero) y Tom Lane:

Respuesta relacionada (¡y comentarios!) Sobre mejoras en lanzamientos recientes:

Básicamente, moneytiene sus usos (muy limitados). El Wiki de Postgres sugiere evitarlo en gran medida, a excepción de aquellos casos estrechamente definidos. La ventaja numerices el rendimiento .

decimales solo un alias numericen Postgres, y se usa ampliamente para datos monetarios, siendo un tipo de "precisión arbitraria". El manual :

El tipo numericpuede almacenar números con una gran cantidad de dígitos. Se recomienda especialmente para almacenar cantidades monetarias y otras cantidades donde se requiere exactitud.

Personalmente, me gusta almacenar moneda como integerrepresentación de centavos si los centavos fraccionales nunca ocurren (básicamente donde el dinero tiene sentido). Eso es más eficiente que cualquier otra de las opciones mencionadas.

Erwin Brandstetter
fuente
44
Hay varias discusiones en las listas de correo que dan la impresión de que al menos no se recomienda el tipo de dinero, por ejemplo: aquí: postgresql.nabble.com/Money-type-todos-td1964190.html#a1964192 plus para ser justos: el el manual para la versión 8.2 lo llamó en desuso: postgresql.org/docs/8.2/static/datatype-money.html
a_horse_with_no_name
11
@a_horse_with_no_name: su enlace es a un hilo de 2007, que también es cuando 8.2 era la versión actual y el moneytipo era, de hecho, obsoleto. Se han solucionado los problemas y el tipo se ha vuelto a agregar en versiones posteriores. Personalmente, me gusta almacenar moneda como integerrepresentación de centavos.
Erwin Brandstetter
1
Erwin, puedes estar en lo correcto pensando solo desde la perspectiva de una base de datos. Sin embargo, si combina Postgresql + Java, NO es nada bueno (según mi experiencia). Leyendo su comentario, usé DINERO para la mayoría de mis campos de moneda y ahora recibo esta excepción de Java: "Se produjo una excepción SQLE: org.postgresql.util.PSQLException: valor incorrecto para el tipo doble: 2,500.00 ". ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡He buscado en Google y no he encontrado una buena solución, así que estoy en la aburrida tarea de cambiarlos a NUMÉRICOS o DECIMALES ahora !!!
MD
1
@MD: Lamento escuchar eso, pero obviamente no hablé por Java (que no puedo). El mensaje de error es extraño. "doble"? Y el separador de miles también podría ser un problema. Es posible que desee comenzar una nueva pregunta sobre eso.
Erwin Brandstetter
2
@PirateApp: Sí, mi favorito personal. Es posible que haya perdido la última oración de mi respuesta, diciendo eso.
Erwin Brandstetter
68

Sus elecciones son:

  1. bigint: almacena la cantidad en centavos. Esto es lo que usan las transacciones EFTPOS.
  2. decimal(12,2): almacena la cantidad con exactamente dos decimales. Esto es lo que usa el software de contabilidad más general.
  3. float: idea terrible - precisión inadecuada. Esto es lo que usan los desarrolladores ingenuos.

La opción 2 es la más común y más fácil de trabajar. Haga que la precisión (12 en mi ejemplo, que significa 12 dígitos en total) sea tan grande o pequeña como le resulte mejor.

Tenga en cuenta que si está agregando varias transacciones que fueron el resultado de un cálculo (por ejemplo, que involucra un tipo de cambio) en un solo valor que tiene un significado comercial, la precisión debería ser mayor para proporcionar un valor macro exacto; considere usar algo así decimal(18, 8)para que la suma sea precisa y los valores individuales se puedan redondear a una precisión de un centavo para su visualización.

Bohemio
fuente
10
Si está trabajando con algún tipo de cálculo de impuestos inversos o divisas, necesita al menos 4 decimales o perderá datos. Entonces numeric(15,4)o numeric(15,6)es una buena idea.
Petrus Theron el
3
Hay una cuarta opción: usar una cadena y usar un tipo decimal equivalente sin pérdida en el idioma del host.
ioquatix
2
¿Qué pasa con un número entero escalado, seguramente almacenar 10000.045 no estaría de más si se almacenara como 10000045 con un factor de escala 1000x?
PirateApp
26

Mantengo todos mis campos monetarios como:

numeric(15,6)

Parece excesivo tener tantos decimales, pero si existe la más mínima posibilidad de tener que lidiar con múltiples monedas, necesitará tanta precisión para la conversión. No importa lo que le presente a un usuario, siempre almaceno en dólares estadounidenses. De esa manera, puedo convertir fácilmente a cualquier otra moneda, dada la tasa de conversión para el día involucrado.

Si nunca haces nada más que una moneda, lo peor aquí es que desperdiciaste un poco de espacio para almacenar algunos ceros.

Michael Collette
fuente
66
Esto tiene el riesgo de resultados incorrectos debido a la falta de truncamiento. Si los valores distintos de cero se filtran involuntariamente en los decimales restantes, por ejemplo, un campo de precio que contiene 0.333333 dólares, entonces puede tener una situación en la que el sistema muestra el resultado de que alguien compra 3 artículos a $ 0.33 cada uno, totalizando hasta $ 1.00 en lugar de $ 0.99.
Peteris
1
Perteris, entonces, ¿qué sugieres en su lugar? No importa cuánta precisión arrojes en este redondeo puede ser un problema. Simplemente no he encontrado una mejor manera, incluso si esta no es la ideal.
Michael Collette
3
Punto fijo y truncar donde sea apropiado. Tan pronto como alcance un valor monetario "almacenable", por ejemplo, un precio ofrecido a un cliente, debe estar en métricas apropiadas, que en la mayoría de los casos estarían en centavos enteros en el entorno minorista estándar. Si tiene diferentes necesidades comerciales (por ejemplo, el precio de los productos de gran volumen por unidad), puede haber una configuración diferente de precisión, pero debe tratar la presentación junto con el almacenamiento , si muestra el número de dinero con x decimales (o viceversa, por ejemplo, en miles enteros), entonces también debe almacenarlo con esa precisión, no menos, pero tampoco más.
Peteris
Para muchos sitios minoristas relacionados que pueden funcionar. El proyecto principal con el que trabajo puede tener una parte que necesita ver el mismo costo en una moneda, con un cliente en otra moneda, para un proveedor en una tercera.
Michael Collette
19

Use un entero de 64 bits almacenado como bigint

Recomiendo usar micro dólares (o una moneda principal similar). Micro significa 1 millonésimo, entonces 1 micro dólar = $ 0.000001.

  • Simple de usar y compatible con todos los idiomas.
  • Suficiente precisión para manejar fracciones de un centavo.
  • Funciona para precios por unidad muy pequeños (como impresiones de anuncios o cargos de API).
  • Tamaño de datos más pequeño para el almacenamiento que las cadenas o los números.
  • Precisión fácil de mantener mediante cálculos y aplicación de redondeo en la salida final.
Mani Gandham
fuente
1
Esta es la respuesta más correcta y cualquier software razonable trata con la unidad monetaria más pequeña disponible. Hacer todas las matemáticas en números enteros significa que no tiene que lidiar con ningún tipo de cambio flotante específico del idioma.
Avamander
1
Lo usaré también. Gracias
¿Por qué esto se numeric(15,6)sugiere en otra respuesta?
Juliusz Gonera
@JuliuszGonera Por los motivos enumerados en la respuesta. Los enteros son más pequeños y se admiten en todas partes, y evitan todos los problemas de truncamiento matemático. Básicamente usa números, pero cambia los decimales para que tenga un número entero que sea mucho más compatible.
Mani Gandham
1
Ah, claro, me perdí la parte del almacenamiento. ¡Gracias! Con respecto a "Simple de usar y compatible con todos los idiomas", desafortunadamente JavaScript admite enteros de hasta 9007199254740991, que es más de 1000 veces menor que el valor máximo de bigint. Hay developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… pero viene con soporte limitado (por ahora) y advertencias (por ejemplo, no puede multiplicarlo fácilmente por un flotante al hacer la conversión de moneda) . Dado que el máximo que puede almacenar en un entero JS usando micro dólares es de $ 9 mil millones, lo que probablemente todavía sea bueno para la mayoría de los casos.
Juliusz Gonera
3

Se usa BigIntpara almacenar la moneda como un entero positivo que representa el valor monetario en la unidad monetaria más pequeña (por ejemplo, 100 centavos para almacenar $ 1.00 o 100 para almacenar ¥ 100 (yen japonés, una moneda decimal cero). Esto es lo que hace Stripe - uno Las empresas de servicios financieros más importantes para el comercio electrónico global.

Max Hodges
fuente