Bagaimana SQL Server menentukan presisi / skala?

8

Saya menjalankan SQL Server 2012

SELECT 
   0.15 * 30 / 360,
   0.15 / 360 * 30 

Hasil:

 0.012500, 
 0.012480

Yang ini bahkan lebih membingungkan bagi saya:

DECLARE @N INT = 360
DECLARE @I DECIMAL(38,26) = 0.15 * 30 / 360     
DECLARE @C DECIMAL(38,26) = 1000000     

SELECT @C *  @I *  POWER(1 + @I, @N)  / ( POWER(1 + @I, @N) - 1 )
SELECT @C * (@I *  POWER(1 + @I, @N)  / ( POWER(1 + @I, @N) - 1 ) )

Pilihan pertama memberi saya hasil yang benar: 12644.44022 Yang kedua memotong hasilnya: 12644.00000

cpacheco
sumber
1
Lihat jawaban ini: stackoverflow.com/a/51445919/87015 , itu berisi kode sampel yang menentukan tipe data yang diharapkan berdasarkan pada tipe data input dan operasi.
Salman A

Jawaban:

13

Menentukan presisi dan skala yang dihasilkan dari ekspresi adalah sarang tikus dan saya tidak berpikir ada yang mengerti aturan yang tepat dalam setiap skenario, terutama ketika mencampur desimal (atau float!) Dan int. Lihat jawaban ini oleh gbn .

Tentu saja Anda dapat menyesuaikan ekspresi untuk memberikan apa yang Anda inginkan dengan membuat konversi eksplisit yang jauh lebih banyak. Ini mungkin berlebihan tetapi:

SELECT 
   CONVERT(DECIMAL(15,6), CONVERT(DECIMAL(15,6), 0.15) 
   * CONVERT(DECIMAL(15,6), 30) 
   / CONVERT(DECIMAL(15,6), 360)),
   CONVERT(DECIMAL(15,6), CONVERT(DECIMAL(15,6), 0.15) 
   / CONVERT(DECIMAL(15,6), 360) 
   * CONVERT(DECIMAL(15,6), 30));

Tidak ada hasil yang dibulatkan secara salah karena matematika floating point yang rusak atau presisi / skala yang salah.

0.012500    0.012500
Aaron Bertrand
sumber
6

Seperti yang disebutkan Aaron Bertrand, ekspresi sangat sulit diprediksi.

Jika Anda berani pergi ke sana, Anda bisa mencoba mendapatkan wawasan menggunakan cuplikan berikut:

DECLARE @number SQL_VARIANT
SELECT @number = 0.15 / 360
SELECT @number
SELECT  
    SQL_VARIANT_PROPERTY(@number, 'BaseType') BaseType,
    SQL_VARIANT_PROPERTY(@number, 'MaxLength') MaxLength,
    SQL_VARIANT_PROPERTY(@number, 'Precision') Precision

Ini hasilnya:

------------
0.000416

(1 row(s) affected)

BaseType     MaxLength    Precision
------------ ------------ ----------
numeric      5            6

(1 row(s) affected)
Oliver Rahner
sumber
3

Meskipun jawaban yang sangat baik sudah ditambahkan ke pertanyaan ini, ada urutan prioritas yang didefinisikan secara eksplisit untuk konversi tipe data dalam SQL Server.

Ketika seorang operator menggabungkan dua ekspresi dari tipe data yang berbeda, aturan untuk presedensi tipe data menentukan bahwa tipe data dengan presedensi yang lebih rendah dikonversi ke tipe data dengan prioritas yang lebih tinggi. Jika konversi itu bukan konversi tersirat yang didukung, kesalahan dikembalikan. Ketika kedua ekspresi operan memiliki tipe data yang sama, hasil operasi memiliki tipe data tersebut.

SQL Server menggunakan urutan prioritas berikut untuk tipe data:

user-defined data types (highest)
sql_variant
xml
datetimeoffset
datetime2
datetime
smalldatetime
date
time
float
real
decimal
money
smallmoney
bigint
int
smallint
tinyint
bit
ntext
text
image
timestamp
uniqueidentifier
nvarchar (including nvarchar(max) )
nchar
varchar (including varchar(max) )
char
varbinary (including varbinary(max) )
binary (lowest)

Jadi, misalnya, jika Anda SELECT 0.5 * 1(mengalikan desimal oleh int) Anda mendapatkan hasil yang dikonversi ke nilai desimal, karena decimalini lebih tinggi diutamakan daripada inttipe data.

Lihat http://msdn.microsoft.com/en-us/library/ms190309.aspx untuk rincian lebih lanjut.

Setelah mengatakan semua itu, mungkin SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); harus mengembalikan nilai desimal, karena praktis semua input adalah desimal. Menariknya, Anda dapat memaksakan hasil yang benar dengan memodifikasi itu SELECTuntuk:

DECLARE @N INT = 360;
DECLARE @I DECIMAL(38,26) = 0.15 * 30 / 360;
DECLARE @C DECIMAL(38,26) = 1000000;

SELECT @C *  @I *  POWER(1 + @I, @N)  / (POWER(1 + @I, @N) - 1);
SELECT @C * (@I *  POWER(1 + @I, @N)  / (POWER(1E0 + @I, @N) - 1));

Ini mengembalikan:

masukkan deskripsi gambar di sini

Saya bingung menjelaskan bagaimana itu membuat perbedaan, meskipun jelas itu terjadi . Dugaan saya adalah 1E0(float eksplisit) dalam POWER(fungsi memaksa SQL Server untuk membuat pilihan berbeda pada tipe output untuk POWERfungsi. Jika anggapan saya benar, itu akan menunjukkan kemungkinan bug dalam POWERfungsi, karena dokumentasi menyatakan input pertama POWER()adalah float, atau angka yang secara implisit dapat dikonversi menjadi float.

Max Vernon
sumber
2
Tautan ke Presisi, Skala, dan Panjang juga sesuai.
ypercubeᵀᴹ
2

Apakah Anda terbiasa dengan SELECT .. INTO sintaks ? Ini adalah trik yang berguna untuk mendekonstruksi situasi seperti ini karena membuat tabel dengan cepat hanya dengan tipe data yang tepat untuk SELECTdaftar yang diberikan .

Anda dapat memecah perhitungan Anda menjadi langkah-langkah penyusunnya, menerapkan aturan prioritas SQL Server saat Anda mulai, untuk melihat bagaimana definisi berubah. Beginilah contoh pertama Anda akan terlihat:

use tempdb;

SELECT
   0.15             as a,
   0.15 * 30        as b,
   0.15 * 30 / 360  as c
into #Step1;

select * from #Step1;

select
    c.name,
    t.name,
    c.precision,
    c.scale
from sys.columns as c
inner join sys.types as t
    on t.system_type_id = c.system_type_id
where object_id = object_id('#Step1');

drop table #Step1;

Ini hasilnya:

name    name        precision   scale
a       numeric     2           2
b       numeric     5           2
c       numeric     9           6
Michael Green
sumber
1
Ini tidak selalu membantu. Keduanya (1+1)dan 2tipe inttetapi pertanyaan ini memiliki contoh di mana mereka akhirnya menghasilkan hasil yang diketik berbeda.
Martin Smith