PostgreSQL: Który typ danych powinien być używany dla waluty?

128

Wygląda na to, że Moneytyp jest odradzany, jak opisano tutaj

Moja aplikacja musi przechowywać walutę, jakiego typu danych mam używać? Numeryczne, Money czy FLOAT?

marzyciel
źródło
7
Jeśli przeczytałeś cały wątek, najlepszym rozwiązaniem jest Numeric.
razpeitia
Każdy, kto pracuje z wieloma walutami i troszczy się o przechowywanie kodów walut oprócz kwot, może chcieć zobaczyć Modelowanie walut w bazie danych (SO) i ISO 4217 (Wikipedia). Krótka odpowiedź jest taka, że ​​będziesz potrzebować dwóch kolumn.
Fabien Snauwaert
Nie używaj pieniędzy
a_horse_with_no_name

Odpowiedzi:

90

Numeryczne z wymuszoną dokładnością do 2 jednostek. Nigdy nie używaj typu danych typu float lub float do reprezentowania waluty, ponieważ jeśli to zrobisz, ludzie będą niezadowoleni, gdy wynik finansowy raportu finansowego będzie nieprawidłowy o + lub - kilka dolarów.

O ile wiem, typ pieniędzy został po prostu pozostawiony z powodów historycznych.

Chris Farmiloe
źródło
9
Nie dlatego unikasz zmiennoprzecinkowych. Nawet numeric będzie miał błędy zaokrąglania, jeśli podzielisz przez cokolwiek, co nie jest podzielone na potęgę dziesięciu, bez względu na zastosowaną dokładność. (Precyzja 2 to i tak zły pomysł ... sprawdź dokumentację)
Doradus
6
Jeśli chcesz obsługiwać dowolne waluty, nigdy nie rób skali równej 2 (w kategoriach postgresql dokładność to liczba wszystkich cyfr, np. W 121.121 jest równa 6). Istnieją waluty, takie jak dinar bahrański, w których 1000 podjednostek równa się jednej jednostce i są waluty, które w ogóle nie mają podjednostek.
Nikolay Arhipov
@NikolayArhipov dobra uwaga, więc tak naprawdę maksimum jestscale - precision
Konrad
1
numeric(3,2)będzie w stanie przechowywać max9.99 3-2 = 1
Konrad
5
Nie rób tego! O ile nie planujesz pomnożyć przez 100 przed załadowaniem jakichkolwiek wartości do innych języków, a następnie obliczeniem liczb całkowitych - skończysz z błędnymi wynikami. Przechowuj rzeczy w centach (najmniejsza jednostka walutowa, z którą masz do czynienia) i oszczędzaj sobie kłopotów. W wielu przypadkach bardzo zła odpowiedź.
Avamander
111

Twoje źródło nie jest w żaden sposób oficjalne. Pochodzi z 2011 roku i nawet nie rozpoznaję autorów. Jeśli typ pieniędzy był oficjalnie „odradzany”, PostgreSQL powiedziałby to w instrukcji - a tak nie jest .

Aby uzyskać bardziej oficjalne źródło , przeczytaj ten wątek w pgsql-general (tylko z tego tygodnia!) , Z oświadczeniami głównych programistów, w tym D'Arcy JM Cain (pierwotny autor typu pieniężnego) i Toma Lane:

Powiązana odpowiedź (i komentarze!) Na temat ulepszeń w ostatnich wersjach:

Zasadniczo moneyma swoje (bardzo ograniczone) zastosowania. PlikPostgres Wiki sugeruje, aby w znacznym stopniu uniknąć, z wyjątkiem ściśle określonych przypadków. Przewaga numericjest wydajność .

decimal jest tylko aliasem dla numeric w Postgres i jest szeroko stosowany w danych monetarnych, będąc typem „arbitralnej precyzji”. Instrukcja :

Typ numeric może przechowywać liczby z bardzo dużą liczbą cyfr. Jest szczególnie polecany do przechowywania kwot pieniężnych i innych ilości, gdzie wymagana jest dokładność.

Osobiście lubię przechowywać walutę jako integerreprezentującą centy, jeśli ułamkowe centy nigdy nie występują (w zasadzie tam, gdzie pieniądze mają sens). To jest bardziej wydajne niż jakakolwiek inna z wymienionych opcji.

Erwin Brandstetter
źródło
4
Na listach dyskusyjnych toczy się kilka dyskusji, które sprawiają wrażenie, że ten rodzaj pieniędzy nie jest przynajmniej zalecany, np .: tutaj: postgresql.nabble.com/Money-type-todos-td1964190.html#a1964192 plus, żeby być uczciwym: instrukcja dla wersji 8.2 nie nazywają to przestarzałe: postgresql.org/docs/8.2/static/datatype-money.html
a_horse_with_no_name
11
@a_horse_with_no_name: Twój link prowadzi do wątku z 2007 r., czyli również wtedy, gdy wersja 8.2 była aktualną wersją, a moneytyp był w rzeczywistości przestarzały. Problemy zostały naprawione, a typ został dodany z powrotem w późniejszych wersjach. Osobiście lubię przechowywać walutę jako integercenty.
Erwin Brandstetter
1
Erwin, możesz mieć rację, myśląc tylko z perspektywy bazy danych. Jeśli jednak połączysz Postgresql + Java, to wcale NIE jest dobrze (z mojego doświadczenia). Czytając Twój komentarz, użyłem MONEY dla większości moich pól walutowych i teraz otrzymuję ten wyjątek Java: „ Wystąpił wyjątek SQLException: org.postgresql.util.PSQLException: Zła wartość dla typu double: 2,500,00 ”. Wyszukałem w Google i nie znalazłem dobrego rozwiązania, więc jestem teraz nudnym zadaniem zmiany ich wszystkich na NUMERYCZNE lub DZIESIĘTNE !!!
MD
1
@MD: Przykro mi to słyszeć, ale oczywiście nie mówiłem w języku Java (czego nie mogę). Komunikat o błędzie jest dziwny. "podwójnie"? Problem może też stanowić separator tysięcy. Możesz zacząć nowe pytanie na ten temat.
Erwin Brandstetter
2
@PirateApp: Tak, mój osobisty faworyt. Mogłeś przegapić ostatnie zdanie mojej odpowiedzi, mówiąc tylko to.
Erwin Brandstetter
68

Masz do wyboru:

  1. bigint: zapisz kwotę w centach. Tego właśnie używają transakcje EFTPOS.
  2. decimal(12,2): zapisz kwotę z dokładnie dwoma miejscami po przecinku. Tego właśnie używa większość programów do obsługi księgi głównej.
  3. float: okropny pomysł - niewystarczająca dokładność. Właśnie tego używają naiwni programiści.

Opcja 2 jest najpopularniejsza i najłatwiejsza w użyciu. Ustaw dokładność (w moim przykładzie 12, co oznacza w sumie 12 cyfr) tak dużą lub małą, jak najbardziej Ci odpowiada.

Zwróć uwagę, że jeśli agregujesz wiele transakcji, które były wynikiem obliczeń (np. Dotyczących kursu wymiany), w jedną wartość, która ma znaczenie biznesowe, precyzja powinna być wyższa, aby zapewnić dokładną wartość makro; rozważ użycie czegoś podobnego, decimal(18, 8)aby suma była dokładna, a poszczególne wartości można było zaokrąglić do centów dokładności w celu wyświetlenia.

Czeski
źródło
10
Jeśli pracujesz z jakimkolwiek rodzajem obliczania podatku zwrotnego lub walutą obcą, potrzebujesz co najmniej 4 miejsc po przecinku, w przeciwnym razie utracisz dane. Więc numeric(15,4)lub numeric(15,6)to dobry pomysł.
Petrus Theron
3
Istnieje czwarta opcja - to jest użycie String i użycie równoważnego bezstratnego typu dziesiętnego w języku hosta.
ioquatix
2
A co ze skalowaną liczbą całkowitą, na pewno przechowywanie 10000,045 nie zaszkodzi, jeśli zostanie zapisane jako 10000045 ze współczynnikiem skalowania 1000x?
PirateApp,
26

Wszystkie pola pieniężne zachowuję jako:

numeric(15,6)

Wydaje się przesadą, aby mieć tyle miejsc po przecinku, ale jeśli istnieje choćby najmniejsza szansa, że ​​będziesz musiał radzić sobie z wieloma walutami, potrzebujesz tak dużej precyzji do przeliczania. Bez względu na to, co przedstawiam użytkownikowi, zawsze przechowuję dane w dolarach amerykańskich. W ten sposób mogę łatwo przeliczyć na dowolną inną walutę, biorąc pod uwagę kurs wymiany na dany dzień.

Jeśli nigdy nie robisz nic poza jedną walutą, najgorsze jest to, że zmarnowałeś trochę miejsca na przechowywanie zer.

