PostgreSQL: ประเภทข้อมูลใดที่ควรใช้สำหรับสกุลเงิน?


130

ดูเหมือนว่าMoneyประเภทจะหมดกำลังใจตามที่อธิบายไว้ที่นี่

แอปพลิเคชันของฉันต้องการจัดเก็บสกุลเงินฉันจะใช้ประเภทข้อมูลใด ตัวเลขเงินหรือลอย?


7
หากคุณได้อ่านกระทู้ทั้งหมดแล้วตัวเลขคือหนทางที่จะไป
razpeitia

สำหรับใครก็ตามที่ทำงานกับหลายสกุลเงินและดูแลเกี่ยวกับการจัดเก็บรหัสสกุลเงินนอกเหนือจากจำนวนเงินคุณอาจต้องการดูการสร้างแบบจำลองสกุลเงินในฐานข้อมูล (SO) และISO 4217 (Wikipedia) คำตอบสั้น ๆ คือคุณต้องมีสองคอลัมน์
Fabien Snauwaert

คำตอบ:


91

ตัวเลขที่มีความแม่นยำ 2 หน่วยบังคับ อย่าใช้ float หรือ float เหมือนประเภทข้อมูลเพื่อแสดงสกุลเงินเพราะถ้าคุณทำเช่นนั้นผู้คนจะไม่พอใจเมื่อตัวเลขด้านล่างของรายงานทางการเงินไม่ถูกต้องโดย + หรือ - ไม่กี่ดอลลาร์

ประเภทเงินเหลือเพียงเหตุผลทางประวัติศาสตร์เท่าที่ฉันสามารถบอกได้


9
นั่นไม่ใช่เหตุผลที่คุณหลีกเลี่ยงจุดลอยตัว แม้แต่ตัวเลขก็จะมีข้อผิดพลาดในการปัดเศษหากคุณหารด้วยสิ่งใดก็ตามที่ไม่ได้หารด้วยเลขกำลังสิบไม่ว่าคุณจะใช้ความแม่นยำเท่าใดก็ตาม (ความแม่นยำของ 2 เป็นความคิดที่ไม่ดีอยู่ดี ... ตรวจสอบเอกสาร)
Doradus

6
หากคุณต้องการรองรับสกุลเงินตามอำเภอใจอย่าสร้างมาตราส่วนให้เท่ากับ 2 (ในแง่ของ postgresql ความแม่นยำคือตัวเลขทั้งหมดเช่นใน 121.121 จะเท่ากับ 6) มีสกุลเงินเช่น Bahrain Dinar ซึ่ง 1,000 หน่วยย่อยเท่ากับหนึ่งหน่วยและมีสกุลเงินที่ไม่มีหน่วยย่อยเลย
Nikolay Arhipov

@NikolayArhipov จุดดีดังนั้นสูงสุดคือจริงscale - precision
Konrad

1
numeric(3,2)จะสามารถจัดเก็บได้สูงสุด9.99 3-2 = 1
Konrad

5
อย่าทำอย่างนี้! ถ้าคุณไม่ได้วางแผนที่จะคูณด้วย 100 ก่อนที่จะโหลดค่าใด ๆ เป็นภาษาอื่นแล้วทำการคำนวณด้วยจำนวนเต็มคุณจะได้ผลลัพธ์ที่ไม่ถูกต้อง จัดเก็บสิ่งของเป็นเซ็นต์ (หน่วยสกุลเงินที่เล็กที่สุดที่คุณกำลังติดต่อด้วย) และช่วยตัวเองไม่ให้ยุ่งยาก คำตอบที่แย่มากในหลาย ๆ กรณี
Avamander

112

แหล่งที่มาของคุณไม่เป็นทางการ มันเกิดขึ้นในปี 2011 และฉันจำผู้แต่งไม่ได้ด้วยซ้ำ หากประเภทเงินถูก "ท้อ" อย่างเป็นทางการ PostgreSQL จะบอกเช่นนั้นในคู่มือ - ซึ่งไม่ได้เป็นเช่นนั้น

สำหรับแหล่งข้อมูลที่เป็นทางการมากขึ้นโปรดอ่านหัวข้อนี้ใน pgsql-general (ตั้งแต่สัปดาห์นี้เท่านั้น!)พร้อมคำแถลงจากนักพัฒนาหลัก ได้แก่ D'Arcy JM Cain (ผู้เขียนต้นฉบับประเภทเงิน) และ Tom Lane:

คำตอบที่เกี่ยวข้อง (และความคิดเห็น!) เกี่ยวกับการปรับปรุงในรุ่นล่าสุด:

โดยทั่วไปmoneyมีการใช้งาน (จำกัด มาก) Postgres วิกิพีเดียให้เห็นส่วนใหญ่จะหลีกเลี่ยงได้ยกเว้นกรณีที่กำหนดไว้อย่างหวุดหวิดเหล่านั้น เปรียบที่เหนือกว่าnumericคือประสิทธิภาพการทำงาน

decimalเป็นเพียงนามแฝงnumericใน Postgres และใช้กันอย่างแพร่หลายสำหรับข้อมูลทางการเงินโดยเป็นประเภท "ความแม่นยำโดยพลการ" คู่มือ :

ประเภทnumericสามารถจัดเก็บตัวเลขที่มีจำนวนหลักได้มาก ขอแนะนำโดยเฉพาะอย่างยิ่งสำหรับการจัดเก็บจำนวนเงินและปริมาณอื่น ๆ ที่จำเป็นต้องมีความแน่นอน

โดยส่วนตัวแล้วฉันชอบเก็บสกุลเงินเป็นintegerตัวแทนของ Cents ถ้า Cents เศษส่วนไม่เคยเกิดขึ้น มีประสิทธิภาพมากกว่าตัวเลือกอื่น ๆ ที่กล่าวถึง


4
มีการพูดคุยกันหลายครั้งเกี่ยวกับรายชื่อส่งไปรษณีย์ซึ่งให้ความรู้สึกว่าอย่างน้อยไม่แนะนำให้ใช้ประเภทเงินเช่นที่นี่: postgresql.nabble.com/Money-type-todos-td1964190.html#a1964192บวกเพื่อความเป็นธรรม: คู่มือสำหรับรุ่น 8.2 ไม่เรียกว่าเลิก: postgresql.org/docs/8.2/static/datatype-money.html
a_horse_with_no_name

11
@a_horse_with_no_name: ลิงก์ของคุณไปยังเธรดจากปี 2007 ซึ่งเมื่อ 8.2 เป็นเวอร์ชันปัจจุบันและmoneyประเภทนี้เลิกใช้งานแล้ว ปัญหาได้รับการแก้ไขแล้วและมีการเพิ่มประเภทกลับในเวอร์ชันที่ใหม่กว่า โดยส่วนตัวแล้วฉันชอบเก็บสกุลเงินเป็นintegerตัวแทนเซ็นต์
Erwin Brandstetter

1
เออร์วินคุณอาจคิดถูกต้องจากมุมมองฐานข้อมูลเพียงอย่างเดียว อย่างไรก็ตามหากคุณรวม Postgresql + Java มันก็ไม่ดีเลย (จากประสบการณ์ของฉัน) เมื่ออ่านความคิดเห็นของคุณฉันใช้ MONEY สำหรับช่องสกุลเงินส่วนใหญ่ของฉันและตอนนี้ฉันได้รับข้อยกเว้น Java นี้: " SQLException เกิดขึ้น: org.postgresql.util.PSQLException: ค่าไม่ถูกต้องสำหรับประเภท double: 2,500.00 " ฉัน googled แล้วและไม่พบวิธีแก้ปัญหาที่ดีดังนั้นฉันจึงมีงานที่น่าเบื่อในการเปลี่ยนทั้งหมดเป็น NUMERIC หรือ DECIMAL ในตอนนี้ !!!
MD

1
@MD: ขออภัยที่ได้ยินเช่นนั้น แต่เห็นได้ชัดว่าฉันไม่ได้พูดกับ Java (ซึ่งฉันทำไม่ได้) ข้อความแสดงข้อผิดพลาดเป็นเลขคี่ "สอง"? และตัวคั่นหลักพันก็อาจเป็นปัญหาได้เช่นกัน คุณอาจต้องการเริ่มคำถามใหม่เกี่ยวกับเรื่องนี้
Erwin Brandstetter

2
@PirateApp: ใช่ฉันชอบส่วนตัว คุณอาจพลาดประโยคสุดท้ายของคำตอบของฉันที่พูดแค่นั้น
Erwin Brandstetter

70

ทางเลือกของคุณคือ:

  1. bigint: จัดเก็บจำนวนเงินเป็นเซ็นต์ นี่คือสิ่งที่ธุรกรรม EFTPOS ใช้
  2. decimal(12,2): จัดเก็บจำนวนเงินด้วยทศนิยมสองตำแหน่ง นี่คือสิ่งที่ซอฟต์แวร์บัญชีแยกประเภททั่วไปส่วนใหญ่ใช้
  3. float: ความคิดที่แย่มาก - ความแม่นยำไม่เพียงพอ นี่คือสิ่งที่นักพัฒนาไร้เดียงสาใช้

