Python UPDATE query with parameters in different places - python

I'm writing a simple script that checks if user account is about to expire. I'm having a problem with an UPDATE query - it doesn't update, basically. All examples I've found on the internet seem to use tuples to update rows, however my case requires parameters to be apart from each other.
I'm completely new to Python (I started literally yesterday). My database is MySQL (almost all examples on the web use SQLite) and I can't change that. I use Python 3 and the server is running on Ubuntu 18.04. I tried replacing %s with ? or :variable. I also tried insecure way of doing this (SQL Injection vulnerable) and it didn't work either.
This is my current code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import mysql.connector
from mysql.connector import Error
import datetime
try:
mydb = mysql.connector.connect(
host="localhost",
user="rootery-root",
passwd="example",
database="playground"
)
sqlCursor = mydb.cursor()
sqlCursor.execute("SELECT id, email, last_email_date FROM users WHERE soon_expires = 1")
sqlResult = sqlCursor.fetchall()
setLastMailCmd = """UPDATE users SET last_email_date =%s WHERE id =%s"""
today = datetime.date.today()
for i in range(0, len(sqlResult)):
id = sqlResult[i][0]
email = sqlResult[i][1]
lastemaildate = sqlResult[i][2]
daydiff = lastemaildate - today
setLastMail = sqlCursor.execute(setLastMailCmd, (id, today))
if daydiff.days >= 30:
print "Sending mail and updating \"last_email_date\"."
setLastMail
else:
print "%s already received an e-mail this month - ignored." % email
setLastMail # debugging purposes
except mysql.connector.Error as error:
print("SQL connection error.".format(error))
finally:
if (mydb.is_connected()):
sqlCursor.close()
mydb.close()
print("Disconnected from database.")
print(today)
I expected it to update my table with data provided by the for loop, however it does nothing at all.

Try using functions more. You put everything in one place and it's not easy to debug.
Your problem is the way you use setLastMail # debugging purposes. It does nothing.
What would be better:
mydb = mysql.connector.connect(
host="localhost",
user="rootery-root",
passwd="example",
database="playground"
)
sqlCursor = mydb.cursor()
def set_last_email(id):
stmt = """UPDATE users SET last_email_date =%s WHERE id =%s"""
today = datetime.date.today()
sqlCursor.execute(stmt, (id, today))
And then just execute your set_last_email(id).
Remember to make cursor global, otherwise it won't be available in your function. Or acquire it directly in your function from global connection.
That's of course a dummy example, but you need to start somewhere :)

Related

Why am I getting "mysql.connector.errors.InternalError: Unread result found" when I submit my select options form in index.html [duplicate]

