深入讲解数据库中Decimal类型的使用以及实现方法

目录
  • 1 背景
  • 2 Decimal类型的使用
    • 2.1 描述Decimal
    • 2.2 建表时定义Decimal
    • 2.3 写入decimal数据
    • 2.4 取出deimcal进行计算
  • 3 Decimal类型的实现
    • 3.1 MySQL
    • 3.2 ClickHouse
    • 3.3 总结
  • 4. MySQL 违反直觉的地方
  • 总结

1 背景

数字运算在数据库中是很常见的需求, 例如计算数量、重量、价格等, 为了满足各种需求, 数据库系统通常支持精准的数字类型和近似的数字类型. 精准的数字类型包含 int, decimal 等, 这些类型在计算过程中小数点位置是固定的, 其结果和行为比较可预测. 当涉及钱时, 这个问题尤其重要, 因此部分数据库实现了专门的 money 类型. 近似的数字类型包含 float, double 等, 这些数字的精度是浮动的.

本文将简要介绍 decimal 类型的数据结构和计算, 对比 decimal 在 MySQL, ClickHouse 两个不同类型系统中的实现差异, 描述实现 decimal 运算的主要思路. MySQL 在结果的长度比较接近上限的情况下, 会有比较违反直觉的地方, 本文会在最后列出这些可能需要注意的问题.

2 Decimal类型的使用

decimal 的使用在多数数据库上都差不多, 下面以 MySQL 的 decimal 为例, 介绍 decimal 的基本使用方法.

2.1 描述Decimal

与 float 和 double 不同, decimal 在创建时需要指定两个描述精度的数字, 分别是 precision 和 scale, precision 指整个 decimal 包括整数和小数部分一共有多少个数字, scale 指 decimal 的小数部分包含多少个数字, 例如:123.45 就是一个 precision=5, scale=2 的 decimal.  我们可以在建表时按照这种方式定义我们想要的 decimal.

2.2 建表时定义Decimal

可以在建表时这样定义一个 decimal:

create table t(d decimal(5, 2));

2.3 写入decimal数据

可以向其中插入合法的数据, 例如

insert into t values(123.45);
insert into t values(123.4);

此时执行 select * from t 会得到

+--------+
| d      |
+--------+
| 123.45 |
| 123.40 |
+--------+

注意到 123.4 变成了 123.40, 这就是精确类型的特点, d 列的每行数据都要求 scale=2, 即小数点后有两位

当插入不满足 precision 和 scale 定义的数据时

insert into t values(1123.45);
ERROR 1264 (22003): Out of range value for column 'd' at row 1
insert into t values(123.456);
Query OK, 1 row affected, 1 warning
show warnings;
+-------+------+----------------------------------------+
| Level | Code | Message                                |
+-------+------+----------------------------------------+
| Note  | 1265 | Data truncated for column 'd' at row 1 |
+-------+------+----------------------------------------+
select * from t;
+--------+
| d      |
+--------+
| 123.46 |
+--------+

类似 1234.5 (precision=5, scale=1)这样的数字看起来满足要求, 但实际上需要满足 scale=2 的要求, 因此会变成 1234.50(precision=6, scale=2) 也不满足要求.

2.4 取出deimcal进行计算

计算的结果不受定义的限制, 而是受到内部实现格式的影响, 对于 MySQL 结果最大可以到 precision=81, scale=30, 但是由于 MySQL decimal 的内存格式和计算函数实现问题, 这个大小不是在所有情况都能达到, 将在后文中详细介绍. 继续上面的例子中:

select d + 9999.999 from t;
+--------------+
| d + 9999.999 |
+--------------+
|    10123.459 |
+--------------+

结果突破了 precision=5, scale=2 的限制, 这里涉及运算时 scale 的变化, 基本规则是:

  1. 加法/减法/sum:取两边最大的 scale
  2. 乘法:两边的 scale 相加
  3. 除法:被除数的 scale + div_precision_increment(取决于数据库实现)

3 Decimal类型的实现

在这一部分中, 我们主要介绍 MySQL 的 decimal 实现, 此外也会对比 ClickHouse, 看看 decimal 在不同系统中的设计与实现差异.

