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

No result for SQL-select if character field in where-clause does not match in length using parameter (ORACLE) #212

Closed
ap-0 opened this issue Mar 8, 2017 · 18 comments

Comments

@ap-0
Copy link

ap-0 commented Mar 8, 2017

Hello,

I am using PyODBC 4.0.14 with Python 3.4.4 on Windows 7 64-Bit and connect to a Oracle 11gR2 database running on a SLES12 machine. The ODBC Driver I am using comes with the Client installation of Oracle: 11.02.00.01

When I execute a select-Statement that contains a string parameter (NCHAR in Oracle) I do not get any results if the length of the parameter and the length of the database field do not exactly match.

See this minimal example:

""" create table foo
    (
        colnum    number(20)    not null,
        colchar1  nchar(6)      not null,
        colchar2  nchar(20)     not null
    );

    insert into foo values (1, 'foobar', 'foobar');
"""

import pyodbc

conn = pyodbc.connect("...")
curs = conn.cursor()


curs.execute("select * from foo;")
print(curs.fetchall()) # Works

curs.execute("select * from foo where colnum = ? and colchar1 = ?;", [ 1, "foobar"] )
print(curs.fetchall()) # Works

curs.execute("select * from foo where colnum = ? and colchar2 = ?;", [ 1, "foobar"] )
print(curs.fetchall()) # Does not work

curs.execute("select * from foo where colnum = ? and colchar2 = ?;", [ 1, "foobar".ljust(20) ] )
print(curs.fetchall()) # Works

This does only happen for Oracle Databases. I have tested this with Postgres 9.6.2 and the issue does not come up. I have yet to test this on a Linux machine and maybe try Oracle 12c - but I don't expect changes.

@skillian
Copy link

skillian commented Mar 8, 2017 via email

@skillian
Copy link

skillian commented Mar 8, 2017 via email

@ap-0
Copy link
Author

ap-0 commented Mar 8, 2017

Yes, if the string is filled with whitespace to the same length I get an result.

curs.execute("select * from foo where colnum = ? and colchar2 = ?;", [ 1, "foobar".ljust(20) ] )
print(curs.fetchall()) # Works

@mkleehammer
Copy link
Owner

So, it sounds like it is working? That is how nchar fields work. If you change the field type to nvarchar then it would work like you expect, I think.

@ap-0
Copy link
Author

ap-0 commented Mar 8, 2017

I see your point but when accessing the database directly through sqlplus in a terminal the statement
select * from foo where colnum = 1 and colchar2 = 'foobar';
returns the desired data rows. Also Tools like Toad for Oracle behave the same. It even works with PyODBC if the column values are formatted into the statement string. It just does not work using parameter.

But I would rather avoid formatting the statement string and use parameter to avoid sql injeciton. Unfortunately changing columns to varchar will not be possible.

Now I am wondering why PostgreSQL does not care about the whitespace padding.
(Point is to use ANSI SQL to the application is interchangeable with different databases)

@keitherskine
Copy link
Collaborator

