对pyxlsbwriter 写入xlsx的数据查询产生NaN转换失败原因分析

上文最后提到,用pyxlsbwriter 写入比用xlsxwriter写入的xlsx数据,执行查询时多了个第49号查询的错误,现在来分析原因。

load tpcds;
call dsdgen(sf=0.1);

原始生成的catalog_sales表数据

D from catalog_sales limit 20;
┌─────────────────┬─────────────────┬─────────────────┬─────────────────────┬───┬─────────────────────┬──────────────────────┬──────────────────────┬───────────────┐
│ cs_sold_date_sk │ cs_sold_time_sk │ cs_ship_date_sk │ cs_bill_customer_sk │ … │ cs_net_paid_inc_tax │ cs_net_paid_inc_ship │ cs_net_paid_inc_sh…  │ cs_net_profit │
│      int32      │      int32      │      int32      │        int32        │   │    decimal(7,2)decimal(7,2)decimal(7,2)decimal(7,2)  │
├─────────────────┼─────────────────┼─────────────────┼─────────────────────┼───┼─────────────────────┼──────────────────────┼──────────────────────┼───────────────┤
│         24508153821224508862153 │ … │             2121.553062.053163.07718.63 │
│         24508153821224508462153 │ … │             1856.893142.083213.4934.48 │
│         24508153821224508682153 │ … │              589.49724.66758.02-771.21 │
│         24508153821224508512153 │ … │             9811.8813741.5014118.885934.50 │
│         24508152948524508634601 │ … │             1988.874997.825162.03-1580.38 │
│         24508152948524508324601 │ … │              916.201304.991340.22-1729.75 │
│         24508152948524508424601 │ … │              733.091655.931663.18-124.27 │
│         24508152948524508824601 │ … │             1891.242198.112253.19502.93 │
│         24508152948524508704601 │ … │             2133.6710374.7510550.92-6268.35 │
│         24508152948524508794601 │ … │              416.61499.50515.5226.46 │
│         24508152948524508734601 │ … │             2463.583019.103067.40-1244.50 │
│         24508152948524508504601 │ … │              147.84186.33186.3357.24 │
│            NULLNULL24508264601 │ … │                NULL4780.115164.73-3207.45 │
│         24508152948524508544601 │ … │            10077.6011501.4011501.406108.60

从xlsx文件读入数据,第一列第14行引发解析错误,忽略错误后保存为NULL,与原数据一致。

D create table catalog_sales as from read_xlsx('source.xlsx',sheet='catalog_sales');
Invalid Input Error:
read_xlsx: Failed to parse cell 'A14': Could not convert string '<NA>' to DOUBLE
D create table catalog_sales as from read_xlsx('source.xlsx',sheet='catalog_sales',ignore_errors=1);
D from catalog_sales limit 20;
┌─────────────────┬─────────────────┬─────────────────┬─────────────────────┬───┬─────────────────────┬──────────────────────┬──────────────────────┬───────────────┐
│ cs_sold_date_sk │ cs_sold_time_sk │ cs_ship_date_sk │ cs_bill_customer_sk │ … │ cs_net_paid_inc_tax │ cs_net_paid_inc_ship │ cs_net_paid_inc_sh…  │ cs_net_profit │
│     doubledoubledoubledouble        │   │       doubledoubledoubledouble     │
├─────────────────┼─────────────────┼─────────────────┼─────────────────────┼───┼─────────────────────┼──────────────────────┼──────────────────────┼───────────────┤
│       2450815.038212.02450886.02153.0 │ … │             2121.553062.053163.07718.63 │
│       2450815.038212.02450846.02153.0 │ … │             1856.893142.083213.4934.48 │
│       2450815.038212.02450868.02153.0 │ … │              589.49724.66758.02-771.21 │
│       2450815.038212.02450851.02153.0 │ … │             9811.8813741.514118.885934.5 │
│       2450815.029485.02450863.04601.0 │ … │             1988.874997.825162.03-1580.38 │
│       2450815.029485.02450832.04601.0 │ … │               916.21304.991340.22-1729.75 │
│       2450815.029485.02450842.04601.0 │ … │              733.091655.931663.18-124.27 │
│       2450815.029485.02450882.04601.0 │ … │             1891.242198.112253.19502.93 │
│       2450815.029485.02450870.04601.0 │ … │             2133.6710374.7510550.92-6268.35 │
│       2450815.029485.02450879.04601.0 │ … │              416.61499.5515.5226.46 │
│       2450815.029485.02450873.04601.0 │ … │             2463.583019.13067.4-1244.5 │
│       2450815.029485.02450850.04601.0 │ … │              147.84186.33186.3357.24 │
│            NULLNULL2450826.04601.0 │ … │                 nan │              4780.115164.73-3207.45 │
│       2450815.029485.02450854.04601.0 │ … │             10077.611501.411501.46108.6

