58

Question: Is it possible to use a variable as your table name without having to use string constructors to do so?


Info:

I'm working on a project right now that catalogs data from a star simulation of mine. To do so I'm loading all the data into a sqlite database. It's working pretty well, but I've decided to add a lot more flexibility, efficiency, and usability to my db. I plan on later adding planetoids to the simulation, and wanted to have a table for each star. This way I wouldn't have to query a table of 20m some planetoids for the 1-4k in each solar system.

I've been told using string constructors is bad because it leaves me vulnerable to a SQL injection attack. While that isn't a big deal here as I'm the only person with access to these dbs, I would like to follow best practices. And also this way if I do a project with a similar situation where it is open to the public, I know what to do.

Currently I'm doing this:

cursor.execute("CREATE TABLE StarFrame"+self.name+" (etc etc)")

This works, but I would like to do something more like:

cursor.execute("CREATE TABLE StarFrame(?) (etc etc)",self.name)

though I understand that this would probably be impossible. though I would settle for something like

cursor.execute("CREATE TABLE (?) (etc etc)",self.name)

If this is not at all possible, I'll accept that answer, but if anyone knows a way to do this, do tell. :)

I'm coding in python.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Narcolapser
  • 5,895
  • 15
  • 46
  • 56

10 Answers10

59

Unfortunately, tables can't be the target of parameter substitution (I didn't find any definitive source, but I have seen it on a few web forums).

