Optimizing Python SQL result loop - python

I have the following working code:
async def loadnames(self, ctx):
"""
Load the current alias of every member in the database.
Useful after joining a new guild or after a long downtime.
"""
async with ctx.message.channel.typing():
message = await ctx.send(f'Adding members to the database... {ctx.author.mention}')
db = await aiomysql.connect(host=globals.mysql_host,user=globals.mysql_user,password=globals.mysql_passwd,db=globals.mysql_db)
cur = await db.cursor()
#await cur.execute("SELECT discord,name FROM aliases WHERE discord=%s AND name=%s", (member.id,str(member)))
#result = await cur.fetchall()
count = 0
for member in ctx.guild.members:
try:
result = await cur.execute("SELECT * FROM aliases WHERE discord=%s AND name=%s", (member.id,str(member)))
if result == 0:
count = count + 1
await cur.execute("INSERT INTO aliases (discord,name) VALUES(%s,%s)", (member.id,str(member)))
await db.commit()
except:
await ctx.send(f'Error adding `{member}`! {ctx.author.mention}')
await cur.close()
db.close()
await message.edit(content=f'{count} members added to the database! {ctx.author.mention}')
Now, this works perfectly fine, the only issue I see with this is that I'm executing a new SELECT query for every iteration of my loop. So I'd like to put the SELECT statement outside of the loop, using cur.fetchall() to put it in a list (see commented lines in the code above), but I don't know after that how to check if a pair of (member.id,str(member)) is in my results.

Consider a single insert-select query in loop using the well know duplicate avoidance in SQL: NOT IN vs. NOT EXISTS vs. LEFT JOIN / IS NULL. Below runs the LEFT JOIN / IS NULL approach:
for member in ctx.guild.members:
try:
sql = """INSERT INTO aliases (discord, name)
SELECT a1.discord, a1.name
FROM aliases a1
LEFT JOIN aliases a2
ON a1.discord = a2.discord
AND a1.name = a2.name
AND a1.discord=%s AND a2.name=%s
WHERE a2.name IS NULL AND a2.discord IS NULL
"""
result = await cur.execute(sql, (member.id, str(member)))
count = cur.rowcount
await db.commit()
except:
await ctx.send(f'Error adding `{member}`! {ctx.author.mention}')
Even better, populate member IDs in a temp table and join to above query for only one query for all member IDs. SQL's set-based processing beats out application layer looping!
# CLEAN AND POPULATE TEMP TABLE
await cur.execute("DELETE FROM mytempTable")
members_list = [(member.id, str(member)) for member in ctx.guild.members]
await cur.executemany("INSERT INTO mytempTable (discord, name) VALUES (%s, %s)",
members_list)
await db.commit()
# ONLY ONE INSERT QUERY
sql = """INSERT INTO aliases (discord, name)
SELECT a1.discord, a1.name
FROM aliases a1
INNER JOIN mytempTable t
ON a1.discord = t.discord
AND a1.name = t.name
LEFT JOIN aliases a2
ON a1.discord = a2.discord
AND a1.name = a2.name
WHERE a2.name IS NULL AND a2.discord IS NULL
"""
result = await cur.execute(sql, (member.id, str(member)))
count = cur.rowcount
await db.commit()

Derived from Parfait's answer, I ended up with this:
async def loadnames(self, ctx):
"""
Load the current alias of every member in the database.
Useful after joining a new guild or after a long downtime.
"""
async with ctx.message.channel.typing():
count = 0
message = await ctx.send(f'Adding members to the database... {ctx.author.mention}')
db = await aiomysql.connect(host=globals.mysql_host,user=globals.mysql_user,password=globals.mysql_passwd,db=globals.mysql_db)
cur = await db.cursor()
await cur.execute("SELECT discord, name FROM aliases")
result = await cur.fetchall()
new_member_list = [(member.id, str(member)) for member in ctx.guild.members]
member_list = [(member[0],member[1]) for member in result]
diff_list = [member for member in new_member_list if member not in member_list]
count = await cur.executemany("INSERT INTO aliases (discord, name) VALUES (%s, %s)", diff_list)
await db.commit()
await cur.close()
db.close()
if count is None: count = 0
await message.edit(content=f'{count} members added to the database! {ctx.author.mention}')
Only one SELECT query at the beginning, then we play with the different lists to get the executemany INSERT statement which will do only the required number of insertions.

Related

How to add warn count to warn command with sqlite?

So im trying to make a warn command using sqlite3
everything is fine
it warns them and stores the reason, guild id and user id in the database
but i want to check for how many warns the user got using sqlite3
i tried so many things but nothing worked
any type of help will be appreciated!..
my code :-
import discord
from discord.ext import commands
import sqlite3
class warn(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.command()
async def warn(self, ctx, member: discord.Member, reason=None):
db = sqlite3.connect("warn.db")
cursor = db.cursor()
sql = ("INSERT INTO main (user_id, reason, guild) VALUES (?, ?, ?)")
val = (member.id, reason, ctx.guild.id)
cursor.execute(sql, val)
em = discord.Embed(
description=f"<:zztick:1067013663535411280> | warned **{member.mention}** for the reason: *{reason}*",
color=0x2F3136
)
em.set_footer(text=f"warn no. {DO SOMETHING}")
await ctx.send(embed=em)
db.commit()
db.close()
async def setup(bot):
await bot.add_cog(warn(bot))
thanks for reading...
You can do something like this:
#commands.command()
async def warn(self, ctx, member: discord.Member, reason=None):
db = sqlite3.connect("warn.db")
cursor = db.cursor()
# Just for checking if user already exists in table
check_user_sql = ("SELECT COUNT(*) FROM main WHERE user_id = ? AND guild = ?")
check_user_val = (member.id, ctx.guild.id)
cursor.execute(check_user_sql, check_user_val)
user_exists = cursor.fetchone()[0]
if user_exists:
# If user exists in table, it will update their warn count
update_warn_count_sql = ("UPDATE main SET warn_count = warn_count + 1 WHERE user_id = ? AND guild = ?")
cursor.execute(update_warn_count_sql, check_user_val)
else:
# If user not exists, insert a new row with a warn count of 0
insert_user_sql = ("INSERT INTO main (user_id, reason, guild, warn_count) VALUES (?, ?, ?, 0)")
insert_user_val = (member.id, reason, ctx.guild.id)
cursor.execute(insert_user_sql, insert_user_val)
# To get current warn count of user
warn_count_sql = ("SELECT warn_count FROM main WHERE user_id = ? AND guild = ?")
cursor.execute(warn_count_sql, check_user_val)
warn_count = cursor.fetchone()[0]

Creating an async connection with psycopg3

I am trying to create an async connection using psycopg3. I was using psycopg2 without async and need to move to async database functions. The docs do not give much information.
So this is what I was using with psycopg2. It worked good.
con = psycopg2.connect(host="HOSTNAME", port="PORT", database=("DATABASE", user="USER", password="PASSWORD")
cursor = con.cursor()
Then when I needed to run a query I would just use
cursor.execute(query, params)
cursor.fetchall() # or con.commit() depending on insert or select statement.
Now that I am moving to async functions, I have tried this
con = await psycopg.AsyncConnection.connect(host="HOSTNAME", port="PORT", database="DATABASE", user="USER", password="PASSWORD")
cursor = await con.cursor()
But I get the error that I cannot use await outside of a function.
The docs tell me to do this
async with await psycopg.AsyncConnection.connect() as aconn:
async with aconn.cursor() as cur:
await cur.execute(...)
So do I need to write this in every function that I want to either read or write records with?
Couple examples in my code using psycopg2 currently
async def check_guild(guild_id):
cursor.execute("SELECT guild_id, guild_name, su_id FROM guild WHERE guild_id = %s", [guild_id])
guild = cursor.fetchone()
return guild
async def config_raffle(guild_id, channel_id, channel_name, channel_cat_id, token, token_id, default_address, su_id, fee):
try:
cursor.execute("""INSERT INTO raffle_config (guild_id, channel_id, channel_name, channel_cat_id, token, default_token, default_address, su_id, fee) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (guild_id) DO UPDATE SET channel_id = EXCLUDED.channel_id, channel_name = EXCLUDED.channel_name, channel_cat_id = EXCLUDED.channel_cat_id, token = EXCLUDED.token,
default_token = EXCLUDED.default_token, default_address = EXCLUDED.default_address, su_id = EXCLUDED.su_id, fee = EXCLUDED.fee""",
(guild_id, channel_id, channel_name, channel_cat_id, token, token_id, default_address, su_id, fee))
con.commit()
except:
logging.exception("Exception", exc_info=True)
con.rollback()
print("Error: 25")
return True
So I am thinking maybe my better option is to use the AsyncConnectionPool. I have a db.py file setup like this:
import psycopg_pool
import os
import dotenv
dotenv.load_dotenv()
conninfo = f'host={os.getenv("HOSTNAME")} port={os.getenv("PORT")} dbname={os.getenv("DATABASE")} user={os.getenv("USER")} password={os.getenv("PASSWORD")}'
pool = psycopg_pool.AsyncConnectionPool(conninfo=conninfo, open=False)
async def open_pool():
await pool.open()
I open the pool when my program runs the on_ready function.
I created new tables this way just fine, but when I try to retrieve records I get this error.
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'AsyncConnection' object has no attribute 'fetchone'
Ended up sorting this out this way:
import psycopg_pool
import os
import dotenv
dotenv.load_dotenv()
conninfo = f'host={os.getenv("HOSTNAME")} port={os.getenv("PORT")} dbname={os.getenv("DATABASE")} user={os.getenv("USER")} password={os.getenv("PASSWORD")}'
pool = psycopg_pool.AsyncConnectionPool(conninfo=conninfo, open=False)
async def open_pool():
await pool.open()
await pool.wait()
print("Connection Pool Opened")
async def select_fetchall(query, args):
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(query, args)
results = await cursor.fetchall()
return results
async def write(query, args):
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(query, args)
if 'RETURNING' in query:
results = await cursor.fetchone()
return results
else:
return
Then I just call the functions when I need to read or write to the database and pass the query and args.

discord py - how can i boost the speed of my code up?

i have a problem. I want to make a task for my giveaway-bot for checking if a giveaway ended. So I created a task for doing it and it runs many lines of code and everything is working. But I noticed that my code is pretty slow. Can anyone help me and say what I can improve and how to speed this up?
I use aiomysql for connecting to my mariadb database and time.time() for checking the code speed.
Sorry if I'm making something wrong, I'm new on this site, feel free to comment if you need anything from me. :)
My Discord-py task:
#tasks.loop(minutes=5.5)
async def end_check(self):
await self.client.wait_until_ready()
start = time.time()
mydb = await getConnection()
mycursor = await mydb.cursor()
current = datetime.now().timestamp()
current = str(current).split(".")
# get the main giveaway-data
await mycursor.execute("SELECT guild_id, channel_id, message_id, gw_req FROM guild_giveaways WHERE end_date < %s", (current[0],))
in_database = mycursor.fetchall()
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
emb = message.embeds[0].description.split("**")
creator_id = emb[7].replace("<#", "").replace(">", "").replace("!", "")
count = 0
gwrole = None
users = []
async for user in message.reactions[0].users():
if guild.get_member(int(user.id)) is None:
continue
if user.bot:
continue
# check if a user has the role/s from the database
bypass_status = False
await mycursor.execute("SELECT bypass_role_id FROM guild_role_settings WHERE guild_id = %s AND bypass_role_id IS NOT NULL", (guild.id,))
role_exist = await mycursor.fetchone()
if role_exist:
rolelist = role_exist[0].split(" ")
for role1 in rolelist:
role = guild.get_role(int(role1))
if role in user.roles:
bypass_status = True
break
if "no_nitro" in entry[3].lower():
if user.avatar_url is not None and bypass_status is False:
if "gif" in str(user.avatar_url):
continue
if user.premium_since is not None and bypass_status is False:
continue
elif "msg" in entry[3].lower():
msg = entry[3].replace("MSG: ", "")
# get the required message count to participate
await mycursor.execute("SELECT message_count FROM guild_message_count WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
data = await mycursor.fetchone()
if data:
if int(data[0]) < int(msg) and bypass_status is False:
continue
else:
if bypass_status is False:
continue
elif "voicetime" in entry[3].lower():
seconds = entry[3].replace("VOICETIME: ", "")
# get the right voice_time to participate
await mycursor.execute("SELECT voice_time FROM guild_voice_time WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
data = await mycursor.fetchone()
if data:
if int(data[0]) < int(seconds) and bypass_status is False:
continue
else:
if bypass_status is False:
continue
elif "role_id" in entry[3].lower():
roleid = entry[3].replace("ROLE_ID: ", "")
role = guild.get_role(int(roleid))
if role not in user.roles and bypass_status is False:
continue
elif "mitglied" in entry[3].lower():
reqtime = entry[3].replace("MITGLIED:", "")
if time.time() - user.joined_at.timestamp() < int(reqtime) and bypass_status is False:
continue
if int(user.id) == int(creator_id):
continue
await mycursor.execute("SELECT ignore_role_id FROM guild_role_settings WHERE guild_id = %s", (guild.id,))
find_data = await mycursor.fetchone()
if find_data:
if find_data[0] is not None and len(find_data[0]) >= 3:
rolelist = find_data[0].split(" ")
for role1 in rolelist:
role = guild.get_role(int(role1))
if role in user.roles:
continue
users.append(user)
count += 1
if int(count) < int(emb[5]):
winners = random.sample(users, k=int(count))
if count <= 0:
await mycursor.close()
mydb.close()
return await message.reply(f"`📛` › **Zu wenig Teilnehmer:** Ich konnte nur `{count}` Gewinner ziehen, {emb[7]}! <:AmongUs:774306215848181760>")
zuwenig = True
else:
zuwenig = False
winners = random.sample(users, k=int(emb[5]))
status = True
# check if server bot private messages are enabled
await mycursor.execute("SELECT dm_status FROM guild_misc_settings WHERE guild_id = %s", (entry[0],))
myresult = await mycursor.fetchone()
if myresult:
if myresult[0] == "False":
status = False
role_status = False
# check if the winner should receive a role
await mycursor.execute("SELECT win_role_id FROM guild_role_settings WHERE guild_id = %s", (entry[0],))
myresult = await mycursor.fetchone()
if myresult:
if myresult[0] is not None:
gwrole = guild.get_role(int(myresult[0]))
if gwrole is not None:
role_status = True
for winner in winners:
if status is True:
try:
done = discord.Embed(title="<a:COOL:805075050368598036> › **GEWINNSPIEL GEWONNEN!** <a:COOL:805075050368598036>",
description="`🤖` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
f"<a:gift:843914342835421185> › Du hast bei dem Gewinnspiel auf **[{guild.name}]({message.jump_url})** gewonnen!\n"
f"<a:love:855117868256198767> › Ein Teammitglied wird sich **demnächst** bei dir melden.",
color=0x778beb)
done.set_image(url="https://i.imgur.com/fBsIE3R.png")
await winner.send(content="Du hast bei einem Gewinnspiel **GEWONNEN!!** <a:blobbeers:862780904112128051>", embed=done)
except discord.Forbidden:
pass
if role_status is True:
try:
await winner.add_roles(gwrole, reason="Gewinnspiel gewonnen!")
except discord.Forbidden:
pass
database_winners = " ".join([str(winner.id) for winner in winners])
winners = ", ".join([winner.mention for winner in winners])
if winners.count(",") >= 1:
winnersdesc = f"{winners} haben **{message.embeds[0].title}** gewonnen! <a:love:855117868256198767>"
else:
winnersdesc = f"{winners} hat **{message.embeds[0].title}** gewonnen! <a:love:855117868256198767>"
embed = discord.Embed(title=message.embeds[0].title,
description="`🤖` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
"<a:trophy:867917461377404949> **__Gewinnspiel - Gewinner__**\n"
f"<:arrow2:868989719319564359> Gewinner: {winners}\n"
f"<:arrow2:868989719319564359> Erstellt von {emb[7]}\n⠀⠀",
color=0xff4d4d)
embed.set_footer(text=f"{self.client.user.name} - Bot", icon_url=str(self.client.user.avatar_url))
embed.timestamp = datetime.now()
embed.set_thumbnail(url=message.embeds[0].thumbnail.url)
if zuwenig is False:
await message.edit(content=":name_badge: **GEWINNSPIEL VORBEI!** :name_badge:", embed=embed)
else:
await message.edit(content=f"`📛` › **Zu wenig Teilnehmer:** Ich konnte nur `{count}` Gewinner ziehen! <:whut:848347703217487912>", embed=embed)
await mycursor.execute("INSERT INTO guild_finished_giveaways (guild_id, channel_id, message_id, winner_id) VALUES (%s, %s, %s, %s)", (entry[0], entry[1], entry[2], database_winners))
await mycursor.execute("SELECT COUNT(*) FROM guild_finished_giveaways WHERE guild_id = %s", (entry[0],))
gcount = await mycursor.fetchone()
count2 = '{:,}'.format(int(gcount[0])).replace(",", ".")
count1 = '{:,}'.format(int(count)).replace(',', '.')
await message.reply(content=f"<a:blobbeers:862780904112128051> **Herzlichen Glückwunsch**, {winnersdesc}\n"
f"› Es gab `{count1}` **gültige** Teilnehmer. Dieses Gewinnspiel war das `{count2}`. auf dem Server. <a:PETTHEPEEPO:772189322392371201>")
if status is True:
try:
done = discord.Embed(
title="<a:Info:810178313733013504> › **GEWINNSPIEL VORBEI!**",
description="`🤖` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
f"`✅` › Das Gewinnspiel auf **[{guild.name}]({message.jump_url})** ist vorbei!\n"
f"`📌` › Da du das Event gestartet hast, habe ich **dich informiert.**\n\n"
f"`💸` › **Zahle das Gewinnspiel** selbst aus, oder kümmere dich\n"
f"`💸` › darum, dass es die zuständige Person erledigt.",
color=0xffa502)
done.set_image(url="https://i.imgur.com/fBsIE3R.png")
creator = guild.get_member(int(creator_id))
await creator.send(content="Ein Gewinnspiel ist **VORBEI!**", embed=done)
except discord.Forbidden:
pass
await mycursor.execute("DELETE FROM guild_giveaways WHERE guild_id = %s AND channel_id = %s AND message_id = %s", (entry[0], entry[1], entry[2]))
await mydb.commit()
mydb.close()
await mycursor.close()
print(f"EndTask: {time.time() - start}")
Your most significant problem is that you are using a blocking method -- cursor.execute() -- in your asynchronous code. This is something you shouldn't do, because then your program cannot do anything else while it waits on the results of the query.
In async code, the idea is that every long operation is abstracted away as an awaitable. The function executes until it hits an await keyword. When that happens, execution of this function is suspended until the result of the awaitable becomes available -- but crucially, it frees up the "event loop" to work on something else in the meantime.
For your program, that would mean that while waiting for the result from your database, you could do useful things like listen for new messages. Or send heartbeat packets, because if you don't do it, then Discord will disconnect your bot, and you'll have to wait until you get connected back.
To avoid this kind of blocking, you need to use something that turns a blocking call into an asynchronous call. Some of these are asyncio.to_thread and asyncio.loop.run_in_executor, the latter of which is considered low-level. Here's an example of the former:
import asyncio
async def slow_insert(value):
""" Bad example, do not do this! """
# these methods are fast, so they are not an issue
mydb = getConnection()
mycursor = mydb.cursor()
# but this hangs the event loop, and prevents the program from doing anything else
result = mycursor.execute("INSERT INTO data VALUES (%s)", value)
return
async def async_insert(value):
""" Do this instead """
# these methods are fast, so they can be done directly
mydb = getConnection()
mycursor = mydb.cursor()
# then we create a function that does what we need
# note that this does not execute the blocking function, it just wraps it into another function
blocking_insert = lambda: mycursor.execute("INSERT INTO data VALUES (%s)", value)
# we schedule this function to run on a thread, and only come back here once that thread has completed
result = await asyncio.to_thread(blocking_insert)
return result
Ideally, of course, you would use a library that allows you to access your database in an async way, rather than wrapping every call like this. There is in fact such a library for MySQL/MariaDB called aiomysql.
None of this would make your program run faster on its own, but it will make it so that any slowness that happens doesn't cause Discord to drop the connection, and it doesn't prevent your bot from responding to messages. To actually make it faster, you have to optimize the queries.
In general, fewer, complicated queries are better than more, simpler queries -- the database server is smart enough to be able to optimize more complicated queries. In your code, there are a lot of simple queries like this:
SELECT ignore_role_id FROM guild_role_settings WHERE guild_id = %s
SELECT bypass_role_id FROM guild_role_settings WHERE guild_id = %s AND bypass_role_id IS NOT NULL
SELECT dm_status FROM guild_misc_settings WHERE guild_id = %s
SELECT win_role_id FROM guild_role_settings WHERE guild_id = %s
Notice how all these queries only depend on group_id. This means that they don't even have to be inside the loop at all, and they can be moved outside it. Perhaps more importantly, you can join them all into one query:
-- SETUP
CREATE TABLE IF NOT EXISTS guild_role_settings (guild_id INTEGER, ignore_role_id INTEGER, bypass_role_id INTEGER, win_role_id INTEGER);
CREATE TABLE IF NOT EXISTS guild_misc_settings (guild_id INTEGER, dm_status BOOLEAN);
CREATE TABLE IF NOT EXISTS some_new_table (guild_id INTEGER, some_new_property INTEGER);
INSERT INTO guild_role_settings VALUES (1, 2, 3, 4);
INSERT INTO guild_role_settings VALUES (2, 0, 0, 0);
INSERT INTO guild_misc_settings VALUES (1, 1);
INSERT INTO guild_misc_settings VALUES (2, 0);
INSERT INTO some_new_table VALUES (1, 1337);
INSERT INTO some_new_table VALUES (2, 0);
-- END SETUP
SELECT
guild_role_settings.ignore_role_id,
guild_role_settings.bypass_role_id,
guild_role_settings.win_role_id,
guild_misc_settings.dm_status,
some_new_table.some_new_property
FROM
guild_misc_settings NATURAL JOIN guild_role_settings NATURAL JOIN some_new_table
WHERE guild_id=1;
Try it online! Also, if you're not clear on what NATURAL JOIN does, take a look at the Wikipedia page with examples. In short, this allows you to perform queries across different tables by considering them as a single table, and querying it as such.
In the case of the queries that include the member ID, you should also combine the two queries into one, but it would probably be better to restructure that part of the code entirely, by first fetching a list of all the members, then performing a query on all of them together, and then doing things with the result of that query:
# instead of this:
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
async for user in message.reactions[0].users():
await mycursor.execute("SELECT guild_message_count.message_count, guild_voice_time.voice_time FROM guild_message_count NATURAL JOIN guild_message_count WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
msg_count, voice_time = await mycursor.fetchone()
# do something...
# do this instead:
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
user_objects = dict()
user_ids = []
async for user in message.reactions[0].users():
user_ids.append(user.id)
user_objects[user.id] = user
# now we have the list of user IDs, as well as their corresponding objects
# we will now form our query of the form
# SELECT ... WHERE user_id IN (%s, %s, %s, %s)
# to query all the user IDs at once
# the idea comes from https://stackoverflow.com/a/283801/5936187
# note that while manipulating SQL strings is usually dangerous
# because of SQL injections, here we are only using the length
# of the list as a parameter, so it is okay.
query = "SELECT user_id, guild_message_count.message_count, guild_voice_time.voice_time FROM guild_message_count NATURAL JOIN guild_message_count WHERE guild_id = %s AND user_id IN "
parameters = ["%s" for _ in user_ids]
parameter_string = "(" + ( ", ".join(parameters) ) + ")"
query += parameter_string
await mycursor.execute(query, [guild.id] + user_ids)
# now the cursor has the resultset of (user_id, msg_count, voice_time)
async for user_id, msg_count, voice_time in mycursor:
# do something...
This way, you're doing a large query at the start of your function, rather than doing small queries on every iteration.
There might be other optimizations you can do besides these, but so far it isn't very obvious what they could be. Even so, they would be SQL optimizations rather than Python code optimizations, so they would probably be better suited to be put into another question.
I see you have a lot of quarry's in the your loop that's the reason why its so slow. cant you make one big quarry? your mysql database will be quicker in sorting data of python.
maybe some hints to begin:
i see you sort out non nitro account but i dont see it in your first quarry.
i see you select all messages, maybe only take the messages after the start of the give away? so your script doesn't have to go through all the data.
almost all if statements (about sorting data) can be written in a quarry's
im not that good whit databases myself, but if i can see your database and specifically know what you want from the query (i suspect a list of possible winners), maybe i can help. and how long takes this script right now?

How to setup on_message with sqlite3 in discord.py?

Hi I created the following event. It takes input one time after that it stores the guild id , channel id and role id in sqlite db . After that when someone in that particular guild mentions minimum 3 users in a particular channel , the bot gives them new role.
class ScrimsCog(commands.Cog, name='Scrims-Commands') :
def __init__(self,bot):
self.bot = bot
#commands.Cog.listener()
async def on_message(self, message):
if message.guild:
db = sqlite3.connect('main.sqlite')
cursor = db.cursor()
cursor.execute(
f"SELECT * FROM main WHERE guild_id = ?", (message.guild.id, ))
result = cursor.fetchone()
if result:
channel = self.bot.get_channel(result[2])
role = message.guild.get_role(result[1])
if role:
if message.channel == channel:
if len(message.mentions) >= 3:
await message.add_reaction(emoji="<a:tick:748476262640779276>")
user = message.author
await user.add_roles(role)
await self.bot.process_commands(message)
#commands.group(invoke_without_command=True)
async def scrimsmod(self,ctx):
await ctx.send('Available Setup Commands: \nscrimsmod channel <#channel>\nscrimsmod role <message>')
#scrimsmod.command()
async def channel(self, ctx, channel:discord.TextChannel):
if ctx.message.author.guild_permissions.manage_messages:
db = sqlite3.connect('main.sqlite')
cursor = db.cursor()
cursor.execute(f"SELECT channel_id FROM main WHERE guild_id = {ctx.guild.id}")
result = cursor.fetchone()
if result is None:
sql = ("INSERT INTO main(guild_id, channel_id) VALUES(?,?)")
val = (ctx.guild.id, channel.id)
await ctx.send(f" Default Registration Channel has been set to {channel.mention}")
elif result is not None:
sql = ("UPDATE main SET channel_id = ? WHERE guild_id = ?")
val = (channel.id, ctx.guild.id)
await ctx.send(f"Default Registration Channel has been updated to {channel.mention}")
cursor.execute(sql, val)
db.commit()
cursor.close()
db.close()
#scrimsmod.command()
async def role(self, ctx,role: discord.Role):
if ctx.message.author.guild_permissions.manage_messages:
db = sqlite3.connect('main.sqlite')
cursor = db.cursor()
cursor.execute(f"SELECT role FROM main WHERE guild_id = {ctx.guild.id}")
result = cursor.fetchone()
if result is None:
sql = ("INSERT INTO main(guild_id, role) VALUES(?,?)")
val = (ctx.guild.id, role.id)
await ctx.send(f"Default role to give on correct registration have been set to `{role}`")
elif result is not None:
sql = ("UPDATE main SET role = ? WHERE guild_id = ?")
val = (role.id, ctx.guild.id)
await ctx.send(f"Default role to give on correct registration have been updated to `{role}`")
cursor.execute(sql, val)
db.commit()
cursor.close()
db.close()
Well, I believe the code is fine it doesn't throw any error . The issue is that it perfectly take input without any error but doesn't store it into db.
I believe the problem is with my db,
code -
db = sqlite3.connect('main.sqlite',timeout=10)
cursor = db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS main(
guild_id INTEGER,
role INTEGER,
channel_id INTEGER
)
''')
I believe the problem is here , I should take TEXT in place INTEGER but I am not sure. Answer me with What do you I am doing wrong and how should I fix that.
I answered your last question with assuming that role and channel is TEXT type in the table and that answer should've solve your problem. If you change them, it will work fine and also it will be more efficient if you define the database variable and cursor on top of the on_message event just for once. So you don't have do connect to the database everytime.

Why will this not send to the channel?

So, I want to send the warn to the user-set log channel, but it says NoneType has no attribute send. But, thing is
it's CLEARLY defined!
EDIT: I found out why. It's selecting the first query it gets from the database, so if the server_id from a different server's table is above another table, it won't work, because you're not in that server
cursor.execute("SELECT channel_id FROM logchannel")
result1 = cursor.fetchone()
if result1 is not None:
channel_id = result1[0]
channel1 = discord.utils.get(server.channels, id=channel_id)
await ctx.send(f"{member.mention} was warned for {reason}")
channel_id = result1[0]
channel1 = discord.utils.get(server.channels, id=channel_id)
await channel1.send(f"{member.mention} has been warned by {user.mention} for {reason}.")
cursor.execute('SELECT warnings FROM warninglog WHERE user_id = %s AND guild_id = %s', (user_id, guild_id))
result = cursor.fetchone()
if result is None:
warnings = 1
cursor.execute('INSERT INTO warninglog (warnings, name, user_id, guild_id ) VALUES (%s, %s, %s, %s)',
(warnings, name, user_id, guild_id))
conn.commit()

Categories