A couple of typos found re-reading my post, the Contribution.contact_id
with a foreign key definition should be `contact_id = Column(Integer,
ForeignKey('civicrm_contact'), nullable=False)`, I left out the closing
parenthesis to the ForeignKey constructor.
Also, the very last line, I didn't close the parenthesis for the
placeholders example, should read: "(e.g. `%(tag_id_2)s`) are passed...".
Couldn't find a way to edit the original.
On Thursday, 22 August 2019 10:43:17 UTC+10, Peter Schutt wrote:
>
> Hi Ira,
>
> For example Integer(xx) says that Integer cannot have parameters and
>> Tinyint seems not to exist.
>
>
> I'm aware of sqlacodegen, although never had the need to use it myself.
> Those issues sound to me like it is using the mysql dialect types, not the
> standard sqlalchemy types. For example `from sqlalchemy import Integer`
> will not accept an argument but `from sqlalchemy.dialects.mysql import
> INTEGER` does accept display_width as a parameter amongst others. Also,
> `from sqlalchemy import TinyInteger` will raise an import error but `from
> sqlalchemy.dialects.mysql import TINYINT` works.
>
> I presume that this front-end overhead will pay back when I write more
>> interesting scripts that use data from Civicrm. If this works then I can
>> map the remainder of the (many) tables.
>
>
> Well, that's certainly the idea. The abstraction allows for writing code
> that is both easier to write and read. Essentially you write a Python app
> and let sqlalchemy handle the bridge between your application and the
> database. It's not a magic bullet, there are plenty of sql paradigms still
> exposed to you through the ORM and there are limitations to using it,
> primarily performance relative to doing bulk operations (for example, see
> here
> <https://docs.sqlalchemy.org/en/13/faq/performance.html#i-m-inserting-400-000-rows-with-the-orm-and-it-s-really-slow>).
>
> Like any tool, you need to weigh up the pros and cons. For example, if you
> are just using the ORM in order to generate schema or construct raw
> queries, there are better ways. The benefit of the ORM comes once you start
> understanding and using the richer feature set. For example, relationships.
>
> In the models that you built above, I notice that you haven't defined any
> foreign keys on columns. I understand that your schema is already created
> and so the foreign keys are already defined at the database level, but that
> information is useful to sqlalchemy when you want to create relationships
> <https://docs.sqlalchemy.org/en/13/orm/tutorial.html#building-a-relationship>
> and
> explicit joins in your queries. One example of applying a foreign key to
> your columns would be on the `Contribution.contact_id` column. Defining
> that as a foreign key would be as simple as changing the definition to
> `contact_id = Column(Integer, ForeignKey('civicrm_contact',
> nullable=False)`. This would then allow you to define a relationship
> attribute on your Contribution model, e.g. `contact =
> relationship('Contact')`. That allows you to access the instance that
> represents the contact associated with a given contribution through
> instance attribute access, e.g. `contribution_instance.contact` would
> return an instance of `Contact` and sqlalchemy will issue the necessary
> queries behind the scenes to make that happen.
>
> Now, how to create the equivalent SQL query?
>
>
> Well, I've had to make a couple of guesses to fill in a couple of blanks.
> I assumed that the column in your query `state_province_id` should be an
> attribute on the `Address` model, and that the `display_name` column
> belongs to the `Entity_Tag` model, as neither of those fields are defined
> elsewhere. We haven't spoken about the Session in any detail yet either,
> but you can read
> https://docs.sqlalchemy.org/en/13/orm/tutorial.html#creating-a-session if
> you need. I've purely tried to emulate your original query as closely as
> possible.
>
> s = Session()
> subquery = (
> s.query(Entity_Tag.display_name)
> .filter(
> Contribution.receive_date > datetime.date(2005, 7, 1),
> Contribution.contact_id == Contact.id,
> Contact.id == Entity_Tag.entity_id,
> Entity_Tag.tag_id == 6,
> )
> .subquery()
> )
> result = (
> s.query(
> Contact.last_name,
> Contact.first_name,
> Address.street_address,
> Address.city,
> Address.postalcode,
> State.name,
> Country.name.label("country"),
> )
> .filter(
> Contact.id == Entity_Tag.entity_id,
> Entity_Tag.tag_id == 6,
> Contact.id == Address.contact_id,
> Address.state_province_id == State.id,
> Address.country_id == Country.id,
> Entity_Tag.display_name.notin_(subquery),
> )
> .distinct()
> .all()
> )
>
> This query issues this sql:
>
> SELECT DISTINCT civicrm_contact.last_name AS civicrm_contact_last_name,
> civicrm_contact.first_name AS civicrm_contact_first_name,
> civicrm_address.street_address
> AS civicrm_address_street_address, civicrm_address.city AS
> civicrm_address_city, civicrm_address.postalcode AS
> civicrm_address_postalcode, civicrm_state_province.name AS
> civicrm_state_province_name, civicrm_country.name AS country
> FROM civicrm_contact, civicrm_address, civicrm_state_province,
> civicrm_country, civicrm_entity_tag
> WHERE civicrm_contact.id = civicrm_entity_tag.entity_id AND
> civicrm_entity_tag.tag_id = %(tag_id_1)s AND civicrm_contact.id =
> civicrm_address.contact_id AND civicrm_address.state_province_id =
> civicrm_state_province.id AND civicrm_address.country_id = civicrm_country
> .id AND civicrm_entity_tag.display_name NOT IN (SELECT civicrm_entity_tag.
> display_name
> FROM civicrm_contribution
> WHERE civicrm_contribution.receive_date > %(receive_date_1)s AND
> civicrm_contribution.contact_id = civicrm_contact.id AND civicrm_contact.id
> = civicrm_entity_tag.entity_id AND civicrm_entity_tag.tag_id = %(tag_id_2)
> s)
>
> Notice that the `FROM` clause in the subquery only contains one column,
> this is becuase the other columns have been automatically correlated with
> the columns expressed in the enclosing query. See here:
> https://docs.sqlalchemy.org/en/13/core/tutorial.html#correlated-subqueries
> .
>
> The scalar values in the query that have been substituted with
> placeholders (e.g. `%(tag_id_2)s` are passed to the db driver in dictionary
> form along with the query, e.g. `{'tag_id_1': 6, 'receive_date_1':
> datetime.date(2005, 7, 1), 'tag_id_2': 6}`.
>
> On Wednesday, 21 August 2019 10:20:30 UTC+10, Ira Fuchs wrote:
>>
>> I noticed that some of the definitions created by sqlacodegen are
>> resulting in errors in python. For example Integer(xx) says that Integer
>> cannot have parameters and Tinyint seems not to exist. Perhaps this is a
>> result of my running a less than current version of mysql?
>>
>> On Tuesday, August 20, 2019 at 6:58:03 PM UTC-4, Ira Fuchs wrote:
>>>
>>> Just an aside: I discovered the sqlacodegen tool which will create the
>>> sqlalchemy class definitions automatically. I ran it against the civicrm
>>> mysql db and it worked. The definitions comprise 5881 lines (428KB).
>>> Fortunately I don't need much of it for my purposes.
>>>
>>> On Tuesday, August 20, 2019 at 6:15:46 PM UTC-4, Ira Fuchs wrote:
>>>>
>>>> I think I may have all the definitions:
>>>>
>>>> class Contact(Base):
>>>> __tablename__ = "civicrm_contact"
>>>> id = Column(Integer, primary_key=True)
>>>> last_name = Column(String(20), nullable=False)
>>>> first_name = Column(String(20), nullable=False)
>>>> class Contribution(Base):
>>>> __tablename__ = "civicrm_contribution"
>>>> id = Column(Integer, primary_key=True)
>>>> contact_id = Column(Integer, nullable=False)
>>>> receive_date = Column(DateTime,nullable=False)
>>>> total_amount = Column(DECIMAL(precision=20,scale=2), nullable=False)
>>>> class Address(Base):
>>>> __tablename__ = "civicrm_address"
>>>> id = Column(Integer, primary_key=True)
>>>> contact_id = Column(Integer, nullable=False)
>>>> street_address = Column(String(96), nullable=False)
>>>> city = Column(String(64), nullable=False)
>>>> postalcode = Column(String(64), nullable=False)
>>>> country_id = Column(Integer, nullable=False)
>>>> class Country(Base):
>>>> __tablename__ = "civicrm_country"
>>>> id = Column(Integer, primary_key=True)
>>>> name = Column(String(64))
>>>> class State(Base):
>>>> __tablename__ = "civicrm_state_province"
>>>> id = Column(Integer, primary_key=True)
>>>> name = Column(String(64), nullable=False)
>>>> abbreviation = Column(String(4), nullable=False)
>>>> country_id = Column(Integer, nullable=False)
>>>> class Entity_Tag(Base):
>>>> __tablename__ = "civicrm_entity_tag"
>>>> id = Column(Integer, primary_key=True)
>>>> entity_id = Column(Integer, nullable=False)
>>>> tag_id = Column(Integer, nullable=False)
>>>>
>>>> Now, how to create the equivalent SQL query? I presume that this
>>>> front-end overhead will pay back when I write more interesting scripts
>>>> that
>>>> use data from Civicrm. If this works then I can map the remainder of the
>>>> (many) tables.
>>>>
>>>> On Tuesday, August 20, 2019 at 5:15:36 PM UTC-4, Ira Fuchs wrote:
>>>>>
>>>>> OK, fixed the case:
>>>>>
>>>>> class Contact(Base):
>>>>> __tablename__ = "civicrm_contact"
>>>>> id = Column(Integer, primary_key=True)
>>>>> last_name = Column(String(20), nullable=False)
>>>>> first_name = Column(String(20), nullable=False)
>>>>> class Contribution(Base):
>>>>> __tablename__ = "civicrm_contribution"
>>>>> id = Column(Integer, primary_key=True)
>>>>> contact_id = Column(Integer, nullable=False)
>>>>> receive_date = Column(DateTime,nullable=False)
>>>>> total_amount = Column(DECIMAL(precision=20,scale=2),
>>>>> nullable=False)
>>>>>
>>>>> On Tuesday, August 20, 2019 at 3:59:55 PM UTC-4, Ira Fuchs wrote:
>>>>>>
>>>>>> OK, I made some progress (see screenshot). I don't need the full
>>>>>> model to be represented as I will not need many of the columns in each
>>>>>> of
>>>>>> the tables. If I could flesh this out enough so that I can do the query
>>>>>> posted above I can probably use that as a template to move forward.
>>>>>>
>>>>>> On Monday, August 19, 2019 at 7:11:59 AM UTC-4, Peter Schutt wrote:
>>>>>>>
>>>>>>> Cool, how about we walk through creating a model for your schema as
>>>>>>> an example. As we go I can point you to the relevant sections of the
>>>>>>> tutorials/docs as they are great and will explain the details much
>>>>>>> better
>>>>>>> than I can, and I'll try to address any questions you have along the
>>>>>>> way.
>>>>>>>
>>>>>>> The first thing you need is your Base class, all of your ORM models
>>>>>>> will inherit from this:
>>>>>>>
>>>>>>> from sqlalchemy.ext.declarative import declarative_base
>>>>>>>
>>>>>>> Base = declarative_base()
>>>>>>>
>>>>>>>
>>>>>>> Your models need to inherit from Base, need a __tablename__ class
>>>>>>> attribute and at least a primary key column. For example, a class to
>>>>>>> represent your "db_contact" table might look like this:
>>>>>>>
>>>>>>> from sqlalchemy import Column, Integer, String
>>>>>>>
>>>>>>> class Contact(Base):
>>>>>>>
>>>>>>> __tablename__ = "db_contact"
>>>>>>>
>>>>>>> id = Column(Integer, primary_key=True)
>>>>>>> last_name = Column(String(20), nullable=False)
>>>>>>> first_name = Column(String(20), nullable=False)
>>>>>>>
>>>>>>> Some things to note this far along:
>>>>>>>
>>>>>>> - In this example, the name of the Column in the database is
>>>>>>> inferred from the name of the class attribute that the Column is
>>>>>>> assigned
>>>>>>> to.
>>>>>>> - Column constructors take arguments that define specifics of
>>>>>>> how the column is defined in the database. E.g. `primary_key=True`,
>>>>>>> `nullable=False`. By declaring a single integer column as primary
>>>>>>> key, it
>>>>>>> will automatically be specified as autoincrement in the schema, as
>>>>>>> well as
>>>>>>> the primary key. Setting nullable=False on other columns declares
>>>>>>> that a
>>>>>>> NOT NULL constraint should be set on those columns.
>>>>>>> - As you are using MySQL, String types (which represent VARCHAR
>>>>>>> type in the database) require a length. If you were creating models
>>>>>>> for
>>>>>>> other backends such as SQLite or postgres, then that isn't required.
>>>>>>>
>>>>>>> Nothing that I've covered here isn't covered in the early stages of
>>>>>>> the ORM Tutorial
>>>>>>> <https://docs.sqlalchemy.org/en/13/orm/tutorial.html#declare-a-mapping>.
>>>>>>>
>>>>>>> Also, you can read the Column API
>>>>>>> <https://docs.sqlalchemy.org/en/13/core/metadata.html#sqlalchemy.schema.Column>
>>>>>>> to
>>>>>>> get a feel for the arguments that you can pass to Column in order to
>>>>>>> define
>>>>>>> your schema.
>>>>>>>
>>>>>>> One more important element is the Foreign Key. I'll use your
>>>>>>> "db_entity_tag" table to give you an example of that:
>>>>>>>
>>>>>>> from sqlalchemy import ForeignKey
>>>>>>>
>>>>>>> class EntityTag(Base):
>>>>>>>
>>>>>>> __tablename__ = "db_entity_tag"
>>>>>>>
>>>>>>> id = Column(Integer, primary_key=True)
>>>>>>> entity_id = Column(
>>>>>>> Integer,
>>>>>>> ForeignKey('db_contact.id', ondelete="CASCADE"),
>>>>>>> nullable=False,
>>>>>>> )
>>>>>>>
>>>>>>> Pass the name of the related column to the ForeignKey object (note
>>>>>>> that this isn't "Class.attributename", it is "tablename.columnname")
>>>>>>> and
>>>>>>> pass the ForeignKey object as a positional argument to the Column
>>>>>>> constructor _after_ the column type argument. ForeignKey api is
>>>>>>> documented
>>>>>>> here
>>>>>>> <https://docs.sqlalchemy.org/en/13/core/constraints.html#sqlalchemy.schema.ForeignKey>
>>>>>>> and
>>>>>>> touched on in the orm tutorial here
>>>>>>> <https://docs.sqlalchemy.org/en/13/orm/tutorial.html#building-a-relationship>
>>>>>>> .
>>>>>>>
>>>>>>> Have a go at filling in the columns that I've missed in those models
>>>>>>> above and defining the rest of the tables in your schema and once you
>>>>>>> get
>>>>>>> that done we can move on. Happy to try to answer any questions you
>>>>>>> might
>>>>>>> have along the way.
>>>>>>>
>>>>>>>
>>>>>>> On Monday, 19 August 2019 12:36:21 UTC+10, Ira Fuchs wrote:
>>>>>>>>
>>>>>>>> Thanks for your reply and offer to help. I am able to create an
>>>>>>>> Engine and connect to the MySQL db. I can execute simple sql queries
>>>>>>>> although I wasn't able to get the query I posted to work due to a
>>>>>>>> syntax
>>>>>>>> error (probably having to do with the quotes). I have not mapped any
>>>>>>>> tables
>>>>>>>> to classes.
>>>>>>>>
>>>>>>>> On Sunday, August 18, 2019 at 8:54:57 PM UTC-4, Peter Schutt wrote:
>>>>>>>>>
>>>>>>>>> Hi Ira, I'd be happy to help you find your feet with the
>>>>>>>>> SQLAlchemy ORM.
>>>>>>>>>
>>>>>>>>> In general when creating an application that uses the SQLAlchemy
>>>>>>>>> ORM, you would start with an Engine (for connecting to the db), a
>>>>>>>>> declarative base class (maps db table to python class) and a Session
>>>>>>>>> instance (for using a connection to issue queries). Do you have any
>>>>>>>>> familiarity with those concepts?
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Monday, 19 August 2019 02:34:05 UTC+10, Ira Fuchs wrote:
>>>>>>>>>>
>>>>>>>>>> I am new to sqlalchemy and I would like to begin using it to
>>>>>>>>>> create scripts with some sql queries that I have been using but need
>>>>>>>>>> to
>>>>>>>>>> change from time to time. It would help a lot if someone could
>>>>>>>>>> translate
>>>>>>>>>> one of these queries to python sqlalchemy using an ORM (not simply
>>>>>>>>>> executing the query as is) so that I could use this as a template
>>>>>>>>>> for other
>>>>>>>>>> queries, or at least learn from it.
>>>>>>>>>>
>>>>>>>>>> One of my queries looks like this:
>>>>>>>>>>
>>>>>>>>>> SELECT DISTINCT last_name, first_name, street_address, city,
>>>>>>>>>> a.name, postal_code, f.name as country FROM db_contact c,
>>>>>>>>>> db_entity_tag d , db_address e, db_state_province a, db_country f
>>>>>>>>>> WHERE
>>>>>>>>>> c.id = d.entity_id and tag_id = 6 and c.id = e.contact_id AND
>>>>>>>>>> state_province_id = a.id and e.country_id = f.id and
>>>>>>>>>> display_name not in ( SELECT display_name FROM db_contribution,
>>>>>>>>>> db_contact
>>>>>>>>>> c, db_entity_tag d WHERE receive_date > '2005-07-01' and contact_id
>>>>>>>>>> =
>>>>>>>>>> c.id and c.id = entity_id and tag_id = 6 )
>>>>>>>>>>
>>>>>>>>>> Thanks for any help.
>>>>>>>>>>
>>>>>>>>>>
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/sqlalchemy/e6cca0bd-5adc-41e1-80bb-e2bdbc7f4541%40googlegroups.com.