使用 pyodbc,我尝试使用 insert_values 执行 SQL 合并语句,insert_values 是一个由 120 个元组组成的列表,每个元组有四个元素,在 Azure SQL DB 表中插入或更新(更新插入)值:
[('2023', 'M12', 'December', '541.442'),
('2023', 'M11', 'November', '486.639'),
('2023', 'M10', 'October', '468.226'),
('2023', 'M09', 'September', '478.802'),
('2023', 'M08', 'August', '475.411'),
('2023', 'M07', 'July', '471.109')]
我不断收到参数标记和参数数量不匹配的错误。我什至不知道如何格式化它,所以一些指导会更好。我可以插入到我的数据库中,并从中选择数据,但我只想插入新信息,并根据连接列匹配时价格列不同的情况更新现有信息。
('The SQL contains 480 parameter markers, but 120 parameters were supplied', 'HY000')
import pyodbc
import json
import textwrap
connection_string = """credentials that work"""
# connection object
obj_pyodbc_connection = pyodbc.connect(connection_string)
# create cursor boject
obj_pyodbc_cursor = obj_pyodbc_connection.cursor()
# Define record set
with open('BLS_source_ppi_data.json') as f:
json_data = json.load(f)
list_BLSIndex_records = []
for var_series in json_data['Results']['series']:
for output in var_series['data']:
filtered_output = {k: v for k, v in output.items() if k in ('year', 'period', 'periodName', 'value')}
list_BLSIndex_records.append(list(filtered_output.values()))
insert_values = list_BLSIndex_records
# Define Data Types
obj_pyodbc_cursor.setinputsizes(
[
(pyodbc.SQL_WVARCHAR, 255, 0),
(pyodbc.SQL_WVARCHAR, 255, 0),
(pyodbc.SQL_WVARCHAR, 255, 0),
(pyodbc.SQL_WVARCHAR, 255, 0)
]
)
# Define table name
table_name = 'z_test_BLS_API'
# Define key columns
col_transaction_year = 'transaction_year'
col_month_num = 'month_num'
# Define other columns
col_month_name = 'month_name'
# Define update columns
col_price_nt = 'price_nt'
# Define values of insert statement
vals = ','.join(["(?, ?, ?, ?)" for _ in insert_values])
# vals = ','.join(["?" for _ in insert_values])
merge_sql = textwrap.dedent("""
MERGE INTO {table_name} AS trgt
USING (
SELECT *
FROM (VALUES {vals}) AS s (transaction_year, month_num, month_name, price_nt)
) AS Source
ON trgt.{trans_year} = Source.{trans_year} AND trgt.{month_num} = Source.{month_num}
WHEN NOT MATCHED THEN
INSERT (transaction_year, month_num, month_name, price_nt)
VALUES (Source.transaction_year, Source.month_num, Source.month_name, Source.price_nt)
WHEN MATCHED AND trgt.price_nt <> Source.price_nt THEN
UPDATE SET {update_column} = Source.{update_column};
""").format(table_name=table_name, trans_year=col_transaction_year, update_column=col_price_nt, month_num=col_month_num, vals=vals)
# Execute Upsert
try:
# obj_pyodbc_cursor.execute(merge_sql, insert_values)
obj_pyodbc_cursor.execute(merge_sql, *insert_values)
# Rollback insert if there are errors
except Exception as e:
obj_pyodbc_cursor.rollback()
print(e)
print('transaction rolled back')
# if there are no errors, commit the insert, print 'records inserted successfully', then close connection with sql
else:
print('records inserted successfully')
obj_pyodbc_cursor.commit()
obj_pyodbc_connection.close()
我已经尝试实施这个解决方案,但我尝试的任何方法都不起作用。请帮助我理解 pyodbc 需要传递什么,列出插入数据的格式,才能发挥作用。我还没有找到任何关于数据必须采用的确切格式的文档,只是使用列表或列表或元组列表将值传递到 pyodbc 游标的教程。
我也尝试过使用 (?, ?, ?, ?) 作为值:
merge_sql = textwrap.dedent("""
MERGE INTO {table_name} AS trgt
USING (
SELECT *
FROM (VALUES (?, ?, ?, ?)) AS s (transaction_year, month_num, month_name, price_nt)
) AS Source
ON trgt.{trans_year} = Source.{trans_year} AND trgt.{month_num} = Source.{month_num}
WHEN NOT MATCHED THEN
INSERT (transaction_year, month_num, month_name, price_nt)
VALUES (Source.transaction_year, Source.month_num, Source.month_name, Source.price_nt)
WHEN MATCHED AND trgt.price_nt <> Source.price_nt THEN
UPDATE SET {update_column} = Source.{update_column};
""").format(table_name=table_name, trans_year=col_transaction_year, update_column=col_price_nt, month_num=col_month_num, vals=vals)
但是我收到以下错误:
('The SQL contains 4 parameter markers, but 120 parameters were supplied', 'HY000')
PYODBC 合并 sql 不起作用:参数标记数不等于提供的参数数。错误代码“HY000”
错误是由于构造
merge
语句并将参数传递给execute
方法造成的。该语句必须采用正确的格式并包含正确数量的参数标记 ?
。由于您使用的是要插入多行的 merge
语句,因此您应该动态构造该语句的 VALUES
部分以匹配要插入/更新的行数。
以下是已存储在Azure数据库表中的数据。
[('2023', 'M12', 'December', '541.401'),
('2023', 'M11', 'November', '486.601'),
('2023', 'M10', 'October', '468.226'),
('2023', 'M09', 'September', '478.802'),
('2023', 'M08', 'August', '475.444'),
('2023', 'M07', 'July', '471.109')]
以下是仅当使用新值修改值时才会插入的数据。
[('2023', 'M12', 'December', '541.111'),
('2023', 'M11', 'November', '486.601'),
('2023', 'M10', 'October', '468.222'),
('2023', 'M09', 'September', '478.802'),
('2023', 'M08', 'August', '475.333'),
('2023', 'M07', 'July', '471.109'),
('2023', 'M06', 'June', '385.100')]
下面是代码,如果已经存在具有相同
transaction_year
和 month_num
但具有不同 month_name
或 price_nt
的行,它将更新这些值。如果某行不存在,它将使用 merge
语句将其插入到表中。
import pyodbc
connection_string = "*****"
conn = pyodbc.connect(connection_string)
cursor = conn.cursor()
data = [('2023', 'M12', 'December', '541.111'),
('2023', 'M11', 'November', '486.601'),
('2023', 'M10', 'October', '468.222'),
('2023', 'M09', 'September', '478.802'),
('2023', 'M08', 'August', '475.333'),
('2023', 'M07', 'July', '471.109'),
('2023', 'M06', 'June', '385.100')]
merge_query = """
MERGE INTO z_test_BLS_API AS Target
USING (
VALUES {}
) AS Source (transaction_year, month_num, month_name, price_nt)
ON Target.transaction_year = Source.transaction_year
AND Target.month_num = Source.month_num
WHEN MATCHED AND (Target.month_name != Source.month_name OR Target.price_nt != Source.price_nt) THEN
UPDATE SET Target.month_name = Source.month_name, Target.price_nt = Source.price_nt
WHEN NOT MATCHED THEN
INSERT (transaction_year, month_num, month_name, price_nt) VALUES (Source.transaction_year, Source.month_num, Source.month_name, Source.price_nt);
""".format(','.join(['(?,?,?,?)' for _ in range(len(data))]))
params = [item for sublist in data for item in sublist]
cursor.execute(merge_query, params)
conn.commit()
cursor.close()
conn.close()
月份为
June
的记录是新记录,值 541.111
是分别插入和修改的新值,如下输出所示。
输出: