【问题标题】:Pandas: How to insert dataframe into Clickhouse熊猫:如何将数据框插入 Clickhouse
【发布时间】:2020-02-13 18:06:09
【问题描述】:

我正在尝试将 Pandas 数据框插入 Clickhouse。

这是我的代码

import pandas
import sqlalchemy as sa

uri = 'clickhouse://default:@localhost/default'
ch_db = sa.create_engine(uri)

pdf = pandas.DataFrame.from_records([
    {'year': 1994, 'first_name': 'Vova'},
    {'year': 1995, 'first_name': 'Anja'},
    {'year': 1996, 'first_name': 'Vasja'},
    {'year': 1997, 'first_name': 'Petja'},
])

pdf.to_sql('test_humans', ch_db, if_exists='append', index=False)

这是我收到的错误。这是否与缺少有关 ENGINE 的一些额外参数有关?我该如何解决这个问题?

异常:代码:62,e.displayText() = DB::Exception:语法错误: 在第 65 位失败(第 7 行,第 2 列):FORMAT TabSeparatedWithNamesAndTypes。预期之一:ENGINE,存储 定义(版本 19.15.2.2(正式版))

记录

INFO:sqlalchemy.engine.base.Engine:EXISTS TABLE test_humans 信息:sqlalchemy.engine.base.Engine:{} 信息:sqlalchemy.engine.base.Engine:创建表 test_humans( 名字文本, 年 BIGINT )

信息:sqlalchemy.engine.base.Engine:{} INFO:sqlalchemy.engine.base.Engine:ROLLBACK

【问题讨论】:

  • 您是否使用clickhouse-dialect 进行sqlalchemy?您能否提供 test_humans-table 的架构描述?
  • 是的,我是。当使用 to_sql() 时,pandas 通常会自动创建一个模式。我想这与此有关。方言创建的创建模式 sql 语句缺少一些关于“ENGINE”的专有命令。
  • 查看我的更新..有关已发出查询的一些日志信息。

标签: python pandas clickhouse


【解决方案1】:

您也可以使用https://github.com/kszucs/pandahouse 插入您的数据框,而无需额外的转换。

pip 安装熊猫屋

import pandahouse as ph

pdf = pandas.DataFrame.from_records([
    {'year': 1994, 'first_name': 'Vova'},
    {'year': 1995, 'first_name': 'Anja'},
    {'year': 1996, 'first_name': 'Vasja'},
    {'year': 1997, 'first_name': 'Petja'},
])

connection = dict(database='default',
                  host='localhost',
                  user='default',
                  password='')

ph.to_clickhouse(pdf, 'test_humans', index=False, chunksize=100000, connection=connection)

【讨论】:

    【解决方案2】:

    从 0.2.0 版本开始 clickhouse_driver 实现方法insert_dataframe。见:https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.insert_dataframe

    【讨论】:

    • 这很好用。它非常简单,并且比使用client.execute("INSERT INTO your_table VALUES", df.to_dict('records')) 更有效,因为它会转置DataFrame 并以列格式发送数据。这不会自动生成表,但无论如何我都不相信。 Clickhouse 有太多关于架构设计的重要决策 - 尤其是分区 - 将其留给自动生成。
    【解决方案3】:

    不用sqlalchemy也可以。

    pip install clickhouse-driver

    from clickhouse_driver import Client
    
    client = Client('localhost')
    df = pandas.DataFrame.from_records([
        {'year': 1994, 'first_name': 'Vova'},
        {'year': 1995, 'first_name': 'Anja'},
        {'year': 1996, 'first_name': 'Vasja'},
        {'year': 1997, 'first_name': 'Petja'},
    ])
    # df processing blablabla...
    client.execute("INSERT INTO your_table VALUES", df.to_dict('records'))
    

    希望这会有所帮助。

    【讨论】:

      【解决方案4】:

      sqlalchemy-clickhouse 不能自动创建表(至少直到版本 0.1.5.post0 包括在内)并将创建表的任何 sql 查询解释为 SELECT-查询总是尾随FORMAT TabSeparatedWithNamesAndTypes-子句。

      要解决此问题,需要使用infi.clickhouse_orm-api 手动创建一个表(此模块与 sqlalchemy-clickhouse 一起提供):

      import pandas as pd
      from infi.clickhouse_orm.engines import Memory
      from infi.clickhouse_orm.fields import UInt16Field, StringField
      from infi.clickhouse_orm.models import Model
      from sqlalchemy import create_engine
      
      
      # define the ClickHouse table schema
      class Test_Humans(Model):
          year = UInt16Field()
          first_name = StringField()
          engine = Memory()
      
      
      engine = create_engine('clickhouse://default:@localhost/test')
      
      # create table manually
      with engine.connect() as conn:
          conn.connection.create_table(Test_Humans) # https://github.com/Infinidat/infi.clickhouse_orm/blob/master/src/infi/clickhouse_orm/database.py#L142
      
      pdf = pd.DataFrame.from_records([
          {'year': 1994, 'first_name': 'Vova'},
          {'year': 1995, 'first_name': 'Anja'},
          {'year': 1996, 'first_name': 'Vasja'},
          {'year': 1997, 'first_name': 'Petja'},
          # ! sqlalchemy-clickhouse ignores the last item so add fake one
          {}
      ])
      
      pdf.to_sql('test_humans', engine, if_exists='append', index=False)
      

      考虑到 sqlalchemy-clickhouse 会忽略最后一项,所以添加一个假的(参见source code 和相关的issue 10)。

      让我们检查一下 DB 中的这张表:

      SELECT *
      FROM test.test_humans
      
      ┌─year─┬─first_name─┐
      │ 1994 │ Vova       │
      │ 1995 │ Anja       │
      │ 1996 │ Vasja      │
      │ 1997 │ Petja      │
      └──────┴────────────┘
      
      4 rows in set. Elapsed: 0.003 sec.
      */
      

      也可以查看https://stackoverflow.com/a/68843676/303298的答案。

      【讨论】:

        猜你喜欢
        • 2013-05-04
        • 2018-04-12
        • 1970-01-01
        • 2021-10-27
        • 1970-01-01
        • 2020-03-24
        • 2021-10-06
        • 2019-05-01
        相关资源
        最近更新 更多