Clickhouse:通过pandas dataframe插入数据且单元格值为null时如何取列的默认值

Zhe*_*ENG 5 database dataframe pandas clickhouse clickhouse-client

我正在尝试将 Pandas 数据框插入 Clickhouse,但遇到了一些问题。\n以下是表架构:

\n
CREATE TABLE IF NOT EXISTS test_table\n(\n    name String,\n    day DateTime64(3) DEFAULT \'2020-07-01 00:00:00\',\n)\nengine = MergeTree\nORDER BY (name, day);\n
Run Code Online (Sandbox Code Playgroud)\n

pandas dataframe 中的数据如下:

\n
   name   day\n0  \'a\'    NaT\n1  \'b\'    NaT\n2  \'c\'   \'2019-08-31 00:00:00\'\n
Run Code Online (Sandbox Code Playgroud)\n

插入的python代码是:

\n
from clickhouse_driver import Client\nwith Client(host="", port="", password="",\n            user="", settings={"use_numpy": True}) as client:\n    client.insert_dataframe(\n        \'INSERT INTO test_table VALUES\',\n        df)\n
Run Code Online (Sandbox Code Playgroud)\n

clickhouse中的结果是

\n
SELECT *\nFROM test_table\n\n\xe2\x94\x8c\xe2\x94\x80name\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80day\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82 a    \xe2\x94\x82 1970-01-01 00:00:00.000\xe2\x94\x82\n\xe2\x94\x82 b    \xe2\x94\x82 1970-01-01 00:00:00.000\xe2\x94\x82\n\xe2\x94\x82 c    \xe2\x94\x82 2019-08-31 00:00:00.000\xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n
Run Code Online (Sandbox Code Playgroud)\n

但我真正想要的是它是默认值,意味着 \'1970-01-01 00:00:00.000\' 将被替换为 \'2020-07-01 00:00:00.000\' 。

\n

我做了一些尝试和调查,以下是我所做的:

\n
    \n
  1. 更改了 NaT 并将其替换为 None 或 Numpy.NaN
  2. \n
\n
df.replace({pd.NaT: None}, inplace=True)\nor\ndf1.replace({pd.NaT: np.NaN}, inplace=True)\n
Run Code Online (Sandbox Code Playgroud)\n

但这些改变的结果仍然是一样的

\n
    \n
  1. clickhouse-client中,当使用insert into时,模式可以工作,结果就是我想要的。像这些:
  2. \n
\n
insert into test_table (name,day) values (\'test-null\',null);\nor\ninsert into test_table (name) values (\'test-sub\');\n\n\n\xe2\x94\x8c\xe2\x94\x80name\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80day\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82 test-null     \xe2\x94\x82 2020-07-01 00:00:00.000\xe2\x94\x82\n\xe2\x94\x82 test-sub      \xe2\x94\x82 2020-07-01 00:00:00.000\xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 在clickhouse-client中,当我使用insert into 和空字符串时,结果将与我使用 pandas dataframe 的结果相同
  2. \n
\n
insert into test_table (name,day) values (\'test-empty\',\'\');\n\nSELECT *\nFROM test_table\n\n\xe2\x94\x8c\xe2\x94\x80name\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80day\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82 test-empty    \xe2\x94\x82 1970-01-01 00:00:00.000\xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n
Run Code Online (Sandbox Code Playgroud)\n

所以,我现在所做的只是将数据帧分成两部分,然后插入两次(我认为这不是Pythonic和高效的),但它确实可以工作

\n
# select not null rows\nmask1 = ~np.isnan(df.day.values)\n\n# select null rows\nmask3 = np.isnan(df.day.values)\n\nwith Client(host="", port="", password=,\n            user="", settings={"use_numpy": True}) as client:\n    # insert entire pandas dataframe\n    client.insert_dataframe(\n        \'INSERT INTO test_table VALUES\',\n       df.loc[mask1])\n    client.insert_dataframe(\n     \'INSERT INTO test_table (* EXCEPT(day)) VALUES\',\n        df.loc[mask3].drop([\'day\'], axis=1))\n
Run Code Online (Sandbox Code Playgroud)\n

总结来说,我想问两件事:

\n
    \n
  1. 有没有更好的方法来实现我的目标:当 pandas dataframe 中的单元格为 NaT/NaN/None 时,插入 clickhouse 后它将成为列的默认值。无需通过 pandas 设置值。
  2. \n
  3. clickhouse 数据类型有错误吗DataTime?当空字符串插入到列中时,它会忽略默认值并使用 clickhouse 自己的默认值。
  4. \n
\n

在我看来,第二个可能是解决这个问题的关键,因为当我使用clickhouse-driver的客户端时,它可能会将NaT/NaN/None转换为空字符串。

\n

编辑:\n对于问题2,我发现在clickhouse中,DateTime列会将空字符串视为 0(零)或 \'0\'(字符串中的零),这可以解释为什么 day 的值为 1970-01- 01 00:00:00.000。

\n

所以,问题是:为什么 DateTime 会这样对待这个值?而且,我猜想 clickhouse-driver 客户端会将None/NaT/NaN 视为空字符串并将空字符串传递给 clickhouse。驱动程序是否可以将 None/NaT/NaN 视为 null(尽管,python 中只有 NoneType)或者只是删除单元格(例如,传递每一行,但我阅读了 clickhouse-driver 的代码发现它传递了每一列以获得完整的值)。

\n