将Pandas中的VARIANT类型插入雪花

时间:2020-11-05 17:41:39

标签: python pandas snowflake-cloud-data-platform

我正在尝试将Pandas数据框中的数据插入到Snowflake中的表中,并且在弄清楚如何正确执行操作时遇到了麻烦。首先,我在Snowflake中创建了一个表,该表具有一些类型为VARIANT的列。例如:

CREATE OR REPLACE TABLE
    mydatabase.myschema.results(
        DATE date, 
        PRODUCT string, 
        PRODUCT_DETAILS variant, 
        ANALYSIS_META variant,
        PRICE float
)

然后在Pandas中,我有一个这样的数据框:

import pandas as pd
record = {'DATE': '2020-11-05',
 'PRODUCT': 'blue_banana',
 'PRODUCT_DETAILS': "{'is_blue': True, 'is_kiwi': nan}",
 'ANALYSIS_META': "None",
 'PRICE': 13.02}
df = pd.DataFrame(record, index=[0])

如您所见,我已经将VARIANT列编码为字符串,这是我从snowflake-connector documentation所了解的,雪花VARIANT类型映射为str dtype反之亦然。

所以,我到目前为止尝试的是以下内容:

from snowflake.connector import pandas_tools

pandas_tools.write_pandas(
                conn=conn,
                df=df,
                table_name="results",
                schema="myschema",
                database="mydatabase")

这确实可行,返回

(True,
 1,
 1,
 [('czeau/file0.txt', 'LOADED', 1, 1, 1, 0, None, None, None, None)])

但是,我在Snowflake中得到的结果不是正确的VARIANT类型。字段ANALYSIS_META是正确的NULL,但是字段PRODUCT_DETAILS的类型为str。看到: enter image description here

(例如,此查询还会引发错误:

SELECT * FROM
MYDATABASE.MYSCHEMA.RESULTS
WHERE PRODUCT_DETAILS:is_blue -- should work for json/variant fields

因此,我的问题是:如何正确格式化我的Pandas数据框,以便将他的VARIANT字段正确地作为嵌套字段插入到Snowflake表中?我以为将字典转换为字符串可以解决问题,但是显然它没有按我预期的那样工作。我在这里想念什么?

2 个答案:

答案 0 :(得分:1)

经过调查,我发现以下解决方案可以工作:

1。确保这些列符合json

此处的关键是json.dumps会将您的数据转换为正确的格式(正确的引号,null的表示形式等)。

import pandas as pd
import json
record = {'DATE': '2020-11-05',
 'PRODUCT': 'blue_banana',
 'PRODUCT_DETAILS': json.dumps({'is_blue': True, 'is_kiwi': None}),
 'ANALYSIS_META': json.dumps(None),
 'PRICE': 13.02}
df = pd.DataFrame(record, index=[0])

2。确保您反复使用parse_jsonINSERT

我们可以write_pandas逐行插入表中,而不必像最初尝试的那样使用INSERT,请确保在所需parse_json类型的列上指定VARIANT,同时还将值编码为字符串(在其周围加上'标记)。需要注意的是,如果您有大量数据,此解决方案将非常慢。

sql = """INSERT INTO MYDATABASE.MYSCHEMA.RESULTS
SELECT
 to_date('{DATE}'),
 '{PRODUCT}',
 parse_json('{PRODUCT_DETAILS}'),
 parse_json('{ANALYSIS_META}'),
 {PRICE}
"""
### CREATE A SNOWFLAKE CONN...

for i, r in df.iterrows():
    conn.cursor().execute(sql.format(**dict(r)))

答案 1 :(得分:1)

我认为这里有两个问题。首先,用于插入变体的语法有点奇怪。其次,重要的是要意识到,单引号'和双引号"在Snowflake(或一般来说是SQL?)方面是不同的。

问题陈述

除了熊猫数据框,我们还可以首先针对字典解决此问题。将其扩展到熊猫数据框中的一行将很简单。

目标是编写此查询:

"""
INSERT INTO mydatabase.myschema.results (
     date, product, product_details, analysis_meta, price
)
SELECT
    '2018-01-01',
    'my_new_products',
    parse_json('{"is_blue": true, "is_kiwi": null}'),
    parse_json('{}'),
    13.02
"""

要实现此目的,必须确保"'不会混淆,并且字典值包装在"parse_json"中。


建议的解决方案

一般的解决方案可以基于(解压缩)record中的项目,并在此过程中进行一些操作。首先,我们需要一个函数,该函数将根据它们的数据类型来处理(要插入的)值:

def process_val(v) -> str:
   """ Wrap dicts and string values for SQL INSERT statement """
   if isinstance(v, dict):
       return f"parse_json({json.dumps(v)})"
   elif isinstance(v, str):
       return f"'{v}'"
   else:
       return v

如果偶然发现了新类型的值,则可以添加它们process_val()。在将处理后的值连接在一起时,我们可以使用process_val()

def create_insert_statement(table: str, record: Dict[str, Any]) -> str:
    columns, values = zip(*record.items())

    col_string = ", ".join(columns)
    val_string = ", ".join(process_val(v) for v in values)

    return f"INSERT INTO {table} ({col_string}) SELECT {val_string}"

您现在可以迭代地插入记录:

table = "mydatabase.myschema.results"

for i, r in df.iterrows():
    query = create_insert_statement(table=table, record=dict(r))
    conn.cursor().execute(query)