0

I am trying to pass a table valued parameter from Python to SQL Server query.

This is my table value that I want pass as a param.

table_values = [
        'table_type_name',
        'dbo',
        (1,None,'Test')
    ]

Here is my param syntax.

params = [(table_values,None)]

Here I am executing this with pyodbc connection

execute(SAVE_QUERY, params)

Here is my SQL query

SAVE_QUERY= """
    SET NOCOUNT ON;
    SET ANSI_NULLS ON;

    DECLARE @table_values TABLE = ?;

    UPDATE 
        table_name
    SET 
        contacts.is_active = 0,
    FROM
        table_name
    LEFT JOIN
        @table_values CC
    ON
        CC.id = contacts.id
    WHERE
        CC.email IS NULL;

"""

it is giving me the below error

[42000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Incorrect syntax near '='. (102) (SQLExecDirectW); [42000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Statement(s) could not be prepared. (8180)

What's the best way to receive the TVP data in this query and store it in variable for using it later part of query.

I have tried removing DECLARE statement but its not working.

8
  • 1
    This might help stackoverflow.com/questions/61148084/… Commented Nov 4, 2024 at 14:11
  • 1
    DECLARE @table_values TABLE = ?; is nonsense, there's no such syntax. LEFT JOIN ? CC makes sense. Commented Nov 4, 2024 at 14:35
  • Thanks @Charlieface, i was trying to find any docs at microsoft which don't mention procedure but rather stand-alone TVP, but nothing there. Commented Nov 4, 2024 at 14:37
  • @siggemannen Well ad-hoc parameterized SQL is actually passed through via EXEC sp_executesql so it comes to the same thing. Commented Nov 4, 2024 at 14:39
  • The LEFT JOIN ? CC suggestion by @Charlieface should work provided that you don't run into this limitation in pyodbc. Commented Nov 4, 2024 at 17:33

1 Answer 1

0

As @Charlieface suggests in a comment to the question, for a user-defined table type

CREATE TYPE [dbo].[table_type_name] AS TABLE(
    [id] [int] NOT NULL,
    [email] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [name] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)

the following code would work

table_values = [
    "table_type_name",
    "dbo",
    (1, None, "Test"),
]

save_query = """\
    SET NOCOUNT ON;
    UPDATE contacts
        SET contacts.is_active = 0
        FROM contacts LEFT JOIN ? CC ON CC.id = contacts.id
        WHERE CC.email IS NULL;
"""
crsr.execute(save_query, (table_values,))

except that the first row of the table data contains a None value and a known issue with pyodbc raises the error

pyodbc.Error: ('HY004', '[HY004] [Microsoft][ODBC Driver 18 for SQL Server]Invalid SQL data type (0) (SQLBindParameter)')

The workaround is to use OPENJSON() like so:

table_rows_as_tuples = [
    (1, None, "Test"),
]
table_rows_as_dicts = [
    dict(zip(["id", "email", "name"], tup)) for tup in table_rows_as_tuples
]

save_query = """\
    SET NOCOUNT ON;
    DECLARE @table_values [dbo].[table_type_name];
    INSERT INTO @table_values
        SELECT id, email, name
        FROM OPENJSON(?)
        WITH (
            id int,
            email nvarchar(255),
            name nvarchar(255)
        );
    UPDATE contacts
        SET contacts.is_active = 0
        FROM contacts LEFT JOIN @table_values CC ON CC.id = contacts.id
        WHERE CC.email IS NULL;
"""
crsr.execute(save_query, (json.dumps(table_rows_as_dicts, default=str),))
Sign up to request clarification or add additional context in comments.

1 Comment

I must admit, using OPENJSON as a crutch like that is really yuck... It adds so much bloat to the code, and is not user friendly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.