ตัวเลือกที่ 2 เป็นวิธีที่ใช้กันทั่วไปและง่ายที่สุด ทำให้ความแม่นยำ (12 ในตัวอย่างของฉันหมายถึงตัวเลข 12 หลัก) ให้ใหญ่หรือเล็กที่สุดเท่าที่จะทำได้ดีที่สุดสำหรับคุณ

โปรดทราบว่าหากคุณรวมธุรกรรมหลายรายการที่เป็นผลมาจากการคำนวณ (เช่นเกี่ยวข้องกับอัตราแลกเปลี่ยน) ให้เป็นมูลค่าเดียวที่มีความหมายทางธุรกิจความแม่นยำควรสูงกว่าเพื่อให้ได้ค่ามหภาคที่ถูกต้อง พิจารณาใช้สิ่งที่ต้องการdecimal(18, 8)เพื่อให้ผลรวมมีความถูกต้องและสามารถปัดเศษค่าแต่ละค่าให้เป็นค่าความแม่นยำกลางสำหรับการแสดงผลได้


10
หากคุณกำลังทำงานกับการคำนวณภาษีย้อนกลับหรือการแลกเปลี่ยนเงินตราต่างประเทศคุณต้องมีทศนิยมอย่างน้อย 4 ตำแหน่งมิฉะนั้นข้อมูลจะสูญหาย ดังนั้นnumeric(15,4)หรือnumeric(15,6)เป็นความคิดที่ดี
Petrus Theron

3
มีตัวเลือกที่ 4 นั่นคือการใช้ String และใช้ประเภททศนิยมที่ไม่สูญเสียเทียบเท่าในภาษาโฮสต์
ioquatix

2
สิ่งที่เกี่ยวกับจำนวนเต็มที่ปรับขนาดการจัดเก็บ 10,000.045 จะไม่เจ็บหากเก็บเป็น 10000045 ด้วยตัวคูณมาตราส่วน 1000x
PirateApp

26

ฉันเก็บช่องทางการเงินทั้งหมดไว้เป็น:

numeric(15,6)

ดูเหมือนว่าจะมีตำแหน่งทศนิยมมากเกินไป แต่หากมีโอกาสน้อยที่สุดที่คุณจะต้องจัดการกับหลายสกุลเงินคุณจะต้องมีความแม่นยำมากในการแปลง ไม่ว่าฉันจะนำเสนอผู้ใช้อะไรฉันก็เก็บเป็นดอลลาร์สหรัฐเสมอ ด้วยวิธีนี้ฉันสามารถแปลงเป็นสกุลเงินอื่นได้อย่างง่ายดายโดยพิจารณาจากอัตราการแปลงสำหรับวันที่เกี่ยวข้อง

หากคุณไม่เคยทำอะไรเลยนอกจากสกุลเงินเดียวสิ่งที่แย่ที่สุดคือคุณเสียพื้นที่เล็กน้อยเพื่อเก็บศูนย์


6
ซึ่งมีความเสี่ยงที่จะเกิดผลลัพธ์ที่ไม่ถูกต้องเนื่องจากไม่มีการตัดทอน หากค่าที่ไม่ใช่ศูนย์รั่วไหลไปยังตำแหน่งทศนิยมที่เหลือโดยไม่ได้ตั้งใจตัวอย่างเช่นช่องราคาที่มี 0.333333 ดอลลาร์คุณอาจมีสถานการณ์ที่ระบบแสดงผลลัพธ์ของการที่มีคนซื้อสินค้า 3 รายการในราคา 0.33 ดอลลาร์ต่อชิ้นรวมเป็นเงินสูงสุด 1.00 ดอลลาร์แทนที่จะเป็น 0.99 ดอลลาร์
Peteris

1
Perteris คุณจะแนะนำอะไรแทน ไม่ว่าคุณจะโยนความแม่นยำมากแค่ไหนในการปัดเศษนี้อาจเป็นปัญหาได้ ฉันยังไม่พบวิธีที่ดีกว่าแม้ว่าวิธีนี้จะไม่เหมาะก็ตาม
Michael Collette

3
จุดคงที่และตัดทอนตามความเหมาะสม ทันทีที่คุณไปถึงมูลค่าเงินที่ "เก็บได้" เช่นราคาที่เสนอให้กับลูกค้าควรอยู่ในเมตริกที่เหมาะสมซึ่งในกรณีส่วนใหญ่จะเป็นเซนต์ทั้งหมดในสภาพแวดล้อมการค้าปลีกมาตรฐาน หากคุณมีความต้องการทางธุรกิจที่แตกต่างกัน (เช่นราคาสินค้าจำนวนมากต่อหน่วย) อาจมีการตั้งค่าความแม่นยำที่แตกต่างกัน แต่คุณต้องปฏิบัติต่อการนำเสนอร่วมกับการจัดเก็บ - หากคุณแสดงหมายเลขเงินด้วยทศนิยม x (หรือในทางกลับกัน เช่นในพันทั้งหมด) แล้วคุณยังต้องเก็บไว้กับที่ถูกต้องไม่น้อย แต่ยังไม่มาก
Peteris

สำหรับไซต์ที่เกี่ยวข้องกับการค้าปลีกจำนวนมากที่อาจใช้งานได้ โครงการหลักที่ฉันทำงานด้วยอาจมีฝ่ายหนึ่งที่ต้องการเห็นต้นทุนเดียวกันในสกุลเงินเดียวกับลูกค้าในสกุลเงินอื่นสำหรับซัพพลายเออร์ในอันดับที่ 3
Michael Collette

20

ใช้จำนวนเต็ม 64 บิตที่จัดเก็บเป็น bigint

ฉันแนะนำให้ใช้ไมโครดอลลาร์ (หรือสกุลเงินหลักที่คล้ายกัน) ไมโครหมายถึง 1 ในล้านดังนั้น 1 ไมโครดอลล่าร์ = 0.000001 ดอลลาร์

  • ใช้งานง่ายและเข้ากันได้กับทุกภาษา
  • มีความแม่นยำเพียงพอที่จะจัดการเศษส่วนร้อยละ
  • ใช้ได้กับราคาต่อหน่วยที่น้อยมาก (เช่นการแสดงโฆษณาหรือค่าบริการ API)
  • ขนาดข้อมูลสำหรับจัดเก็บน้อยกว่าสตริงหรือตัวเลข
  • ง่ายต่อการรักษาความแม่นยำผ่านการคำนวณและใช้การปัดเศษที่ผลลัพธ์สุดท้าย

1
นี่คือคำตอบที่ถูกต้องที่สุดและซอฟต์แวร์ที่สมเหตุสมผลเกี่ยวข้องกับหน่วยสกุลเงินที่เล็กที่สุดในมือ การคำนวณเลขจำนวนเต็มทั้งหมดหมายความว่าคุณไม่จำเป็นต้องจัดการกับการลอยตัวเฉพาะภาษาใด ๆ
Avamander

1
จะใช้ที่เกินไป ขอบคุณ

ทำไมจึงnumeric(15,6)แนะนำมากกว่านี้ในคำตอบอื่น
Juliusz Gonera

@JuliuszGonera ด้วยเหตุผลที่ระบุไว้ในคำตอบ จำนวนเต็มมีขนาดเล็กกว่าและรองรับได้ทุกที่และหลีกเลี่ยงปัญหาการตัดทอนทางคณิตศาสตร์ทั้งหมด โดยพื้นฐานแล้วจะใช้ตัวเลข แต่เปลี่ยนทศนิยมเพื่อให้คุณมีจำนวนเต็มที่เข้ากันได้มากขึ้น
Mani Gandham

2
ใช่ฉันพลาดส่วนที่เกี่ยวกับการจัดเก็บข้อมูล ขอบคุณ! เกี่ยวกับการ "ใช้ง่ายและเข้ากันได้กับทุกภาษา" โชคร้าย JavaScript สนับสนุนจำนวนเต็มถึง 9007199254740991 ซึ่งเป็นมากกว่า 1000x bigintน้อยกว่าค่าสูงสุด มีdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/…แต่มาพร้อมกับการสนับสนุนที่ จำกัด (สำหรับตอนนี้) และข้อควรระวัง (เช่นคุณไม่สามารถคูณด้วยการลอยตัวได้อย่างง่ายดายเมื่อทำการแปลงสกุลเงิน) . เนื่องจากจำนวนสูงสุดที่คุณสามารถจัดเก็บเป็นจำนวนเต็ม JS โดยใช้ไมโครดอลล่าร์คือ 9 พันล้านดอลลาร์ซึ่งอาจจะดีสำหรับกรณีส่วนใหญ่
Juliusz Gonera

5

ใช้BigIntเพื่อเก็บสกุลเงินเป็นจำนวนเต็มบวกที่แสดงมูลค่าเงินในหน่วยสกุลเงินที่เล็กที่สุด (เช่น 100 เซ็นต์เพื่อเก็บ 1.00 ดอลลาร์หรือ 100 เพื่อเก็บ 100 เยน (เยนญี่ปุ่นเป็นสกุลเงินที่เป็นศูนย์ทศนิยม) นี่คือสิ่งที่ Stripe ทำ - หนึ่ง บริษัท ผู้ให้บริการทางการเงินที่สำคัญที่สุดสำหรับอีคอมเมิร์ซระดับโลก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.