【问题标题】:build SQL dynamic query with psycopg2 python library and using good conversion type tools使用 psycopg2 python 库并使用良好的转换类型工具构建 SQL 动态查询
【发布时间】:2012-10-28 20:05:20
【问题描述】:

我在设计一个使用 here 描述的 psycopg2 库规范的好算法时遇到了一些问题

我想建立一个与这个字符串相等的动态查询:

SELECT ST_GeomFromText('POLYGON((0.0 0.0,20.0 0.0,20.0 20.0,0.0 20.0,0.0 0.0))');

如您所见,我的 POLYGON 对象包含多个点,读入一个简单的 csv 文件 some.csv,其中包含:

0.0;0.0
20.0;0.0
20.0;20.0
0.0;20.0
0.0;0.0

所以我动态构建查询,csv 中行/数据数的函数。

这里我的程序生成要执行的 SQL Query 字符串:

import psycopg2
import csv 

# list of points
lXy = []

DSN= "dbname='testS' user='postgres' password='postgres' host='localhost'"
conn = psycopg2.connect(DSN)

curs = conn.cursor()

def genPointText(curs,x,y):
    generatedPoint = "%s %s" % (x,y)
    return generatedPoint

#Lecture fichier csv
polygonFile = open('some.csv', 'rb')
readerCSV = csv.reader(polygonFile,delimiter = ';')

for coordinates in readerCSV:
    lXy.append(genPointText(curs,float(coordinates[0]),float(coordinates[1])))

# function of list concatenation by separator
def convert(myList,separator):
    return separator.join([str(i) for i in myList])

# construct simple query with psycopg
def genPolygonText(curs,l):
    # http://initd.org/psycopg/docs/usage.html#python-types-adaptation
    generatedPolygon = "POLYGON((%s))" % convert(l, ",")
    return generatedPolygon

def executeWKT(curs,geomObject,srid):
    try:
            # geometry ST_GeomFromText(text WKT, integer srid);
        finalWKT = "SELECT ST_GeomFromText('%s');" % (geomObject) 
        print finalWKT
        curs.execute(finalWKT)
    except psycopg2.ProgrammingError,err:
        print "ERROR = " , err

polygonQuery = genPolygonText(curs,lXy)
executeWKT(curs,polygonQuery,4326)

如您所见,这是可行的,但是由于python对象和sql postgresql对象之间的转换问题,这种方式不正确。

在文档中,我只看到了为静态查询提供和转换数据的示例。您是否知道在查询的动态构建中创建具有正确类型的正确字符串的“优雅”方式?

更新 1:

正如你所看到的,当我在这个简单的例子中使用 psycopg 类型转换函数时,我有这样的错误:

query = "ST_GeomFromText('POLYGON(( 52.146542 19.050557, 52.148430 19.045527, 52.149525 19.045831, 52.147400 19.050780, 52.147400 19.050780, 52.146542 19.050557))',4326)"
name = "my_table"

try:
    curs.execute('INSERT INTO %s(name, url, id, point_geom, poly_geom) VALUES (%s);', (name,query))
except psycopg2.ProgrammingError,err:
    print "ERROR = " , err

错误相等:

ERROR =  ERREUR:  erreur de syntaxe sur ou près de « E'my_table' »
LINE 1: INSERT INTO E'my_table'(name, poly_geom) VALUES (E'ST_GeomFr...

更新 2:

感谢stackoverflow用户的最终代码!

#info lib : http://www.initd.org/psycopg/docs/
import psycopg2
# info lib : http://docs.python.org/2/library/csv.html
import csv 

# list of points
lXy = []

DSN= "dbname='testS' user='postgres' password='postgres' host='localhost'"

print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)

curs = conn.cursor()

def genPointText(curs,x,y):
    generatedPoint = "%s %s" % (x,y)
    return generatedPoint

#Lecture fichier csv
polygonFile = open('some.csv', 'rb')
readerCSV = csv.reader(polygonFile,delimiter = ';')

for coordinates in readerCSV:
    lXy.append(genPointText(curs,float(coordinates[0]),float(coordinates[1])))

# function of list concatenation by separator
def convert(myList,separator):
    return separator.join([str(i) for i in myList])

# construct simple query with psycopg
def genPolygonText(l):
    # http://initd.org/psycopg/docs/usage.html#python-types-adaptation
    generatedPolygon = "POLYGON((%s))" % convert(l, ",")
    return generatedPolygon

def generateInsert(curs,tableName,name,geomObject):
    curs.execute('INSERT INTO binome1(name,geom) VALUES (%s, %s);' , (name,geomObject))


def create_db_binome(conn,name):

    curs = conn.cursor()

    SQL = (
        "CREATE TABLE %s"
        " ("
        " polyname character varying(15),"
        " geom geometry,"
        " id serial NOT NULL,"
        " CONSTRAINT id_key PRIMARY KEY (id)"
        " )" 
        " WITH ("
        " OIDS=FALSE"
        " );"
        " ALTER TABLE %s OWNER TO postgres;"
        ) %(name,name)
    try:
      #print SQL
      curs.execute(SQL)

    except psycopg2.ProgrammingError,err:
      conn.rollback()
      dropQuery = "ALTER TABLE %s DROP CONSTRAINT id_key; DROP TABLE %s;" % (name,name)
      curs.execute(dropQuery)
      curs.execute(SQL)

    conn.commit()

def insert_geometry(polyname,tablename,geometry):

    escaped_name = tablename.replace('""','""')

    try:
        test = 'INSERT INTO %s(polyname, geom) VALUES(%%s, ST_GeomFromText(%%s,%%s))' % (escaped_name)
        curs.execute(test, (tablename, geometry, 4326))
        conn.commit()
    except psycopg2.ProgrammingError,err:
        print "ERROR = " , err

################
# PROGRAM MAIN #
################

polygonQuery = genPolygonText(lXy)
srid = 4326
table = "binome1"

create_db_binome(conn,table)
insert_geometry("Berlin",table,polygonQuery)
insert_geometry("Paris",table,polygonQuery)

polygonFile.close()
conn.close()

【问题讨论】:

  • 您的程序生成的查询文本是什么?错误信息是什么?
  • 我用简单的例子和​​错误跟踪更新:)

标签: python postgresql type-conversion postgis psycopg2


【解决方案1】:

您正在尝试将表名作为参数传递。如果您只是查看 PostgreSQL 错误日志,您可能会立即看到这一点。

您试图通过 psycopg2 作为参数传递的表名被转义,产生如下查询:

INSERT INTO E'my_table'(name, url, id, point_geom, poly_geom) VALUES (E'ST_GeomFromText(''POLYGON(( 52.146542 19.050557, 52.148430 19.045527, 52.149525 19.045831, 52.147400 19.050780, 52.147400 19.050780, 52.146542 19.050557))'',4326)');'

这不是您想要的,也不会起作用;你不能像文字一样转义表名。必须使用普通的 Python 字符串插值来构造动态 SQL,实际字面值只能使用参数化语句占位符。

params = ('POLYGON(( 52.146542 19.050557, 52.148430 19.045527, 52.149525 19.045831, 52.147400 19.050780, 52.147400 19.050780, 52.146542 19.050557))',4326)
escaped_name = name.replace('"",'""')
curs.execute('INSERT INTO "%s"(name, url, id, point_geom, poly_geom) VALUES (ST_GeomFromText(%%s,%%s));' % escaped_name, params)

看看我是如何直接插入名称来生成查询字符串的:

INSERT INTO my_table(name, url, id, point_geom, poly_geom) VALUES (ST_GeomFromText(%s,%s));

%% 通过 % 替换转换为普通的 %)。然后我将该查询与定义POLYGON 的字符串和ST_GeomFromText 的另一个参数一起用作查询参数。

我没有对此进行测试,但它应该可以为您提供正确的想法并帮助解释问题所在。

非常小心在进行这样的字符串插值时,这是SQL injection 的简单途径。我在上面显示的代码中做了非常粗略的引用,但如果您的客户端库提供了一个正确的标识符引用函数,我想使用它。

【讨论】:

  • 谢谢!根据您的回答,我通过创建数据库的完整工作解决方案/程序来更新我的问题。
【解决方案2】:

正确的方法是使用 psycopg2 2.7 的新 sql module,其中包含一个 Identifier 对象。这允许您以安全的方式动态指定 SQL 标识符。

不幸的是,PyPi 上还没有 2.7(截至写作时为 2.6.2)。

在此之前,psycopg2 在“如何将字段/表名称传递给查询?”标题下对此进行了介绍。 http://initd.org/psycopg/docs/faq.html#problems-with-type-conversions

您可以使用 AsIs 函数将 SQL 标识符与数据值一起传递给执行函数。

注意:这提供了没有安全性。和使用格式字符串一样好,不推荐。 这样做的唯一真正优势是您鼓励未来的代码遵循执行 + 数据风格。以后也可以轻松搜索AsIs

from psycopg2.extensions import AsIs
<snip>
with transaction() as cur:
    # WARNING: not secure
    cur.execute('SELECT * from %(table)s', {'table': AsIs('mytable')})

【讨论】:

    【解决方案3】:

    现在 PyPi 上有 2.7,这是一个动态查询的示例。

    在本例中,我将假设多边形作为您 csv 文件中的字典。键可以是上面提到的名称、url、id、point_geom、poly_geom,但只要表结构包含相同的键,它们就无关紧要。

    可能有一种方法可以缩短它,但我希望这能阐明 sql 函数的使用,即 sql.SQLsql.Identifiersql.Placeholder,以及如何连接字符串列表 sql.SQL('..').join(list())

    from psycopg2 import sql
    table = 'my_table'
    polygon = Polyogon.from_file()  # or something
    column_list = list()
    value_list = list()
    
    # Convert the dictionary to lists
    for column, value in polygon.items():
        column_list.append(sql.Identifier(column))  # Convert to identifiers
        value_list.append(value)
    
    # Build the query, values will be inserted later
    query = sql.SQL("INSERT INTO {} ({}) VALUES ({}) ON CONFLICT DO NOTHING").format(
                    sql.Identifier(table),
                    sql.SQL(', ').join(column_list),  # already sql.Identifier
                    sql.SQL(', ').join([sql.Placeholder()] * len(value_list)))
    
    # Execute the cursor
    with postgres.cursor() as p_cursor:
        # execute requires tuples and not a list
        p_cursor.execute(insert_query, tuple(value_list))  
    

    参考:http://initd.org/psycopg/docs/sql.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 2015-12-01
      • 1970-01-01
      • 2018-01-27
      相关资源
      最近更新 更多