Michael Collette
źródło
6
Stwarza to ryzyko błędnych wyników z powodu braku obcięcia. Jeśli niezerowe wartości przypadkowo przeciekają do pozostałych miejsc po przecinku, na przykład do pola ceny zawierającego 0,333333 dolara, to może dojść do sytuacji, w której system pokaże wynik kupna 3 pozycji po 0,33 dolara za sztukę, w sumie do 1,00 dolara zamiast 0,99 dolara.
Peteris,
1
Perteris, więc co proponujesz zamiast tego? Bez względu na to, jaką precyzję zastosujesz w tym zaokrągleniu, może to stanowić problem. Po prostu nie znalazłem lepszego sposobu, nawet jeśli ten nie jest idealny.
Michael Collette,
3
Punkt stały i obcięty w razie potrzeby. Gdy tylko osiągniesz „dającą się przechować” wartość pieniężną, np. Cenę oferowaną klientowi, powinna ona mieć odpowiednie wskaźniki, które w większości przypadków byłyby wyrażone w całych centach w standardowym środowisku detalicznym. Jeśli masz inne potrzeby biznesowe (np. Cena towarów o dużej objętości na jednostkę), może być inne ustawienie dokładności, ale musisz traktować prezentację razem z przechowywaniem - jeśli wyświetlasz liczbę pieniędzy z x miejscami po przecinku (lub odwrotnie, np. w całych tysiącach), to musisz również przechowywać go z taką dokładnością, nie mniej, ale też nie więcej.
Peteris,
W przypadku wielu witryn związanych z handlem detalicznym, które mogą działać. Główny projekt, z którym pracuję, może mieć jedną stronę, która musi zobaczyć ten sam koszt w jednej walucie, z klientem w innej walucie, dla dostawcy w jeszcze trzeciej.
Michael Collette
19

Użyj 64-bitowej liczby całkowitej przechowywanej jako bigint

Zalecam używanie mikro dolarów (lub podobnej głównej waluty). Mikro oznacza 1 milionową, więc 1 mikrodolar = 0,000001 $.

  • Prosty w użyciu i kompatybilny z każdym językiem.
  • Wystarczająca precyzja do obsługi ułamków centa.
  • Działa w przypadku bardzo niskich cen jednostkowych (takich jak wyświetlenia reklam lub opłaty za interfejs API).
  • Mniejszy rozmiar danych do przechowywania niż ciągi lub liczby.
  • Łatwe do utrzymania dokładności poprzez obliczenia i zaokrąglanie na końcowym wyjściu.
Mani Gandham
źródło
1
To jest najbardziej poprawna odpowiedź, a każde rozsądne oprogramowanie radzi sobie z najmniejszą dostępną jednostką walutową. Wykonywanie wszystkich obliczeń matematycznych na liczbach całkowitych oznacza, że ​​nie musisz zajmować się żadną zmienną zmiennoprzecinkową specyficzną dla języka.
Avamander
1
Będzie tego też używać. Dzięki
Dlaczego to numeric(15,6)zasugerowano w innej odpowiedzi?
Juliusz Gonera
@JuliuszGonera Z powodów wymienionych w odpowiedzi. Liczby całkowite są mniejsze i obsługiwane wszędzie i pozwalają uniknąć wszystkich problemów związanych z obcinaniem matematyki. Zasadniczo używa liczb, ale przesuwa ułamki dziesiętne, dzięki czemu masz liczbę całkowitą, która jest znacznie bardziej zgodna.
Mani Gandham
1
Ach, racja, przegapiłem część dotyczącą przechowywania. Dzięki! Jeśli chodzi o „Prosty w użyciu i kompatybilny z każdym językiem”, niestety JavaScript obsługuje liczby całkowite do 9007199254740991, które są ponad 1000 razy mniejsze niż maksymalna wartość bigint. Istnieje developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... ale ma ograniczone wsparcie (na razie) i zastrzeżenia (np. Nie można go łatwo pomnożyć przez liczbę zmiennoprzecinkową podczas przeliczania walut) . Biorąc pod uwagę, że maksymalna wartość, jaką można zapisać w postaci liczby całkowitej JS za pomocą mikro-dolarów, wynosi 9 miliardów dolarów, co prawdopodobnie nadal jest dobre w większości przypadków.
Juliusz Gonera
3

Służy BigIntdo przechowywania waluty jako dodatniej liczby całkowitej reprezentującej wartość pieniężną w najmniejszej jednostce walutowej (np. 100 centów do przechowywania 1,00 USD lub 100 do przechowywania jenów 100 (jen japoński, waluta o zerowej wartości dziesiętnej). To właśnie robi Stripe - jeden najważniejszych firm świadczących usługi finansowe dla globalnego handlu elektronicznego.

Max Hodges
źródło