I am inserting JSON data into a MySQL database
I am parsing the JSON and then inserting it into a MySQL db using the python connector
Through trial, I can see the error is associated with this piece of code
for steps in result['routes'][0]['legs'][0]['steps']:
query = ('SELECT leg_no FROM leg_data WHERE travel_mode = %s AND Orig_lat = %s AND Orig_lng = %s AND Dest_lat = %s AND Dest_lng = %s AND time_stamp = %s')
if steps['travel_mode'] == "pub_tran":
travel_mode = steps['travel_mode']
Orig_lat = steps['var_1']['dep']['lat']
Orig_lng = steps['var_1']['dep']['lng']
Dest_lat = steps['var_1']['arr']['lat']
Dest_lng = steps['var_1']['arr']['lng']
time_stamp = leg['_sent_time_stamp']
if steps['travel_mode'] =="a_pied":
query = ('SELECT leg_no FROM leg_data WHERE travel_mode = %s AND Orig_lat = %s AND Orig_lng = %s AND Dest_lat = %s AND Dest_lng = %s AND time_stamp = %s')
travel_mode = steps['travel_mode']
Orig_lat = steps['var_2']['lat']
Orig_lng = steps['var_2']['lng']
Dest_lat = steps['var_2']['lat']
Dest_lng = steps['var_2']['lng']
time_stamp = leg['_sent_time_stamp']
cursor.execute(query,(travel_mode, Orig_lat, Orig_lng, Dest_lat, Dest_lng, time_stamp))
leg_no = cursor.fetchone()[0]
print(leg_no)
I have inserted higher level details and am now searching the database to associate this lower level information with its parent. The only way to find this unique value is to search via the origin and destination coordinates with the time_stamp. I believe the logic is sound and by printing the leg_no immediately after this section, I can see values which appear at first inspection to be correct
However, when added to the rest of the code, it causes subsequent sections where more data is inserted using the cursor to fail with this error -
raise errors.InternalError("Unread result found.")
mysql.connector.errors.InternalError: Unread result found.
The issue seems similar to MySQL Unread Result with Python
Is the query too complex and needs splitting or is there another issue?
If the query is indeed too complex, can anyone advise how best to split this?
EDIT As per #Gord's help, Ive tried to dump any unread results
cursor.execute(query,(leg_travel_mode, leg_Orig_lat, leg_Orig_lng, leg_Dest_lat, leg_Dest_lng))
leg_no = cursor.fetchone()[0]
try:
cursor.fetchall()
except mysql.connector.errors.InterfaceError as ie:
if ie.msg == 'No result set to fetch from.':
pass
else:
raise
cursor.execute(query,(leg_travel_mode, leg_Orig_lat, leg_Orig_lng, leg_Dest_lat, leg_Dest_lng, time_stamp))
But, I still get
raise errors.InternalError("Unread result found.")
mysql.connector.errors.InternalError: Unread result found.
[Finished in 3.3s with exit code 1]
scratches head
EDIT 2 - when I print the ie.msg, I get -
No result set to fetch from
All that was required was for buffered to be set to true!
cursor = cnx.cursor(buffered=True)
The reason is that without a buffered cursor, the results are "lazily" loaded, meaning that "fetchone" actually only fetches one row from the full result set of the query. When you will use the same cursor again, it will complain that you still have n-1 results (where n is the result set amount) waiting to be fetched. However, when you use a buffered cursor the connector fetches ALL rows behind the scenes and you just take one from the connector so the mysql db won't complain.
I was able to recreate your issue. MySQL Connector/Python apparently doesn't like it if you retrieve multiple rows and don't fetch them all before closing the cursor or using it to retrieve some other stuff. For example
import mysql.connector
cnxn = mysql.connector.connect(
host='127.0.0.1',
user='root',
password='whatever',
database='mydb')
crsr = cnxn.cursor()
crsr.execute("DROP TABLE IF EXISTS pytest")
crsr.execute("""
CREATE TABLE pytest (
id INT(11) NOT NULL AUTO_INCREMENT,
firstname VARCHAR(20),
PRIMARY KEY (id)
)
""")
crsr.execute("INSERT INTO pytest (firstname) VALUES ('Gord')")
crsr.execute("INSERT INTO pytest (firstname) VALUES ('Anne')")
cnxn.commit()
crsr.execute("SELECT firstname FROM pytest")
fname = crsr.fetchone()[0]
print(fname)
crsr.execute("SELECT firstname FROM pytest") # InternalError: Unread result found.
If you only expect (or care about) one row then you can put a LIMIT on your query
crsr.execute("SELECT firstname FROM pytest LIMIT 0, 1")
fname = crsr.fetchone()[0]
print(fname)
crsr.execute("SELECT firstname FROM pytest") # OK now
or you can use fetchall() to get rid of any unread results after you have finished working with the rows you retrieved.
crsr.execute("SELECT firstname FROM pytest")
fname = crsr.fetchone()[0]
print(fname)
try:
crsr.fetchall() # fetch (and discard) remaining rows
except mysql.connector.errors.InterfaceError as ie:
if ie.msg == 'No result set to fetch from.':
# no problem, we were just at the end of the result set
pass
else:
raise
crsr.execute("SELECT firstname FROM pytest") # OK now
cursor.reset() is really what you want.
fetchall() is not good because you may end up moving unnecessary data from the database to your client.
The problem is about the buffer, maybe you disconnected from the previous MySQL connection and now it cannot perform the next statement. There are two ways to give the buffer to the cursor. First, only to the particular cursor using the following command:
import mysql.connector
cnx = mysql.connector.connect()
# Only this particular cursor will buffer results
cursor = cnx.cursor(buffered=True)
Alternatively, you could enable buffer for any cursor you use:
import mysql.connector
# All cursors created from cnx2 will be buffered by default
cnx2 = mysql.connector.connect(buffered=True)
cursor = cnx.cursor()
In case you disconnected from MySQL, the latter works for you.
Enjoy coding
If you want to get only one result from a request, and want after to reuse the same connexion for other requests, limit your sql select request to 1 using "limit 1" at the end of your request.
ex "Select field from table where x=1 limit 1;"
This method is faster using "buffered=True"
Set the consume_results argument on the connect() method to True.
cnx = mysql.connector.connect(
host="localhost",
user="user",
password="password",
database="database",
consume_results=True
)
Now instead of throwing an exception, it basically does fetchall().
Unfortunately this still makes it slow, if you have a lot of unread rows.
There is also a possibility that your connection to MySQL Workbench is disconnected. Establish the connection again. This solved the problem for me.
cursor.reset()
and then create tables and load entries
Would setting the cursor within the for loop, executing it, and then closing it again in the loop help?
Like:
for steps in result['routes'][0]['legs'][0]['steps']:
cursor = cnx.cursor()
....
leg_no = cursor.fetchone()[0]
cursor.close()
print(leg_no)

Having trouble creating a MySQL database with python