It seems Oracle does follow the ANSI 92 standard of ignoring trailing spaces when comparing strings (https://docs.oracle.com/cd/A97630_01/appdev.920/a96624/b_char.htm#175), but the issue here may be the result of using parameter values. This StackOverflow answer suggests that the parameterized values are sent with specific typing that force trailing spaces to be included in the comparison.

@mkleehammer
Copy link
Owner

I wonder if the fix would be, in this case, to provide the parameters as CHAR instead of VARCHAR. I'm very concerned about what problems that would cause when comparing to actual varchar columns.

This is related to a couple of other issues where we need to know the SQL type of the parameters according to the SQL, not the values we're providing. For example, if we're passing the number '7' that is being used to compare against a bigint field, it would be better if we passed as bigint rather than int. We don't know until we ask the driver, but that requires a compilation.

The current version of pyodbc always prepares SQL if parameters are provided, so it is possible that the performance would not change. I was planning on eliminating the prepare, but we could use the flag to keep it.

@gordthompson
Copy link
Collaborator

How about something like this? (I used some SQL Server terminology to describe the resulting actions, but the actual ODBC implementation would presumably be agnostic.):

# (1) as now, straight SQL Batch execute
crsr.execute(sql_string)
# ... no need to change existing client code

# (2) RPC call to sp_executesql with current parameter handling
params = (param1, param2,)
crsr.execute(sql_string, params)
# ... also no need to change existing client code

# (3) new explicit prepare/execute
crsr.prepare(sql_string)  # new method
# ... sends SQL command text to server to be compiled
# ... and lets us retrieve the expected parameter types/sizes
params = (param1, param2,)
crsr.executeprepared(params)  # new method
# ... does RPC call to sp_execute

@mkleehammer
Copy link
Owner

It is doable, but I'm not sure how that would help. Are you suggesting you'd change the parameters to match the sizes? Once we've prepared, pyodbc can match up the sizes automatically.

@gordthompson
Copy link
Collaborator

I wasn't thinking of changing the params (in Python) after the .prepare. If pyodbc can take care of optimizing the parameters based on the information it gets back from the server then that's great.

It's more about having control over when a prepare takes place, sort of a continuation of my comment here. So not only could an explicit .prepare be used to avoid sending the command text over and over (via sp_prepexec, as it does now), it could also let pyodbc get the information it needs to manage the parameters more effectively.

@mkleehammer
Copy link
Owner

Ah. It isn't strictly necessary, though it still might be a good idea from a usability point of view.

ODBC only allows one prepared statement per HSTMT (Cursor), so you can simply create one cursor for each SQL statement you want to prepare. They are prepared automatically if there are parameters and reused if you execute with the same (exactly the same) SQL.

It might be useful for two reasons: (1) some developers are familiar with prepared statements in other languages and (2) you don't have to keep the Cursor and the SQL, just the cursor.

If I have to prepare anyway to get the parameter type info, it might be better to maintain the old API to keep the surface area down.

@gordthompson
Copy link
Collaborator

gordthompson commented Mar 27, 2017

I guess my point is that we won't necessarily need to prepare in every case (simply to optimize parameters), and by giving users the option to explicitly request a prepare - e.g., when their intent is to do a large number of repetitive operations, or when char vs. string matters - you don't have to give up #214 completely.

@mkleehammer
Copy link
Owner

OK - I hate to have to ask this, but is there an easy way to setup Oracle on CentOS? I've installed Oracle XE in a VM but cannot get anything to connect. Do I still need all of the listener **** if I'm using Oracle instant client? (Honestly, after using PostgreSQL, MySQL, and SQL Server do they not consider making it work out of the box? I get you need to pay a real DBA a million dollars but it isn't helping in situations like this where I don't really care about how optimized it is.)

@ap-0
Copy link
Author

ap-0 commented Apr 5, 2017

Usually a listener is installed along with the database. You can check whether the listener is up and running by using $ORACLE_HOME/bin/lsnrctl status as the oracle user.

For my Oracle installation on Suse Linux I have installed a pre-installation package. It creates a oracle user and sets up the required environment to run the database. AFAIK Oracle provides a RPM for other distributions too.

@mkleehammer
Copy link
Owner

mkleehammer commented Apr 5, 2017

I did install from an RPM, but I still can't connect to it with isql or pyodbc. I also can't figure out what the original database is actually named - XE, xe, test, etc. I'll look for more docs, but I spent a few hours on the weekend and was hoping there was a simple doc somewhere.

@ap-0
Copy link
Author

ap-0 commented Apr 6, 2017

You can try ps -ef | grep pmon. If a database is currently up the result should show ora_pmon_<databasename>. /etc/oratab should also contain one line per database.

If you have found the database name (if the installation created one) you can connect to it using sqlplus sys@ORACLE_SID. ORCLE_SID is the service name configured in the tnsnames.ora file. See here.

This links describes the full installation but maybe there are some hints on what you might have missed.

@sidnaik03
Copy link

Any solution for this ? I am facing same issue with NCHAR datatype.

@keitherskine
Copy link
Collaborator

Closed due to inactivity. Feel free to re-open with current information if necessary.

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

No branches or pull requests

6 participants