实现 decimal 需要思考以下问题

  1. 支持多大的 precision 和 scale
  2. 在哪里存储 scale
  3. 在连续乘法或除法时, scale 不断增长, 整数部分也不断扩大, 而存储的 buffer 大小总是有上限的, 此时应该如何处理?
  4. 除法可能产生无限小数, 如何决定除法结果的 scale?
  5. decimal 的表示范围和计算性能是否有冲突, 是否可以兼顾

3.1 MySQL

先来看看 MySQL decimal 相关的数据结构

typedef int32 decimal_digit_t;

struct decimal_t {
  int intg, frac, len;
  bool sign;
  decimal_digit_t *buf;
};

MySQL 的 decimal 使用一个长度为 len 的 decimal_digit_t (int32) 的数组 buf 来存储 decimal 的数字, 每个 decimal_digit_t 最多存储 9 个数字, 用 intg 表示整数部分的数字个数, frac 表示小数部分的数字个数, sign 表示符号. 小数部分和整数部分需要分开存储, 不能混合在一个 decimal_digit_t 中, 两部分都向小数点对齐, 这是因为整数和小数通常需要分开计算, 所以这样的格式可以更容易地将不同 decimal_t 小数和整数分别对齐, 便于加减法运算. len 在 MySQL 实现中恒为 9, 它表示存储的上限, 而 buf 实际有效的部分, 则是由 intg 和 frac 共同决定. 例如:

// 123.45 decimal(5, 2) 整数部分为 3,  小数部分为 2
decimal_t dec_123_45 = {
  int intg = 3;
  int frac = 2;
  int len = 9;
  bool sign = false;
  decimal_digit_t *buf = {123, 450000000, ...};
};

MySQL 需要使用两个 decimal_digit_t (int32) 来存储 123.45, 其中第一个为 123, 结合 intg=3, 它就表示整数部分为 123, 第二个数字为 450000000 (共 9 个数字), 由于 frac=2, 它表示小数部分为 .45

再来看一个大一点的例子:

// decimal(81, 18) 63 个整数数字, 18 个小数数字, 用满整个 buffer
// 123456789012345678901234567890123456789012345678901234567890123.012345678901234567
decimal_t dec_81_digit = {
  int intg = 63;
  int frac = 18;
  int len = 9;
  bool sign = false;
  buf = {123456789, 12345678, 901234567, 890123456, 789012345, 678901234, 567890123, 12345678, 901234567}
};

这个例子用满了 81 个数字, 但是也有些场景无法用满 81 个数字, 这是因为整数和小数部分是分开存储的, 所以一个 decimal_digit_t (int32) 可能只存储了一个有效的小数数字, 但是其余的部分没有办法给整数部分使用, 例如一个 decimal 整数部分有 62 个数字, 小数部分有 19 个数字(precision=81, scale=19), 那么小数部分需要使用 3 个 decimal_digit_t (int32), 整数部分还有 54 个数字的余量, 无法存下 62 个数字. 这种情况下, MySQL 会优先满足整数部分的需求, 自动截断小数点后的部分, 将它变成 decimal(80, 18)

接下来看看 MySQL 如何在这个数据结构上进行运算. MySQL 通过一系列 decimal_digit_t(int32) 来表示一个较大的 decimal, 其计算也是对这个数组中的各个 decimal_digit_t 分别进行, 如同我们在小学数学计算时是一个数字一个数字地计算, MySQL 会把每个 decimal_digit_t 当作一个数字来进行计算、进位. 由于代码较长, 这里不再对具体的代码进行完整的分析, 仅对代码中核心部分进行分析, 如果感兴趣, 可以直接参考 MySQL 源码 strings/decimal.h 和 strings/decimal.cc 中的 decimal_add, decimal_mul, decimal_div 等代码.

准备步骤

在真正计算前, 还需要做一些准备工作:

  1. MySQL 会将数字的个数 ROUND_UP 到 9 的整数倍, 这样后面就可以按照 decimal_digit_t 为单位来进行计算
  2. 此外还要针对参与运算的两个 decimal 的具体情况, 计算结果的 precision 和 scale, 如果发现结果的 precision 超过了支持的上限, 那么会按照 decimal_digit_t 为单位减少小数的数字.
  3. 在乘法过程中, 如果发生了 2 中的减少行为, 则需要 TRUNCATE 两个运算数, 避免中间结果超出范围.

