SQLAlchemy compiled statement without percent signs conditionally escaped - sqlalchemy

Is there a way to obtain the compiled SQLAlchemy statement without having the percentage signs conditionally escaped?
The following MySQL example:
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects import mysql
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import select
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer(), primary_key=True)
name = Column(String(80), unique=True)
statement = select([User.name]).where(User.name.like('bob%')).limit(10)
query = str(statement.compile(dialect=mysql.dialect(), compile_kwargs={'literal_binds': True}))
print(query)
generates a compiled statement with the percent sign escaped:
SELECT users.name
FROM users
WHERE users.name LIKE 'bob%%'
LIMIT 10
rather than:
SELECT users.name
FROM users
WHERE users.name LIKE 'bob%'
LIMIT 10
I gather this is to ensure that the string can correctly be formatted using the MySQL pyformat parameter style. Is the correct logic for obtaining the later statement simply:
print(query % {})

Wouldn't it be easier to just the .startswith() ColumOperator to create your like by doing
statement = select([User.name]).where(User.name.startswith('bob')).limit(10)

Related

joinedload and load_only but with filtering

I have two models with a simple FK relationship, Stock and Restriction (Restriction.stock_id FK to Stock).
class Restriction(Model):
__tablename__ = "restrictions"
id = db.Column(db.Integer, primary_key=True)
stock_id = FK("stocks.id", nullable=True)
name = db.Column(db.String(50), nullable=False)
class Stock(Model):
__tablename__ = "stocks"
id = db.Column(db.Integer, primary_key=True)
ticker = db.Column(db.String(50), nullable=False, index=True)
I would like to retrieve Restriction object and related Stock but only Stock's ticker (there are other fields omitted here). I can simply do this with:
from sqlalchemy.orm import *
my_query = Restriction.query.options(
joinedload(Restriction.stock).load_only(Stock.ticker)
)
r = my_query.first()
I get all columns for Restriction and only ticker for Stocks with above. I can see this in the SQL query run and also I can access r.stock.ticker and see no new queries run as it is loaded eagerly.
The problem is I cannot filter on stocks now, SQLAlchemy adds another FROM clause if I do my_query.filter(Stock.ticker == 'GME'). This means there is a cross product and it is not what I want.
On the other hand, I cannot load selected columns from relationship using join. ie.
Restriction.query.join(Restriction.stock).options(load_only(Restriction.stock))
does not seem to work with relationship property. How can I have a single filter and have it load selected columns from relationship table?
SQL I want run is:
SELECT restrictions.*, stocks.ticker
FROM restrictions LEFT OUTER JOIN stocks ON stocks.id = restrictions.stock_id
WHERE stocks.ticker = 'GME'
And I'd like to get a Restriction object back with its stock and stock's ticker. Is this possible?
joinedload basically should not be used with filter. You probably need to take contains_eager option.
from sqlalchemy.orm import *
my_query = Restriction.query.join(Restriction.stock).options(
contains_eager(Restriction.stock).load_only(Stock.ticker)
).filter(Stock.ticker == 'GME')
r = my_query.first()
Because you are joining using stock_id it will also be in the results as Stock.id beside Stock.ticker. But other fields would be omitted as you wish.
I have written short post about it recently if you are interested: https://jorzel.hashnode.dev/an-orm-can-bite-you

peewee using 't1' as table and not my table

I'm new to peewee and can't seem to figure out how to correctly send a query.
Here's my Meta:
class Meta:
database = db
db_table = 'profile'
To my understanding, I told peewee to use the table 'profile'
But when I try to select from the table with:
Profile.get(Profile.name == user)
I always get an error that is referring to the table 't1' and not my table 'profile'
How do I tell peewee to use a specific table and NOT t1?
Peewee uses aliases when constructing queries, so although it's referring to "t1", the query itself will probably look like:
SELECT * FROM "profile" AS "t1" WHERE "t1"."name" = '<whatever>';

expanding the SQL query inside managers in Django models?