I can't seem to figure out why my code won't create a MySQL table.
Everything seems ok to me, although my knowledge is limited.
import mysql.connector
import bot_keys as bk
conn = mysql.connector.connect(
host = bk.dbHost,
user = bk.dbUser,
password = bk.dbPass,
database = bk.dbName
)
mycursor = conn.cursor()
def create_table():
sql_fs1 = ("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"
"SET AUTOCOMMIT = 0;START TRANSACTION;"
"SET time_zone = '+00:00';"
"CREATE TABLE `test_table`(`id` int(10) NOT NULL PRIMARY KEY "
"AUTO_INCREMENT,`date_added` timestamp NOT NULL DEFAULT "
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`test_info` "
"varchar(10) NOT NULL,`xxx` varchar(255) NOT NULL);")
mycursor.execute(sql_fs1, multi=True)
conn.commit()
create_table()
Can anyone point out where I am failing? Thanks in advance.
I would first check to see if your connection is actually working...
if conn.is_connected():
db_Info = conn.get_server_info()
print("Connected to MySQL Server version ", db_Info)
This assumes your bot keys have been properly parameter-ized, if i were you I would attempt to initially run w hard coded values, then parameter-ize once youve successfully run things....
The error will go away if you delete the line
conn.commit
The create table command causes an implicit commit,
https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html

Need advice in changing MySQL connection options (Python)

I've been stuck for a few days trying to run some code in MySQL to fill a database that I have already created. Initially upon running I got the error 1251 :
"Client does not support authentication protocol requested by server; consider upgrading MySQL client". In the MySQL documentation and stackoverflow answers I found, I was led to change the default insecureAuth setting from the default false to true. Here is the code I am currently using...
import datetime
import MySQLdb as mdb
from math import ceil
def obtain_btc():
now = datetime.datetime.utcnow()
symbols = ['BTC', 'Crypto', 'Bitcoin', 'No Sector', 'USD', now, now]
return symbols
def insert_btc_symbols(symbols, insecureAuth):
db_host = 'localhost'
db_user = 'natrob'
db_pass = '**********'
db_name = 'securities_master'
con = mdb.connect(host=db_host,user=db_user,passwd=db_pass,db=db_name,{insecureAuth:true})
column_str = "ticker, instrument, name, sector, currency, created_date, last_updated_date"
insert_str = (("%s, ")*7)[:2]
final_str = ("INSERT INTO symbols (%s) VALUES (%s)" % (column_str,insert_str))
print (final_str,len(symbols))
with con:
cur = con.cursor()
for i in range(0,int(ceil(len(symbols)/100.0))):
cur.executemany(final_str,symbols[i*100:(i+1)*100-1])
if __name__ == "__main__":
symbols = obtain_btc()
insert_btc_symbols(symbols)
I recently have gotten the error: "non-keyword arg after keyword arg". I've tried to switch the order to no avail, which leads me to believe that I may not be changing the default setting correctly. Any help or advice is appreciated. Thank you.
The issue looks like is coming from {insecureAuth:true} where it is not a keyword argument. ie var=value. I'm not familiar with the library but if that is a keyword then you should be able to set it as a keyword or pass it with **
con = mdb.connect(host=db_host,user=db_user,passwd=db_pass,db=db_name,insecureAuth=True)
or
con = mdb.connect(host=db_host,user=db_user,passwd=db_pass,db=db_name,**{insecureAuth:true})
I managed to get the section of code working by getting the public key for the password and using that in place of the normal password. This was in lieu of using the insecureAuth parameters.

Empty table in MySQL even though Python can insert data into table

I'm new to mySQL and Python.
I have code to insert data from Python into mySQL,
conn = MySQLdb.connect(host="localhost", user="root", passwd="kokoblack", db="mydb")
for i in range(0,len(allnames)):
try:
query = "INSERT INTO resumes (applicant, jobtitle, lastworkdate, lastupdate, url) values ("
query = query + "'"+allnames[i]+"'," +"'"+alltitles[i]+"',"+ "'"+alldates[i]+"'," + "'"+allupdates[i]+"'," + "'"+alllinks[i]+"')"
x = conn.cursor()
x.execute(query)
row = x.fetchall()
except:
print "error"
It seems to be working fine, because "error" never appears. Instead, many rows of "1L" appear in my Python shell. However, when I go to MySQL, the "resumes" table in "mydb" remains completely empty.
I have no idea what could be wrong, could it be that I am not connected to MySQL's server properly when I'm viewing the table in MySQL? Help please.
(I only use import MySQLdb, is that enough?)
use commit to commit the changes that you have done
MySQLdb has autocommit off by default, which may be confusing at first
You could do commit like this
conn.commit()
or
conn.autocommit(True) Right after the connection is created with the DB

Getting server time and using it in python

I have a unique situation where I am in need of pulling the current time from an SQL server and using that value in my python program as a string. I don't know how to pull it down to print or assign to a variable.
Example:
do stuff...
var = mySQL server datetime <---- How do i do that part ??
print var;
do more stuff...
You can use a library like _mysql to connect to the server then something like this should work.
import _mysql
db = _mysql.connect(host="localhost",user="user", passwd="pass", db="db")
db.execute('select now()')
r = db.store_result()
time_var = r[0][0]
print time_var
In most cases your safest bet is to work with GMT and calculate the offset wherever you need it. If you need to get the date from MySQL however it should look similar to this:
import MySQLdb
db = MySQLdb.connect(host="localhost", user="root", passwd="")
c = db.cursor()
c.execute("SELECT NOW()")
current_time = c.fetchone()
the current_time will be a datetime.datetime object and you can treat it any way you like from there

Categories