加法主要步骤

首先, 因为两个数字的 precision 和 scale 可能不相同, 需要做一些准备工作, 将小数点对齐, 然后开始计算, 从最末尾小数开始向高位加, 分为三个步骤:

  1. 将小数较多的 decimal 多出的小数数字复制到结果中
  2. 将两个 decimal 公共的部分相加
  3. 将整数较多的 decimal 多出的整数数字与进位相加到结果中

代码中使用了 stop, stop2 来标记小数点对齐后, 长度不同的数字出现差异的位置.

/* part 1 - max(frac) ... min (frac) */
while (buf1 > stop) *--buf0 = *--buf1;

/* part 2 - min(frac) ... min(intg) */
carry = 0;
while (buf1 > stop2) {
  ADD(*--buf0, *--buf1, *--buf2, carry);
}

/* part 3 - min(intg) ... max(intg) */
buf1 = intg1 > intg2 ? ((stop3 = from1->buf) + intg1 - intg2)
                     : ((stop3 = from2->buf) + intg2 - intg1);
while (buf1 > stop3) {
  ADD(*--buf0, *--buf1, 0, carry);
}

乘法主要步骤

乘法引入了一个新的 dec2, 表示一个 64 bit 的数字, 这是因为两个 decimal_digit_t(int32) 相乘后得到的可能会是一个 64 bit 的数字. 在计算时一定要先把类型转换到 dec2(int64), 再计算, 否则会得到溢出后的错误结果. 乘法与加法不同, 乘法不需要对齐, 例如计算 11.11 5.0, 那么只要计算 111150=55550, 再移动小数点位置就能得到正确结果 55.550

MySQL 实现了一个双重循环将 decimal1 的 每一个 decimal_digit_t 与 decimal2 的每一个 decimal_digit_t 相乘, 得到一个 64 位的 dec2,  其低 32 位是当前的结果, 其高 32 位是进位.

typedef decimal_digit_t dec1;
typedef longlong dec2;
for (buf1 += frac1 - 1; buf1 >= stop1; buf1--, start0--) {
  carry = 0;
  for (buf0 = start0, buf2 = start2; buf2 >= stop2; buf2--, buf0--) {
    dec1 hi, lo;
    dec2 p = ((dec2)*buf1) * ((dec2)*buf2);
    hi = (dec1)(p / DIG_BASE);
    lo = (dec1)(p - ((dec2)hi) * DIG_BASE);
    ADD2(*buf0, *buf0, lo, carry);
    carry += hi;
  }
  if (carry) {
    if (buf0 < to->buf) return E_DEC_OVERFLOW;
    ADD2(*buf0, *buf0, 0, carry);
  }
  for (buf0--; carry; buf0--) {
    if (buf0 < to->buf) return E_DEC_OVERFLOW;
    ADD(*buf0, *buf0, 0, carry);
  }
}

除法主要步骤

除法使用的是 Knuth's Algorithm D, 其基本思路和手动除法也比较类似.

首先使用除数的前两个 decimal_digit_t 组成一个试商因数, 这里使用了一个 norm_factor 来保证数字在不溢出的情况下尽可能扩大, 这是因为 decimal 为了保证精度必须使用整形来进行计算, 数字越大, 得到的结果就越准确. D3: 猜商, 就是用被除数的前两个 decimal_digit_t 除以试商因数 这里如果不乘 norm_factor, 则 start1[1] 和 start2[1] 都不会体现在结果之中.

D4: 将 guess 与除数相乘, 再从被除数中剪掉结果 然后做一些修正, 移动向下一个 decimal_digit_t, 重复这个过程.

想更详细地了解这个算法可以参考 https://skanthak.homepage.t-online.de/division.html

norm2 = (dec1)(norm_factor * start2[0]);
if (likely(len2 > 0)) norm2 += (dec1)(norm_factor * start2[1] / DIG_BASE);
x = start1[0] + ((dec2)dcarry) * DIG_BASE;
y = start1[1];
guess = (norm_factor * x + norm_factor * y / DIG_BASE) / norm2;
for (carry = 0; buf2 > start2; buf1--) {
  dec1 hi, lo;
  x = guess * (*--buf2);
  hi = (dec1)(x / DIG_BASE);
  lo = (dec1)(x - ((dec2)hi) * DIG_BASE);
  SUB2(*buf1, *buf1, lo, carry);
  carry += hi;
}
carry = dcarry < carry;