If you are worried about injection (you probably should be), you can write a function that cleans the string before passing it. Since you are looking for just a table name, you should be safe just accepting alphanumerics, stripping out all punctuation, such as )(][;, and whitespace. Basically, just keep A-Z a-z 0-9.

def scrub(table_name):
    return ''.join( chr for chr in table_name if chr.isalnum() )

scrub('); drop tables --')  # returns 'droptables'
Donald Miner
  • 38,889
  • 8
  • 95
  • 118
  • 24
    Personally, I'd throw an exception if such characters are seen instead. They must not be there, so something is wrong and I'd rather know about it. – Jan Hudec May 04 '11 at 06:18
  • 3
    -1; the details of this answer seem flawed. It's overly restrictive (not allowing, for instance, underscores in table names, which are fairly common) and, as @JanHudec has pointed out, if `scrub()` ever actually removes any characters, it's almost certainly going to have broken the query; that should cause an exception to be thrown immediately rather than waiting for query execution time to find out. – Mark Amery Apr 18 '17 at 22:23
  • 1
    The issue is that parameters can only substitute in values, not other identifiers or general bits of SQL. That means that they're trivial to inject into the bytecoded version of the query. Substituting more generally would require recompiling the query (yes, even for just changing table or column names) so the SQLite engine says “no”, and you need to do it in the outer language. – Donal Fellows Sep 30 '18 at 13:39
  • In my mind, the real question is why the end user has input into the *names of tables* used in the database. Perhaps there's a better way to normalize OP's database. – Karl Knechtel Jan 09 '23 at 04:23
12

For people searching for a way to make the table as a variable, I got this from another reply to same question here:

It said the following and it works. It's all quoted from mhawke:

You can't use parameter substitution for the table name. You need to add the table name to the query string yourself. Something like this:

query = 'SELECT * FROM {}'.format(table)
c.execute(query)

One thing to be mindful of is the source of the value for the table name. If that comes from an untrusted source, e.g. a user, then you need to validate the table name to avoid potential SQL injection attacks. One way might be to construct a parameterised query that looks up the table name from the DB catalogue:

import sqlite3

def exists_table(db, name):
    query = "SELECT 1 FROM sqlite_master WHERE type='table' and name = ?"
    return db.execute(query, (name,)).fetchone() is not None
jubibanna
  • 1,095
  • 9
  • 21
  • `exists_table` is a nice parameterized check when the table might exist; however it doesn't work so well in the OP's `create table` question... – Gregor y Dec 22 '20 at 01:23
9

I wouldn't separate the data into more than one table. If you create an index on the star column, you won't have any problem efficiently accessing the data.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • I find that hard to believe. To the best of my knowledge sqlite runs through the table and checks to see if the value of the index i want and the value of the index of a star are the same. if they are it selects other wise it continues on and ignores it. That would me it would have to iterate through the couple million entries to find the info it needs. which from a processor stand point, isn't a big deal. but my hdd won't be able to keep up.
    also, that would me the addition of 8*(number of planetoids) bytes to the already large file. I don't like the sound of that.
    – Narcolapser Jul 14 '10 at 16:06
  • 2
    You need to learn more about how SQLite and all relational databases work. They uses indexes to quickly find the rows you want. What you describe is called a "full table scan", and yes, it is horrible. But it can be easily avoided. People make tables with millions of rows all the time, and never have to incur the costs of full table scans. – Ned Batchelder Jul 14 '10 at 16:28
  • ah. So this isn't like an identifier inside the actual data being stored? That makes more sense. and yes you are right. I do need to learn more. I can't argue that as I just started using sqlite about 3 weeks ago. >. – Narcolapser Jul 14 '10 at 22:02
  • The index is a separate structure apart from the rows in the table. You use a key and the index to quickly find the rows of interest. – Ned Batchelder Jul 14 '10 at 22:56
  • If your table FRAME has a column named STAR, then you can tell SQLite to index it with: create index frame_star on frame (star); More here: http://www.sqlite.org/lang_createindex.html This isn't specific to SQLite, this is standard SQL, used by all relational databases. – Ned Batchelder Jul 14 '10 at 23:35
1

Try with string formatting:

sql_cmd = '''CREATE TABLE {}(id, column1, column2, column2)'''.format(
            'table_name')
db.execute(sql_cmd)

Replace 'table_name' with your desire.

Windows Eight
  • 43
  • 1
  • 7
0

To avoid hard-coding table names, I've used:

table = "sometable"
c = conn.cursor()

c.execute('''CREATE TABLE IF NOT EXISTS {} (
                importantdate DATE,
                somename VARCHAR,
                )'''.format(table))

c.execute('''INSERT INTO {} VALUES (?, ?)'''.format(table),
                  (datetime.strftime(datetime.today(), "%Y-%m-%d"),
                   myname))
0

What about f-Strings, much more readable.

table = "myTableName"
condition = "ID = 5"
sql = f'''SELECT * FROM {table} WHERE {condition}'''
juerg
  • 479
  • 5
  • 16
-1

As has been said in the other answers, "tables can't be the target of parameter substitution" but if you find yourself in a bind where you have no option, here is a method of testing if the table name supplied is valid.
Note: I have made the table name a real pig in an attempt to cover all of the bases.

import sys
import sqlite3
def delim(s):
  delims="\"'`"
  use_delim = []
  for d in delims:
   if d not in s:
    use_delim.append(d)
  return use_delim

db_name = "some.db"
db = sqlite3.connect(db_name)
mycursor = db.cursor()
table = 'so""m ][ `etable'
delimiters = delim(table)
if len(delimiters) < 1:
    print "The name of the database will not allow this!"
    sys.exit()
use_delimiter = delimiters[0]
print "Using delimiter ", use_delimiter
mycursor.execute('SELECT name FROM sqlite_master where (name = ?)', [table])
row = mycursor.fetchall()
valid_table = False
if row:
    print (table,"table name verified")
    valid_table = True
else:
    print (table,"Table name not in database", db_name)

if valid_table:
    try:
        mycursor.execute('insert into ' +use_delimiter+ table +use_delimiter+ ' (my_data,my_column_name) values (?,?) ',(1,"Name"));
        db.commit()
    except Exception as e:
        print "Error:", str(e)
    try:
        mycursor.execute('UPDATE ' +use_delimiter+ table +use_delimiter+ ' set my_column_name = ? where my_data = ?', ["ReNamed",1])
        db.commit()
    except Exception as e:
        print "Error:", str(e)
db.close()
Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
  • What if my table is called `my table`? It's perfectly `valid_table`, but your `UPDATE` would fail. – alephreish Nov 11 '15 at 13:32
  • @har-wradim Yes! You are absolutely correct. If you decided to use a table name containing spaces, just as field names containing spaces in sqlite are valid but you wouldn't use them unless you wanted a world of pain. – Rolf of Saxony Nov 11 '15 at 15:18
  • I just wanted to stress that your solution doesn't address the question. A table may be present in the database and bear an inconvenient name at the same time. – alephreish Nov 11 '15 at 15:37
  • No, this is not a solution. Tables are allowed to have any names (yes, they can include quotation marks, brackets, be identical to key words and be Bobby-Table-like) - space was just an example. See e.g. this [blog](http://blog.christosoft.de/2012/10/sqlite-escaping-table-acolumn-names/) – alephreish Nov 11 '15 at 15:58
  • @har-wradim Edited again, to use double quotes as per http://blog.christosoft.de/2012/10/sqlite-escaping-table-acolumn-names/ – Rolf of Saxony Nov 12 '15 at 09:12
  • That's much better. Now you have to take care of the potential double quotes in table names: `my"table` -> `my""table` – alephreish Nov 12 '15 at 12:49
-1

you can use something like this conn = sqlite3.connect() createTable = '''CREATE TABLE %s (# );''' %dateNow) conn.execute(createTable)

basically, if we want to separate the data into several tables according to the date right now, for example, you want to monitor a system based on the date.

createTable = '''CREATE TABLE %s (# );''' %dateNow) means that you create a table with variable dateNow which according to your coding language, you can define dateNow as a variable to retrieve the current date from your coding language.

  • 1
    Thank you for this code snippet, which might provide some limited, immediate help. A [proper explanation](https://meta.stackexchange.com/q/114762/349538) would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you’ve made. – Dwhitz Apr 09 '19 at 09:37
-1

You can save your query in a .sql or txt file and use the open().replace() method to use variables in any part of your query. Long time reader but first time poster so I apologize if anything is off here.

```SQL in yoursql.sql```
Sel *
From yourdbschema.tablenm


```SQL to run```
tablenm = 'yourtablename'

cur = connect.cursor() 

query = cur.execute(open(file = yoursql.sql).read().replace('tablenm',tablenm))
Deeboozie
  • 1
  • 1
-4

You can pass a string as the SQL command:

import sqlite3
conn = sqlite3.connect('db.db')
c = conn.cursor()
tablename, field_data = 'some_table','some_data'
query = 'SELECT * FROM '+tablename+' WHERE column1=\"'+field_data+"\""
c.execute(query)
act123
  • 5
  • 1