Search code examples
pythonsql-serverpython-3.xsql-server-2014pyodbc

Syntax error with query parameter for percentage calculation


I have a pyodbc query that works fine but I am trying to apply an update to add a new calculated field to the results. The working query is:

cursor = cnxn.cursor()
cursor.execute("SELECT RestaurantLocation.ID, Restaurant.DisplayName FROM 
    Restaurant INNER JOIN RestaurantLocation ON Restaurant.ID = 
    RestaurantLocation.Restaurant_ID WHERE (RestaurantLocation.Active = 1)")
row = cursor.fetchall()

columns = [column[0] for column in cursor.description]
for ID,DisplayName in row:
    DisplayName = DisplayName.replace(" ","")
    cursor = cnxn.cursor()
    cursor.execute("SET NOCOUNT ON; SELECT ScheduledService.ServiceDate, 
    Restaurant.DisplayName AS RestaurantName, InvoiceLineItem.Invoice_ID, 
    ScheduledRestaurantService.Location_ID, ScheduledService.Service_ID, 
    Service.DisplayName AS ServiceName, InvoiceLineItem.Description, 
    InvoiceLineItem.Amount AS 'Subtotal' INTO #Subtotal FROM 
    ScheduledService INNER JOIN Invoice ON ScheduledService.Invoice_ID = 
    Invoice.ID INNER JOIN InvoiceLineItem ON Invoice.ID = 
    InvoiceLineItem.Invoice_ID INNER JOIN Payment ON Invoice.ID = 
    Payment.RelatedInvoice_ID INNER JOIN ScheduledRestaurantService ON 
    ScheduledService.ID = ScheduledRestaurantService.ID INNER JOIN 
    RMenuAllocatedScheduleSlot ON ScheduledRestaurantService.TimeSlot_ID = 
    RMenuAllocatedScheduleSlot.ID INNER JOIN [User] ON 
    ScheduledService.RelatedUser_ID = [User].ID INNER JOIN 
    RestaurantLocation ON ScheduledRestaurantService.Location_ID = 
    RestaurantLocation.ID INNER JOIN Restaurant ON 
    RestaurantLocation.Restaurant_ID = Restaurant.ID INNER JOIN Service ON 
    ScheduledService.Service_ID = Service.ID WHERE 
    (ScheduledService.ServiceDate BETWEEN ? AND ?) AND (NOT 
    (ScheduledService.Status_ID = 6)) AND (ScheduledService.Service_ID IN 
    (17, 18, 23, 24, 25)) AND (InvoiceLineItem.Description IN ('Subtotal')) 
    AND (ScheduledRestaurantService.Location_ID = ?) SELECT 
    #Subtotal.Invoice_ID, InvoiceLineItem.Amount AS 'SalesTax' INTO 
    #SalesTax FROM #Subtotal LEFT OUTER JOIN InvoiceLineItem ON 
    #Subtotal.Invoice_ID = InvoiceLineItem.Invoice_ID WHERE 
    (InvoiceLineItem.Description IN ('Sales Tax')) SELECT 
    #Subtotal.Invoice_ID, CASE WHEN #Subtotal.Service_ID IN (17,23,25,26) 
    THEN 0.00 ELSE InvoiceLineItem.Amount END AS 'Tip' INTO #Tip FROM 
    #Subtotal LEFT OUTER JOIN InvoiceLineItem ON #Subtotal.Invoice_ID = 
    InvoiceLineItem.Invoice_ID WHERE (InvoiceLineItem.Description IN 
    ('Tip')) Select #Subtotal.ServiceDate, #Subtotal.RestaurantName, 
    #Subtotal.Invoice_ID, #Subtotal.Location_ID, #Subtotal.Service_ID, 
    #Subtotal.Subtotal, #SalesTax.SalesTax, #Tip.Tip Into #Final From 
    #Subtotal LEFT OUTER JOIN #SalesTax ON #Subtotal.Invoice_ID = 
    #SalesTax.Invoice_ID LEFT OUTER JOIN #Tip ON #Subtotal.Invoice_ID = 
    #Tip.Invoice_ID SET NOCOUNT OFF; Select * From #Final Order By 
    #Final.Service_ID, #Final.ServiceDate Drop Table #Subtotal Drop Table 
    #SalesTax Drop Table #Tip Drop Table #Final", (firstDay,lastDay,ID))
    rows = cursor.fetchall()

Everything there works fine. The results from the second query are based on unique IDs created in the first query.

But when I try to add a new calculated field to the second query I get an "Incorrect Syntax Error" from the SQL Db. Here is the updated query:

cursor = cnxn.cursor()
cursor.execute("SELECT RestaurantLocation.ID, Restaurant.DisplayName FROM 
    Restaurant INNER JOIN RestaurantLocation ON Restaurant.ID = 
    RestaurantLocation.Restaurant_ID WHERE (RestaurantLocation.Active = 1)")
row = cursor.fetchall()

columns = [column[0] for column in cursor.description]
for ID,DisplayName in row:
    DisplayName = DisplayName.replace(" ","")
    cursor = cnxn.cursor()
    print(firstDay,lastDay,ID)
    if ID == 13:
        percentage = 0.18
    else:
        if ID == 25:
            percentage = 0.18
        else:
            percentage = 0.20
    cursor.execute("SET NOCOUNT ON; SELECT ScheduledService.ServiceDate, 
        Restaurant.DisplayName AS RestaurantName, 
        InvoiceLineItem.Invoice_ID, ScheduledRestaurantService.Location_ID, 
        ScheduledService.Service_ID, Service.DisplayName AS ServiceName, 
        InvoiceLineItem.Description, InvoiceLineItem.Amount AS 'Subtotal' 
        INTO #Subtotal FROM ScheduledService INNER JOIN Invoice ON 
        ScheduledService.Invoice_ID = Invoice.ID INNER JOIN InvoiceLineItem 
        ON Invoice.ID = InvoiceLineItem.Invoice_ID INNER JOIN Payment ON 
        Invoice.ID = Payment.RelatedInvoice_ID INNER JOIN 
        ScheduledRestaurantService ON ScheduledService.ID =                                                 
        ScheduledRestaurantService.ID INNER JOIN RMenuAllocatedScheduleSlot 
        ON ScheduledRestaurantService.TimeSlot_ID = 
        RMenuAllocatedScheduleSlot.ID INNER JOIN [User] ON 
        ScheduledService.RelatedUser_ID = [User].ID INNER JOIN 
        RestaurantLocation ON ScheduledRestaurantService.Location_ID = 
        RestaurantLocation.ID INNER JOIN Restaurant ON 
        RestaurantLocation.Restaurant_ID = Restaurant.ID INNER JOIN Service 
        ON ScheduledService.Service_ID = Service.ID WHERE 
        (ScheduledService.ServiceDate BETWEEN ? AND ?) AND (NOT 
        (ScheduledService.Status_ID = 6)) AND (ScheduledService.Service_ID 
        IN (17, 18, 23, 24, 25)) AND (InvoiceLineItem.Description IN 
        ('Subtotal')) AND (ScheduledRestaurantService.Location_ID = ?) 
        SELECT #Subtotal.Invoice_ID, InvoiceLineItem.Amount AS 'SalesTax' 
        INTO #SalesTax FROM #Subtotal LEFT OUTER JOIN InvoiceLineItem ON 
        #Subtotal.Invoice_ID = InvoiceLineItem.Invoice_ID WHERE 
        (InvoiceLineItem.Description IN ('Sales Tax')) SELECT 
        #Subtotal.Invoice_ID, CASE WHEN #Subtotal.Service_ID IN 
        (17,23,25,26) THEN 0.00 ELSE InvoiceLineItem.Amount END AS 'Tip' 
        INTO #Tip FROM #Subtotal LEFT OUTER JOIN InvoiceLineItem ON 
        #Subtotal.Invoice_ID = InvoiceLineItem.Invoice_ID WHERE 
        (InvoiceLineItem.Description IN ('Tip')) Select 
        #Subtotal.ServiceDate, #Subtotal.RestaurantName, 
        #Subtotal.Invoice_ID, #Subtotal.Location_ID, #Subtotal.Service_ID, 
        #Subtotal.Subtotal, #SalesTax.SalesTax, #Subtotal.Subtotal * .? AS 
        Discount, #Tip.Tip Into #Final From #Subtotal LEFT OUTER JOIN 
        #SalesTax ON #Subtotal.Invoice_ID = #SalesTax.Invoice_ID LEFT OUTER 
        JOIN #Tip ON #Subtotal.Invoice_ID = #Tip.Invoice_ID SET NOCOUNT OFF; 
        Select * From #Final Order By #Final.Service_ID, #Final.ServiceDate 
        Drop Table #Subtotal Drop Table #SalesTax Drop Table #Tip Drop Table 
        #Final", (firstDay,lastDay,ID,percentage))
    rows = cursor.fetchall()

The error is being thrown by the value I am assigning to "percentage". I first tried to fix this by using int(), so my IF statement became:

if ID == 13:
    percentage = int(0.18)
else:
    if ID == 25:
        percentage = int(0.18)
    else:
        percentage = int(0.20)

My error there was not recognizing the int() was converted using no decimal places. So I updated the IF statement to use float():

if ID == 13:
        percentage = float(0.18)
    else:
        if ID == 25:
            percentage = float(0.18)
        else:
            percentage = float(0.20)

After using float() to set "percentage" I get the exact same error from SQL. I have tried multiple variations of the IF statements above, but they all result in the exact same error.

What am I doing wrong?


Solution

  • The error is caused by the period (decimal point) immediately before the fourth parameter placeholder:

    ... #Subtotal.Subtotal * .? AS Discount
    

    That is causing pyodbc to call sp_prepexec with a query like

    SELECT ... #Subtotal.Subtotal * .@P1 AS Discount FROM #Subtotal
    

    which is invalid syntax.

    Parameter placeholders should always appear on their own and never rely on delimiters or other elements of the command text to modify their behaviour. In this case,

    sql = "SELECT id, Subtotal, #Subtotal.Subtotal * ? AS Discount FROM #Subtotal"
    params = (Decimal('0.05'),)
    crsr.execute(sql, params)
    

    which sends

    exec sp_prepexec @p1 output,N'@P1 numeric(3,3)',N'SELECT id, Subtotal, #Subtotal.Subtotal * @P1 AS Discount FROM #Subtotal',50
    

    and executes without error.