3.2 ClickHouse

ClickHouse 是列存, 相同列的数据会放在一起, 因此计算时通常也将一列的数据合成 batch 一起计算.

一列的 batch 在 ClickHouse 中使用 PODArray, 例如上图中的 c1 在计算时就会有一个 PODArray, 进行简化后大致可以表示如下:

class PODArray {
    char * c_start          = null;
    char * c_end            = null;
    char * c_end_of_storage = null;
}

在计算时会讲 c_start 指向的数组转换成实际的类型, 对于 decimal, ClickHouse 使用足够大的 int 来表示, 根据 decimal 的 precision 选择 int32, int64 或者 int128. 例如一个 decimal(10, 2), 123.45, 使用这样方式可以表示为一个 int32_t, 其内容为 12345, decimal(10, 3) 的 123.450 表示为 123450. ClickHouse 用来表示每个 decimal 的结构如下, 实际上就是足够大的 int:

template <typename T>
struct Decimal
{
    using NativeType = T;
    // ...
    T value;
};
using Int32 = int32_t;
using Int64 = int64_t;
using Int128 = __int128;
using Decimal32 = Decimal<Int32>;
using Decimal64 = Decimal<Int64>;
using Decimal128 = Decimal<Int128>;

显而易见, 这样的表示方法相较于 MySQL 的方法更轻量, 但是范围更小, 同时也带来了一个问题是没有小数点的位置, 在进行加减法、大小比较等需要小数点对齐的场景下, ClickHouse 会在运算实际发生的时候将 scale 以参数的形式传入, 此时配合上面的数字就可以正确地还原出真实的 decimal 值了.

ResultDataType type = decimalResultType(left, right, is_multiply, is_division);

int scale_a = type.scaleFactorFor(left, is_multiply);
int scale_b = type.scaleFactorFor(right, is_multiply || is_division);
OpImpl::vector_vector(col_left->getData(), col_right->getData(), vec_res,
                      scale_a, scale_b, check_decimal_overflow);

例如两个 decimal: a = 123.45000(p=8, s=5), b = 123.4(p=4, s=1), 那么计算时传入的参数就是  col_left->getData() = 123.45000 10 ^ 5 = 12345000, scale_a = 1, col_right->getData() = 123.4 10 ^ 1 = 1234, scale_b = 10000, 12345000 1 和 1234 10000 的小数点位置是对齐的, 可以直接计算.

加法主要步骤

ClickHouse 实现加法同样要先对齐, 对齐的方法是将 scale 较小的数字乘上一个系数, 使两边的 scale 相等. 然后直接做加法即可. ClickHouse 在计算中也根据 decimal 的 precision 进行了细分, 对于长度没那么长的 decimal, 直接用 int32, int64 等原生类型计算就可以了, 这样大大提升了速度.

bool overflow = false;
if constexpr (scale_left)
    overflow |= common::mulOverflow(a, scale, a);
else
    overflow |= common::mulOverflow(b, scale, b);

overflow |= Op::template apply<NativeResultType>(a, b, res);
template <typename T>
inline bool addOverflow(T x, T y, T & res)
{
    return __builtin_add_overflow(x, y, &res);
}

template <>
inline bool addOverflow(__int128 x, __int128 y, __int128 & res)
{
    static constexpr __int128 min_int128 = __int128(0x8000000000000000ll) << 64;
    static constexpr __int128 max_int128 = (__int128(0x7fffffffffffffffll) << 64) + 0xffffffffffffffffll;
    res = x + y;
    return (y > 0 && x > max_int128 - y) || (y < 0 && x < min_int128 - y);
}

乘法主要步骤

同 MySQL, 乘法不需要对齐, 直接按整数相乘就可以了, 比较短的 decimal 同样可以使用 int32, int64 原生类型. int128 在溢出检测时被转换成 unsigned int128 避免溢出时的未定义行为.

template <typename T>
inline bool mulOverflow(T x, T y, T & res)
{
    return __builtin_mul_overflow(x, y, &res);
}

template <>
inline bool mulOverflow(__int128 x, __int128 y, __int128 & res)
{
    res = static_cast<unsigned __int128>(x) * static_cast<unsigned __int128>(y);    /// Avoid signed integer overflow.
    if (!x || !y)
        return false;

    unsigned __int128 a = (x > 0) ? x : -x;
    unsigned __int128 b = (y > 0) ? y : -y;
    return (a * b) / b != a;
}

除法主要步骤

先转换 scale 再直接做整数除法. 本身来讲除法和乘法一样是不需要对齐小数点的, 但是除法不一样的地方在于可能会产生无限小数, 所以一般数据库都会给结果一个固定的小数位数, ClickHouse 选择的小数位数是和被除数一样, 因此需要将 a 乘上 scale, 然后在除法运算的过程中, 这个 scale 被自然减去, 得到结果的小数位数就可以保持和被除数一样.

bool overflow = false;
if constexpr (!IsDecimalNumber<A>)
    overflow |= common::mulOverflow(scale, scale, scale);
overflow |= common::mulOverflow(a, scale, a);
if (overflow)
    throw Exception("Decimal math overflow", ErrorCodes::DECIMAL_OVERFLOW);

return Op::template apply<NativeResultType>(a, b);

3.3 总结

MySQL 通过一个 int32 的数组来表示一个大数, ClickHouse 则是尽可能使用原生类型, GCC 和 Clang 都支持 int128 扩展, 这使得 ClickHouse 的这种做法可以比较方便地实现.

MySQL 与 ClickHouse 的实现差别还是比较大的, 针对我们开始提到的问题, 分别来看看他们的解答.

  1. precision 和 scale 范围, MySQL 最高可定义 precision=65, scale=30, 中间结果最多包含 81 个数字, ClickHouse 最高可定义 precision=38, scale=37, 中间结果最大为 int128 的最大值 -2^127 ~ 2^127-1.
  2. 在哪里存储 scale, MySQL 是行式存储, 使用火山模型逐行迭代, 计算也是按行进行, 每个 decimal 都有自己的 scale;ClickHouse 是列式存储, 计算按列批量进行, 每行按照相同的 scale 处理能提升性能, 因此 scale 来自表达式解析过程中推导出来的类型.
  3. scale 增长, scale 增长超过极限时, MySQL 会通过动态挤占小数空间, truncate 运算数, 尽可能保证计算完成, ClickHouse 会直接报溢出错.
  4. 除法 scale, MySQL 通过 div_prec_increment 来控制除法结果的 scale, ClickHouse 固定使用被除数的 scale.
  5. 性能, MySQL 使用了更宽的 decimal 表示, 同时要进行 ROUND_UP, 小数挤占, TRUNCATE 等动作, 性能较差, ClickHouse 使用原生的数据类型和计算最大限度地提升了性能.

4. MySQL 违反直觉的地方

在这一部分中, 我们将讲述一些 MySQL 实现造成的违反直觉的地方. 这些行为通常发生在运算结果接近 81 digit 时, 因此如果可以保证运算结果的范围较小也可以忽略这些问题.

  1. 乘法的 scale 会截断到 31, 且该截断是通过截断运算数字的方式来实现的, 例如: select 10000000000000000000000000000000.100000000 10000000000000000000000000000000 = 10000000000000000000000000000000.100000000000000000000000000000 10000000000000000000000000000000.555555555555555555555555555555 返回 1, 第二个运算数中的 .555555555555555555555555555555 全部被截断
  2. MySQL 使用的 buffer 包含了 81 个 digit 的容量, 但是由于小数部分必须和整数部分分开, 因此很多时候无法用满 81 个 digit, 例如: select 99999999999999999999999999999999999999999999999999999999999999999999999999.999999 = 99999999999999999999999999999999999999999999999999999999999999999999999999.9 返回 1
  3. 计算过程中如果发现整数部分太大会动态地挤占小数部分, 例如: select 999999999999999999999999999999999999999999999999999999999999999999999999.999999999 + 999999999999999999999999999999999999999999999999999999999999999999999999.999999999 = 999999999999999999999999999999999999999999999999999999999999999999999999 + 999999999999999999999999999999999999999999999999999999999999999999999999 返回 1
  4. 除法计算中间结果不受 scale = 31 的限制, 除法中间结果的 scale 一定是 9 的整数倍, 不能按照最终结果来推测除法作为中间结果的精度, 例如 select 2.0000 / 3 3 返回 2.00000000, 而 select 2.00000 / 3 3 返回 1.999999998, 可见前者除法的中间结果其实保留了更多的精度.
  5. 除法, avg 计算最终结果的小数部分如果正好是 9 的倍数, 则不会四舍五入, 例如: select 2.00000 / 3 返回 0.666666666, select 2.0000 / 3 返回 0.66666667
  6. 除法, avg 计算时, 运算数字的小数部分如果不是 9 的倍数, 那么会实际上存储 9  的倍数个小数数字, 因此会出现以下差异:
create table t1 (a decimal(20, 2), b decimal(20, 2), c integer);

insert into t1 values (100000.20, 1000000.10,   5);
insert into t1 values (200000.20, 2000000.10,   2);
insert into t1 values (300000.20, 3000000.10,   4);
insert into t1 values (400000.20, 4000000.10,   6);
insert into t1 values (500000.20, 5000000.10,   8);
insert into t1 values (600000.20, 6000000.10,   9);
insert into t1 values (700000.20, 7000000.10,   8);
insert into t1 values (800000.20, 8000000.10,   7);
insert into t1 values (900000.20, 9000000.10,   7);
insert into t1 values (1000000.20, 10000000.10, 2);
insert into t1 values (2000000.20, 20000000.10, 5);
insert into t1 values (3000000.20, 30000000.10, 2);

select sum(a+b), avg(c), sum(a+b) / avg(c) from t1;
+--------------+--------+-------------------+
| sum(a+b)     | avg(c) | sum(a+b) / avg(c) |
+--------------+--------+-------------------+
| 115500003.60 | 5.4167 |   21323077.590317 |
+--------------+--------+-------------------+
1 row in set (0.01 sec)

select 115500003.60 / 5.4167;
+-----------------------+
| 115500003.60 / 5.4167 |
+-----------------------+
|       21322946.369561 |
+-----------------------+
1 row in set (0.00 sec)

总结