执行第49号查询报错

Conversion Error: Could not cast value nan to DECIMAL(15,4) when casting from source column sum(COALESCE(cr_return_amount, CAST(0 AS DOUBLE)))

LINE 54:                 (cast(sum(coalesce(cr.cr_return_amount,0)) AS decimal(15...
                          ^

再查看SQL原文,

        (SELECT cs.cs_item_sk AS item,
                (cast(sum(coalesce(cr.cr_return_quantity,0)) AS decimal(15,4))/ cast(sum(coalesce(cs.cs_quantity,0)) AS decimal(15,4))) AS return_ratio,
                (cast(sum(coalesce(cr.cr_return_amount,0)) AS decimal(15,4))/ cast(sum(coalesce(cs.cs_net_paid,0)) AS decimal(15,4))) AS currency_ratio
         FROM catalog_sales cs
         LEFT OUTER JOIN catalog_returns cr ON (cs.cs_order_number = cr.cr_order_number
                                                AND cs.cs_item_sk = cr.cr_item_sk) ,date_dim

cr别名引用的是catalog_returns表,再读取catalog_returns表,
原始数据

D from catalog_returns limit 20;
┌─────────────────────┬─────────────────────┬────────────┬──────────────────────┬───┬──────────────────┬────────────────────┬─────────────────┬──────────────┐
│ cr_returned_date_sk │ cr_returned_time_sk │ cr_item_sk │ cr_refunded_custom…  │ … │ cr_refunded_cash │ cr_reversed_charge │ cr_store_credit │ cr_net_loss  │
│        int32        │        int32        │   int32    │        int32         │   │   decimal(7,2)decimal(7,2)decimal(7,2)decimal(7,2) │
├─────────────────────┼─────────────────────┼────────────┼──────────────────────┼───┼──────────────────┼────────────────────┼─────────────────┼──────────────┤
│             2450861589395264601 │ … │           113.4365.7990.86368.99 │
│             2450985792146084601 │ … │           356.5214.1029.97251.47 │
│             2450901229328267572 │ … │           602.27582.811131.36336.64 │
│             2450939365489647572 │ … │            38.11125.2790.72117.93 │
│             24510436556313151351 │ … │             3.649.078.73153.90 │
│             2450980418182507674 │ … │           209.0066.18595.69119.86 │
│             245099591433619276 │ … │          1005.01241.20231.75535.34 │
│             24510251812113519276 │ … │            56.999.263.26116.82 │
│             24509082699810481731 │ … │            89.1571.77953.57670.60 │
│             2450998500878501731 │ … │            77.6169.07558.92192.06 │
│             2451001402715386748 │ … │           186.1799.12168.7927.56 │
│             2451000348828836950 │ … │           637.46182.807.6296.79 │
│             24510126284410164957 │ … │           379.676925.29288.564009.95 │
│             2450915454031392003 │ … │             NULL88.69NULLNULL │
D create table catalog_returns as from read_xlsx('source.xlsx',sheet='catalog_returns',ignore_errors=1);
D from catalog_returns limit 20;
┌─────────────────────┬─────────────────────┬────────────┬──────────────────────┬───┬──────────────────┬────────────────────┬─────────────────┬─────────────┐
│ cr_returned_date_sk │ cr_returned_time_sk │ cr_item_sk │ cr_refunded_custom…  │ … │ cr_refunded_cash │ cr_reversed_charge │ cr_store_credit │ cr_net_loss │
│       doubledoubledoubledouble        │   │      doubledoubledoubledouble    │
├─────────────────────┼─────────────────────┼────────────┼──────────────────────┼───┼──────────────────┼────────────────────┼─────────────────┼─────────────┤
│           2450861.058939.0526.04601.0 │ … │           113.4365.7990.86368.99 │
│           2450985.079214.0608.04601.0 │ … │           356.5214.129.97251.47 │
│           2450901.022932.0826.07572.0 │ … │           602.27582.811131.36336.64 │
│           2450939.036548.0964.07572.0 │ … │            38.11125.2790.72117.93 │
│           2451043.065563.01315.01351.0 │ … │             3.649.078.73153.9 │
│           2450980.041818.0250.07674.0 │ … │            209.066.18595.69119.86 │
│           2450995.09143.0361.09276.0 │ … │          1005.01241.2231.75535.34 │
│           2451025.018121.01351.09276.0 │ … │            56.999.263.26116.82 │
│           2450908.026998.01048.01731.0 │ … │            89.1571.77953.57670.6 │
│           2450998.050087.0850.01731.0 │ … │            77.6169.07558.92192.06 │
│           2451001.040271.0538.06748.0 │ … │           186.1799.12168.7927.56 │
│           2451000.034882.0883.06950.0 │ … │           637.46182.87.6296.79 │
│           2451012.062844.01016.04957.0 │ … │           379.676925.29288.564009.95 │
│           2450915.045403.0139.02003.0 │ … │              nan │              88.69 │             nan │         nan │

可以观察到,cr_refunded_cash等3列选中的最后1行,有3个nan值,而原始数据是NULL, 不一致,这就是导致错误的原因,实际上,前面catalog_sales表的cs_net_paid_inc_tax也有nan值,也与原始数据不一致,虽然没有引发报错,但也有隐患。
虽然duckdb可以处理nan,比如:

SELECT sqrt(2) > '-inf', 'nan' > sqrt(2);
┌────────────────────┬───────────────────┐
│ (sqrt(2) > '-inf')('nan' > sqrt(2)) │
│      booleanboolean      │
├────────────────────┼───────────────────┤
│ truetrue              │
└────────────────────┴───────────────────┘

但tpcds的SQL并没有针对nan编写,因为原始数据就不存在nan。
再查看pyxlsbwriter的xlsx_writer.py源码, 跟NaN有关的有这几行,其实对平常的SQL计算没有意义,直接都处理成NULL, 就可以了。

                    # Handle special float values
                    if isinstance(cell, float):
                        if cell != cell:  # NaN
                            self._write_string_cell(buffer, "NaN", row_idx == 0)
                            continue
                        elif cell == float('inf'):
                            self._write_string_cell(buffer, "∞", row_idx == 0)
                            continue
                        elif cell == float('-inf'):
                            self._write_string_cell(buffer, "-∞", row_idx == 0)
                            continue  

恰好duckdb的excel插件处理逻辑是:

ignore_errors BOOLEAN false Whether to ignore errors and silently replace cells that cant be cast to the corresponding inferred column type with NULL’s.

它觉得NaN是能转换成对应数据类型的,就直接读入,而不是转换成NULL,反而造成了问题。

pyxlsbwriter还存在把所有整数都识别成浮点数问题,看来它为追求速度,牺牲了一部分准确度,需要改进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值