4

At the moment, I have a database which contains username, password, etc. I am wishing to look into the database to check if duplicates are in there.

con = lite.connect('userInfo.db')
with con:
    cur = con.cursor()
    cur.execute("SELECT * FROM Users WHERE LOWER(Username) = LOWER(?)", (newID,))
    rows = cur.fetchall()
    if len(rows)!=0:
        return "Duplicate detected"

Here is my code at the moment. newID is a new name and I wish to check if there are any existing entries in the database with the same name.

My question is - is the way I am doing it in my code a good idea? I'm mainly concerned with my approach. Should I be using something other than fetchall() ?

Thank you for your time! :) P.S. This is for a website application.

user432584920684
  • 379
  • 1
  • 8
  • 19

2 Answers2

5

Here's a way to do exactly what you asked for - find out if a given username already exists:

import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute ("""
              CREATE TABLE users (
                  uid INTEGER PRIMARY KEY AUTOINCREMENT,
                  username TEXT UNIQUE,
                  email TEXT UNIQUE );
             """)

test_users = (
    {'username':"Alice",    'email':"Alice@mail.com"},
    {'username':"Billy",    'email':"Billy@mail.com"},
    {'username':"Charles",  'email':"Charles@mail.com"},
    {'username':"Dick",     'email':"Dick@mail.com"},
    {'username':"Emily",    'email':"Emily@mail.com"},
    {'username':"Faramir",  'email':"Faramir@mail.com"},
)

for user in test_users:
    conn.execute("INSERT INTO users (username, email) VALUES (?,?)",
                 (user['username'],user['email'])
                )

result = conn.execute("SELECT COUNT(*) FROM users WHERE username='Alice'")
number_of_Alices = result.next()[0] # number_of_Alices will be 1

Since all you want is a COUNT this is adequate.


Really, though, you shouldn't be enforcing the uniqueness of the usernames yourself. Let the database do that for you by specifying the field to be either UNIQUE or PRIMARY KEY.

If you try to insert "Alice", "alice@wonderland.com" after creating the database like above, this will get you an sqlite3.IntegrityError:

>>> conn.execute("""INSERT INTO users (username, email)
...                     VALUES ("Alice", "alice@wonderland.com");""")
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
sqlite3.IntegrityError: column username is not unique

To detect this, try to run the INSERT and detect whether it fails.

try:
    conn.execute("""INSERT INTO users (username, email)
                    VALUES ("Alice", "alice@wonderland.com");""")
except sqlite3.IntegrityError:
    print ("Username 'Alice' was already taken.")

Incidentally, be very careful with using the upper/lowercase functions. Does "Главное в новостях".lower() mean what you think it means?


Since you mention this is for a webapp, I'll just remind you to store your passwords as salted hashes of the password, using unique salts for each user (never as plain text!), and to guard against SQL injection by using the (?,?,?,?,...) placeholders for SQL queries, not the (%s,%s) % (var1, var2) string interpolation method.

To quote the sqlite3 documentation:

Usually your SQL operations will need to use values from Python variables. You shouldn’t assemble your query using Python’s string operations because doing so is insecure; it makes your program vulnerable to an SQL injection attack.

Instead, use the DB-API’s parameter substitution. Put ? as a placeholder wherever you want to use a value, and then provide a tuple of values as the second argument to the cursor’s execute() method. (Other database modules may use a different placeholder, such as %s or :1.) For example:

If you don't do this, then someone could request the username Robert Menzies; DROP TABLE users; with hilarious results.

Community
  • 1
  • 1
Li-aung Yip
  • 12,320
  • 5
  • 34
  • 49
  • Thank you for your fine explanation. Greatly appreciated. Just out of curiosity, what does `Robert Menzies; DROP TABLE users;` actually do and why? Thans! – user432584920684 Mar 19 '12 at 06:15
  • Actually the name would need to be `Robert Menzies); DROP TABLE users;`. Then if your query is like `"INSERT INTO users VALUES (%s)" % username`, then after the variable substitution you will have `"INSERT INTO users VALUES (Robert Menzies); DROP TABLE users;` - which will insert `Robert Menzies` into the `users` table, then **delete the entire `users` table.** – Li-aung Yip Mar 19 '12 at 07:15
  • 1
    This is called an SQL injection attack, and is possible because the `;` character ends an SQL command and begins a new one. **One** of the things you have to do is escape `;` characters in user input so this doesn't happen. Using the recommended practice - `(?,?,?...)` placeholders - in the `sqlite3` documentation will guard against this attack and more. – Li-aung Yip Mar 19 '12 at 07:17
0

Why not set the field in SQLite to unique which will prevent duplicate entries in the first place.

daniel
  • 393
  • 1
  • 3
  • 10
  • What happens if a duplicate entry is detected? – user432584920684 Mar 19 '12 at 02:36
  • @Vincent if you set Username colummn to unique and a insert is made into the Users table with an already existing Username it will result in a fail. e.g. there will never be any duplications in the first place. – daniel Mar 19 '12 at 03:11
  • So would my program essentially 'crash' ? Sorry, I'm new and just starting out at coding. Your help is greatly appreciated. – user432584920684 Mar 19 '12 at 03:15
  • You will get a `SQLite3.OperationalError` which says "something went wrong". If duplicate usernames could be reasonably expected you can catch the exception using a `try... except` block and recover. If duplicate usernames should never be attempted in the first place it's OK to crash. – Li-aung Yip Mar 19 '12 at 03:28
  • And the `username` field should really be `PRIMARY KEY`. That said, using a separate `user_id INTEGER PRIMARY KEY AUTOINCREMENT` and `username TEXT UNIQUE` is probably a better way of doing things - that way strange characters in the `username` won't cause bugs (will your code handle the username "Главное в новостях" correctly?) and users will be free to change their usernames at will (since it's not being used as a key anywhere else.) – Li-aung Yip Mar 19 '12 at 03:31
  • I'm trying to make a web application and it is essentially a 'sign up page' - making sure there is no duplication of usernames. I will try the `try... except` thing now. As for the strange characters, I added a check using `if newID.isalnum() is False:` so that should not pose a problem. – user432584920684 Mar 19 '12 at 03:42
  • @Vincent: `if newID.isalnum()` is a bad idea. There are legitimate reasons for people to use non-ASCII usernames. What happens if a Frenchman wants to join your site? `"Éponine Thénardier".isalnum() == False`! – Li-aung Yip Mar 19 '12 at 03:51
  • Actually even `"Robert Menzies".isalnum()` is false (because of the space character.) – Li-aung Yip Mar 19 '12 at 04:34