到此这篇关于数据库中Decimal类型使用以及实现方法的文章就介绍到这了,更多相关Decimal类型使用及实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mysql中decimal数据类型小数位填充问题详解

    前言 在开发过程中,我们往往会用到decimal数据类型.因为decimal是MySQL中存在的精准数据类型. MySQL中的数据类型有:float,double等非精准数据类型和decimal这种精准. 区别:float,double等非精准类型,在DB中保存的是近似值. Decimal则以字符串的形式保存精确的原始数值. decimal介绍: decimal(a,b) 其中:a指定指定小数点左边和右边可以存储的十进制数字的最大个数,最大精度38.b指定小数点右边可以存储的十进制数字的最大个数

  • MySQL数据类型中DECIMAL的用法实例详解

    MySQL数据类型中DECIMAL的用法实例详解 在MySQL数据类型中,例如INT,FLOAT,DOUBLE,CHAR,DECIMAL等,它们都有各自的作用,下面我们就主要来介绍一下MySQL数据类型中的DECIMAL类型的作用和用法. 一般赋予浮点列的值被四舍五入到这个列所指定的十进制数.如果在一个FLOAT(8, 1)的列中存储1. 2 3 4 5 6,则结果为1. 2.如果将相同的值存入FLOAT(8, 4) 的列中,则结果为1. 2 3 4 6. 这表示应该定义具有足够位数的浮点列以便

  • MySQL中decimal类型用法的简单介绍

    MySQL中支持浮点数的类型有FLOAT.DOUBLE和DECIMAL类型,DECIMAL 类型不同于FLOAT和DOUBLE,DECIMAL 实际是以串存放的.DECIMAL 可能的最大取值范围与DOUBLE 一样,但是其有效的取值范围由M 和D 的值决定.如果改变M 而固定D,则其取值范围将随M 的变大而变大. 对于精度比较高的东西,比如money,建议使用decimal类型,不要考虑float,double, 因为他们容易产生误差,numeric和decimal同义,numeric将自动转

  • 转换科学计数法的数值字符串为decimal类型的方法

    在操作数据库时,需要将字符串转换成decimal类型. 代码如下: select cast('0.12' as decimal(18,2)); select convert(decimal(18,2), '0.12'); 当需要将科学计数法的数字字符串转换成decimal时,这2种写法都报错: msg 8114, level 16, state 5, line 1 error converting data type varchar to numeric. select cast('0.12e+

  • 数据库 数据类型float到C#类型decimal, float数据类型转化无效

    今天运行系统突然出错,数据类型转化无效,错误在system.data.sqlclient.get_decimal()方法,初步估计是数据库的float类型转换为c#的decimal类型时出错了,实体类使用的是decimal?类型,心想就算数据库里的数值是空,也不能出现转换错误啊. 网上百般搜索,很多人遇到类似情况,多数是直接去float类型时候出错,如(float)dr[0],后来看到有说应该是先转化成double,然后在转化成float就可以了 尝试了一下把实体类的decimal?类型的字段改

  • MySQL中Decimal类型和Float Double的区别(详解)

    MySQL中存在float,double等非标准数据类型,也有decimal这种标准数据类型. 其区别在于,float,double等非标准类型,在DB中保存的是近似值,而Decimal则以字符串的形式保存数值. float,double类型是可以存浮点数(即小数类型),但是float有个坏处,当你给定的数据是整数的时候,那么它就以整数给你处理.这样我们在存取货币值的时候自然遇到问题,我的default值为:0.00而实际存储是0,同样我存取货币为12.00,实际存储是12. 幸好mysql提供

  • 详解MySQL数据类型DECIMAL(N,M)中N和M分别表示的含义

    同事问MySQL数据类型DECIMAL(N,M)中N和M分别表示什么含义,M不用说,显然是小数点后的小数位数,但这个N究竟是小数点之前的最大位数,还是加上小数部分后的最大位数?这个还真记不清了.于是乎,创建测试表验证了一番,结果如下: 测试表,seller_cost字段定义为decimal(14,2) CREATE TABLE `test_decimal` ( `id` int(11) NOT NULL, `seller_cost` decimal(14,2) DEFAULT NULL ) EN

  • mysql decimal数据类型转换的实现

    最近在工作遇到数据库中存的数据类型是: decimal(14,4) 遇到的问题是: 当我使用python 读取到内存中时,总是带着 decimal字符, 再写入其它mysql表中时,数据类型为int型,导致数据入库不成功. import pymysql # 创建数据库连接 con = pymysql.connect() sql = '''select created_time from schma.table LIMIT 10''' try: cur = con.cursor(cursor=py

  • MySQL数据类型DECIMAL用法详解

    MySQLDECIMAL数据类型用于在数据库中存储精确的数值.我们经常将DECIMAL数据类型用于保留准确精确度的列,例如会计系统中的货币数据. 要定义数据类型为DECIMAL的列,请使用以下语法: column_name DECIMAL(P,D); 在上面的语法中: P是表示有效数字数的精度.P范围为1〜65. D是表示小数点后的位数.D的范围是0~30.MySQL要求D小于或等于(<=)P. DECIMAL(P,D)表示列可以存储D位小数的P位数.十进制列的实际范围取决于精度和刻度. 与IN

  • 深入讲解数据库中Decimal类型的使用以及实现方法

    目录 1 背景 2 Decimal类型的使用 2.1 描述Decimal 2.2 建表时定义Decimal 2.3 写入decimal数据 2.4 取出deimcal进行计算 3 Decimal类型的实现 3.1 MySQL 3.2 ClickHouse 3.3 总结 4. MySQL 违反直觉的地方 总结 1 背景 数字运算在数据库中是很常见的需求, 例如计算数量.重量.价格等, 为了满足各种需求, 数据库系统通常支持精准的数字类型和近似的数字类型. 精准的数字类型包含 int, decima

  • MySQL数据库中varchar类型的数字比较大小的方法

    创建测试表 -- ---------------------------- -- Table structure for check_test -- ---------------------------- DROP TABLE IF EXISTS `check_test`; CREATE TABLE `check_test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `current_price` varchar(10) NOT NULL, `price`

  • python pandas中DataFrame类型数据操作函数的方法

    python数据分析工具pandas中DataFrame和Series作为主要的数据结构. 本文主要是介绍如何对DataFrame数据进行操作并结合一个实例测试操作函数. 1)查看DataFrame数据及属性 df_obj = DataFrame() #创建DataFrame对象 df_obj.dtypes #查看各行的数据格式 df_obj['列名'].astype(int)#转换某列的数据类型 df_obj.head() #查看前几行的数据,默认前5行 df_obj.tail() #查看后几

  • C#中Decimal类型截取保留N位小数并且不进行四舍五入操作

    一.问题描述 开发中,需要使Decimal类型数据保留小数点后的两位小数且不需要进行四舍五入操作,即直接截取小数点后面的两位小数即可.例如:1.245M --> 1.24,而不是1.25 使用Decimal.Round()方法可以实现保留Decimal类型数据小数点后的若干位小数,但是该方法会进行四舍五入操作,而不是直接截取.所以,该方法不可取,在网上搜寻一番也没有找到合适的方法,便自己实现了一个截取Decimal类型数据小数点后若干位的方法. 二.详细的实现代码 using System; n

  • SQL Server数据库中批量导入数据的四种方法总结

    在软件项目实施的时候,数据导入一直是项目人员比较头疼的问题.其实,在SQL Server中集成了很多成批导入数据的方法.有些项目实施顾问头疼的问题,在我们数据库管理员眼中,是小菜一碟.现在的重点就是,如何让用户了解这些方法,让数据导入变得轻松一些. 第一:使用Select Into语句 若企业数据库都采用的是SQL Server数据库的话,则可以利用Select Into语句来实现数据的导入.Select Into语句,他的作用就是把数据从另外一个数据库中查询出来,然后加入到某个用户指定的表中.

  • 在MySQL数据库中使用C执行SQL语句的方法

    他们将讨论返回数据的语句,例如INSERT以及不返回数据的语句,例如UPDATE和DELETE.然后,他们将编写从数据库检索数据的简单程序 执行SQL语句 现在,我们已经有了一个连接,并且知道如何处理错误,是时候讨论使用我们的数据库来作一些实际工作了.执行所有类型的SQL的主关键字是mysql_query: int mysql_query(MYSQL *connection, const char *query) 正如您所见,它非常简单.它取一个指向连接结构的指针和包含要执行的SQL的文本字符串

  • SQL Server数据库中批量导入数据的2种方法

    在软件项目实施的时候,数据导入一直是项目人员比较头疼的问题.其实,在SQL Server中集成了很多成批导入数据的方法.有些项目实施顾问头疼的问题,在我们数据库管理员眼中,是小菜一碟.现在的重点就是,如何让用户了解这些方法,让数据导入变得轻松一些.相信以下方法大家都用过了,温故而知新哈,如果有更好的方法希望大家都提出来~ 一.使用Select Into语句 若企业数据库都采用的是SQL Server数据库的话,则可以利用Select Into语句来实现数据的导入.Select Into语句,他的

  • Java实现获得MySQL数据库中所有表的记录总数可行方法

    在MySQL中,可以通过SELECT COUNT(*) FROM table_name查询某个表中有多少条记录.如果想知道某个数据库中所有别的记录总数应该怎么做呢?本文给出两种可行的Java程序,解决该问题. 1. 首先确定数据库中有多少个表,然后对每个表执行SELECT COUNT(*) FROM table_name 复制代码 代码如下: import java.sql.Connection; import java.sql.DriverManager; import java.sql.Pr

  • 详细讲解Swift中的类型占位符

    Swift 的类型推断能力从一开始就是语言的核心部分,它极大地减少了我们在声明有默认值的变量和属性时手动指定类型的工作.例如,表达式var number = 7不需要包含任何类型注释,因为编译器能够推断出值7是一个Int,我们的number变量应该被相应的类型化. 作为 Xcode 13.3 的一部分而一起发布的 Swift 5.6,通过引入 "类型占位符(type placeholders) "的概念,继续扩展这些类型推理能力,这在处理集合和其他通用类型时非常有用. 例如,假设我们想

随机推荐