Here is the code from django docs that explains the use of managers.
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
SELECT p.id, p.question, p.poll_date, COUNT(*)
FROM polls_opinionpoll p, polls_response r
WHERE p.id = r.poll_id
GROUP BY p.id, p.question, p.poll_date
ORDER BY p.poll_date DESC""")
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], question=row[1], poll_date=row[2])
p.num_responses = row[3]
result_list.append(p)
return result_list
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
poll_date = models.DateField()
objects = PollManager()
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll)
person_name = models.CharField(max_length=50)
response = models.TextField()
I have two questions based on this code:
1) where is r.poll_id coming from? I understand Response has foreignKey relationship to OpinionPoll. In order to JOIN OpinionPoll table with Response table, I need to join on their id.
HOwever to access the poll id in Response, I would do r.poll.id.
Is the syntax, r.poll_id, a MySQL syntax.
why GROUP BY p.id, p.question, p.poll_date? why GROUP BY p.id alone is not sufficient?
2) Is it possible to turn the above raw SQL query into a django ORM query?If so how would that look like?
I am not a SQL guy. so bear with me, if this sounds stupid
EDIT:
If I want to create OpinionPoll and Response tables outside of Django, how will SQL statment for create look like?
In the Django shell, when I run
python manage.py sqlall appname
I get the following:
BEGIN;
CREATE TABLE "myapp_opinionpoll" (
"id" integer NOT NULL PRIMARY KEY,
"question" varchar(200) NOT NULL,
"poll_date" date NOT NULL
)
;
CREATE TABLE "myapp_response" (
"id" integer NOT NULL PRIMARY KEY,
"poll_id" integer NOT NULL REFERENCES "myapp_opinionpoll" ("id"),
"person_name" varchar(50) NOT NULL,
"response" text NOT NULL
)
;
CREATE INDEX "larb_response_70f78e6b" ON "myapp_response" ("poll_id");
COMMIT;
I see something like REFERENCES "myapp_opinionpoll" and CREATE INDEXabove. I am not sure
if this is how in SQL it is done?
[1] Django model will create foreign keys like fieldname_id as the field in mysql. So you see the field poll = models.ForeignKey(OpinionPoll) creates this field.
About GROUP BY, because these fields are exactly what selected, except for the aggregate function, grouping them exactly can make them distinct.
[2] Try this, I didn't debug, but may helps:
from django.db.models import Count
OpinionPoll.objects.annotate(num_responses=Count('response'))
For more about aggregation, see the docs: https://docs.djangoproject.com/en/1.6/topics/db/aggregation/

SQLAlchemy query returning results with values outside filter criteria

I'm using SQLAlchemy to access an SQLite db. I have populated the database with 50,000 entries as defined by the Event ORM below. When I query the database and filter for 'time' greater than some value, I consistently get results with time values before the requested time.
How do I craft the query such that if properly filters based on time?
Here is my query code:
query = session.query(Event)
query = query.filter(Event.time > starttime)
Here is the Event ORM
Base = declarative_base()
class Event (Base):
__tablename__ = 'events'
eventid = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
time = sqlalchemy.Column(sqlalchemy.Integer)
value = sqlalchemy.Column(sqlalchemy.Float)
#end class
Your model Event.time. is an Integer Column if you want to filter by a date time object you have to change the column of use datetime object to filter.
from sqlalchemy import func
from dateutil import tz
time = Column(DateTime(timezone=True), nullable=False,
server_default=func.now())
starttime = datetime.datetime.now(tz=tz.tzlocal()) # local timezone
query = session.query(Event).filter(Event.time > starttime)

SQLALchemy filter_by() on a foreign key producting weird sql

I'm attempting to filter on a foreign key and none of the SO answers I've searched for have lent any results.
Where are my query statements.
testing = Comments\
.filter(Comments.post_id==post_id)
print(testing)
testing = Comments\
.query.join(Post, aliased=True)\
.filter(Comments.post_id==post_id)
print(testing)
Here's what my class definitions looks like
class Comments(db.Model):
comment_id = db.Column(db.Integer, primary_key=True)
post_id = db.Column(
db.Integer,
db.ForeignKey("Post.post_id"),
nullable=False)
class post(db.Model):
post_id = db.Column(db.Integer, primary_key=True)
Comments = db.relationship(
'Comments',
backref='Post',
lazy='dynamic')
The actual SQL queries which are being produced from the first and second case. They both have this weird :post_id_1 thing. In both cases I'm getting a null set back.
FROM "Comments"
WHERE "Comments".post_id = :post_id_1
FROM "Comments" JOIN "post" AS "post_1" ON "post_1".post_id = "Comments".post_id
WHERE "Comments".post_id = :post_id_1
If I do a simple
Select * from Comments where post_id = 1
in the mysql CLI I get a result set.
Your model definition is weird, the following part is not correctly indented:
Comments = db.relationship(
'Comments',
backref='Post',
lazy='dynamic')
Or maybe it's just a copy/paste issue (just to be sure).
What you call "weird :esc_id_1 thing" is in fact an named placeholder. They will be replaced by the real value when the SQL statement will be executed (this is mainly to avoid SQL injection, the driver is responsible to escape values).