SQLAlchemy 从查询结果中访问列类型

SQLAlchemy accessing column types from query results

我正在使用 SQLAlchemy(使用 pymssql 驱动程序)连接到 SQL 服务器数据库。

import sqlalchemy

conn_string = f'mssql+pymssql://{uid}:{pwd}@{instance}/?database={db};charset=utf8'
sql = 'SELECT * FROM FAKETABLE;'

engine = sqlalchemy.create_engine(conn_string)
connection = engine.connect()
result = connection.execute(sql)
result.cursor.description

这导致:

(('col_1', 1, None, None, None, None, None),
 ('col_2', 1, None, None, None, None, None),
 ('col_3', 4, None, None, None, None, None),
 ('col_4', 3, None, None, None, None, None),
 ('col_5', 3, None, None, None, None, None))

根据 PEP 249(游标的 .description 属性):

The first two items (name and type_code) are mandatory, the other five are optional and are set to None if no meaningful values can be provided.

我假设整数 (1, 1, 4, 3, 3) 是列类型。

我的两个问题:

  1. 如何将这些整数映射到数据类型(如字符、整数等)?
  2. 这些是 SQL 数据类型吗?如果不是,是否可以获取 SQL 数据类型?

FWIW,我在使用 raw_connection() 而不是 connect() 时得到相同的结果。

遇到了三个类似的问题(没有回答这个具体问题)。我需要使用 connect() + execute() 方法。

If no, is it possible to get the SQL data types?

SQL 服务器函数 sys.dm_exec_describe_first_result_set 可用于为提供的查询直接获取 SQL 列的数据类型:

SELECT column_ordinal, name, system_type_name, *
FROM sys.dm_exec_describe_first_result_set('here goes query', NULL, 0) ; 

在你的例子中:

sql = """SELECT column_ordinal, name, system_type_name 
    FROM sys.dm_exec_describe_first_result_set('SELECT * FROM FAKETABLE', NULL, 0) ;"""

对于:

CREATE TABLE FAKETABLE(id INT, d DATE, country NVARCHAR(10));

SELECT column_ordinal, name, system_type_name 
FROM sys.dm_exec_describe_first_result_set('SELECT * FROM FAKETABLE', NULL, 0) ;

+-----------------+----------+------------------+
| column_ordinal  |  name    | system_type_name |
+-----------------+----------+------------------+
|              1  | id       | int              |
|              2  | d        | date             |
|              3  | country  | nvarchar(10)     |
+-----------------+----------+------------------+

db<>fiddle demo

查看PEP249type_code 看起来不一样,通过不同的数据库类型。

所以这个答案将集中在 MS SQL 服务器上。

  1. How to map these integers to data types (like char, integer, etc.)?

您可以使用以下代码创建 type_codetype_object 的字典:

import inspect
import pymssql

code_map = {
    type_obj.value: (type_name, type_obj)
    for type_name, type_obj
    in inspect.getmembers(
        pymssql,
        predicate=lambda x: isinstance(x, pymssql.DBAPIType),
    )
}

这将产生以下字典:

{2: ('BINARY', <DBAPIType 2>),
 4: ('DATETIME', <DBAPIType 4>),
 5: ('DECIMAL', <DBAPIType 5>),
 3: ('NUMBER', <DBAPIType 3>),
 1: ('STRING', <DBAPIType 1>)}

不幸的是,我无法访问 MS SQL 服务器的 运行 实例。所以我无法检查类型结果是否与您的示例匹配。

  1. Are these SQL data types? If no, is it possible to get the SQL data types?

查看 PEP 和此结果:此字段不是 SQL 数据类型。这是“类型对象”。

DB API 看起来没有提供检查查询结果元数据的方法/函数。 API 只是提供了一种将数据类型从 SQL 绑定到 python 类型的方法。

如果您需要获取准确的 SQL 数据类型,那么您必须编写特定于服务器的 SQL 查询。

可能是驱动本身。下面我有几乎和你一样的代码,只是使用 AdventureWorks 上的 pyodbc 驱动程序。我选择了一个有很多不同数据类型的 table,它们都在显示。

import sqlalchemy
conn_string = conn_string = f'mssql+pyodbc://{username}:{pwd}@{instance}/AdventureWorksLT2017?driver=ODBC+Driver+17+for+SQL+Server'
sql = 'SELECT TOP 10 * FROM SalesLT.Product;'

engine = sqlalchemy.create_engine(conn_string)
connection = engine.connect()

result = connection.execute(sql)
print(result.cursor.description)

输出:

(('ProductID', <class 'int'>, None, 10, 10, 0, False), ('Name', <class 'str'>, None, 50, 50, 0, False), ('ProductNumber', <class 'str'>, None, 25, 25, 0, False), ('Color', <class 'str'>, None, 15, 15, 0, True), ('StandardCost', <class 'decimal.Decimal'>, None, 19, 19, 4, False), ('ListPrice', <class 'decimal.Decimal'>, None, 19, 19, 4, False), ('Size', <class 'str'>, None, 5, 5, 0, True), ('Weight', <class 'decimal.Decimal'>, None, 8, 8, 2, True), ('ProductCategoryID', <class 'int'>, None, 10, 10, 0, True), ('ProductModelID', <class 'int'>, None, 10, 10, 0, True), ('SellStartDate', <class 'datetime.datetime'>, None, 23, 23, 3, False), ('SellEndDate', <class 'datetime.datetime'>, None, 23, 23, 3, True), ('DiscontinuedDate', <class 'datetime.datetime'>, None, 23, 23, 3, True), ('ThumbNailPhoto', <class 'bytearray'>, None, 0, 0, 0, True), ('ThumbnailPhotoFileName', <class 'str'>, None, 50, 50, 0, True), ('rowguid', <class 'str'>, None, 36, 36, 0, False), ('ModifiedDate', <class 'datetime.datetime'>, None, 23, 23, 3, False))

你能试试这个驱动程序作为比较吗?