Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deciphering SQL_VARIANT values #307

Closed
gordthompson opened this issue Dec 4, 2017 · 7 comments · Fixed by #1354
Closed

Deciphering SQL_VARIANT values #307

gordthompson opened this issue Dec 4, 2017 · 7 comments · Fixed by #1354
Labels

Comments

@gordthompson
Copy link
Collaborator

gordthompson commented Dec 4, 2017

A question on Stack Overflow regarding pyodbc and SQL_VARIANT got me curious to see if an output converter function might be feasible. I confirmed that, without an output converter function, pyodbc would throw

pyodbc.ProgrammingError: ('ODBC SQL type -150 is not yet supported. column-index=0 type=-150', 'HY106')

when I tried to retrieve a SQL_VARIANT column. When I added a little function to simply return the bytes (so I could see what we had to work with) ...

import pyodbc  # 4.0.21


def return_bytes(the_bytes):
    return the_bytes


conn_str = (
    r'DRIVER=ODBC Driver 11 for SQL Server;'
    r'SERVER=(local)\SQLEXPRESS;'
    r'DATABASE=myDb;'
    r'Trusted_Connection=yes;'
)
cnxn = pyodbc.connect(conn_str, autocommit=True)
crsr = cnxn.cursor()

crsr.execute("CREATE TABLE #tmp (id INT PRIMARY KEY, x SQL_VARIANT)")
crsr.execute("INSERT INTO #tmp (id, x) VALUES (1, 'Gord')")
crsr.execute("INSERT INTO #tmp (id, x) VALUES (2, 1685221191)")

cnxn.add_output_converter(-150, return_bytes)
for row in crsr.execute("SELECT x FROM #tmp ORDER BY id"):
    print(row.x)

crsr.close()
cnxn.close()

... I found that just the data bytes were returned, without the metadata to describe what type of value it was. That is, under Python 3.6 the above code prints

b'Gord'
b'Gord'

so we apparently cannot differentiate between the values in the two rows: 'Gord' and 1685221191.

The Microsoft document Mapping Data Types (ODBC) suggests that there is a way to get that information (SQL_CA_SS_VARIANT_TYPE) so perhaps pyodbc can make it available to the output converter function.

@LeoQuote
Copy link

I have the same issue when trying to select data structure from database.

@jcfernandez-890825
Copy link

I also have problem when I tried to do a SELECT over a SQL_VARIANT Column.
I'm using Ubuntu 18.04, python3.6, pyodbc 4.0.27, freetds 1.00.82-2, unixodbc 2.3.7.
I have tried both [Microsoft ODBC Driver 17] and [FreeTDS] individually.

Using TDS version 4.2 I can make the SELECT, but I can't use v4.2 with Django 2.2 because it complains with Datetime Fields.

By using pyodbc.connect(...).add_output_converter(-150, sql_variant_handler), where sql_variant_handler is a function I'm able to retrieve the SQL_VARIANT value, but such value is a encrypted somehow. I'm currently trying to find a way to get the real data (integer values) to no avail.

@gordthompson
Copy link
Collaborator Author

@jcfernandez-890825 - If your output converter function is receiving four bytes and you want to convert them to an int then try struct.unpack('i', the_bytes), e.g.,

>>> struct.unpack('i', b'Gord')
(1685221191,)

@ghost
Copy link

ghost commented Apr 1, 2020

I know it won't be ideal for all use cases but if your workload is not intensive or overly complex and you're trying to retrieve data in SQL Server from something like a system property where the value is of type sql_variant, I had luck doing the type conversion in the SQL statement so that when it was returned to pyodbc it was already in a compatible type:

            cnxn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER='+server+';DATABASE='+database+';UID='+username+';PWD='+ password)
            cursor = cnxn.cursor()
            cursor.execute("SELECT CONVERT(VARCHAR(MAX), SERVERPROPERTY('InstanceDefaultDataPath'), 2)")
            datapath = cursor.fetchone()
            datapath = str(pathlib.PurePath(datapath[0]))

@hummingbird1989
Copy link

@gordthompson Do you know if this request was ever worked on?

@gordthompson
Copy link
Collaborator Author

@hummingbird1989 - Not that I'm aware of.

@v-chojas
Copy link
Contributor

That's because pyODBC Is generic and variant types are specific to SQL Server, however you can obtain the base type of a variant using SQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/sql-variant-property-transact-sql

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants