Pony ORM JOIN синтаксис

Я начал использовать Pony и еще не понял, как использовать соединения. В примерах, которые я видел, когда left_join() использовался с двумя предложениями for, но когда я пытался повторить это в своем коде, я получаю сообщение об ошибке типа "коллекция ожидалась, получена" для p в пруду "" Возможно, кто-то может объяснить, как использовать это или указать мне страницу документации, где это уже объяснено?

1 ответ

Решение

Допустим, у нас есть следующие объекты:

from pony import orm

db = orm.Database()

class Person(db.Entity):
    id = orm.PrimaryKey(int, auto=True)
    name = orm.Required(str)
    age = orm.Required(int)
    contacts = orm.Set('Contact')

class Contact(db.Entity):
    id = orm.PrimaryKey(int, auto=True)
    person = orm.Required('Person')
    type = orm.Required(str)
    value = orm.Required(str)

db.generate_mapping(create_tables=True)

with orm.db_session:
    john = Person(name='John', age=23)
    mary = Person(name='Mary', age=21)
    mike = Person(name='Mike', age=18)
    arthur = Person(name='Arthur', age=25)

    john.contacts.create(type='mobile', value='1234567')
    john.contacts.create(type='email', value='john@example.com')

    mary.contacts.create(type='mobile', value='76543321')
    mary.contacts.create(type='skype', value='mary123')

    mike.contacts.create(type='mobile', value='2345678')

Теперь мы хотим напечатать имя человека и контактную информацию для каждого человека старше 20 лет. Есть несколько способов сделать это.


Первый способ - это когда мы явно указываем условие соединения. Этот способ довольно многословен:

query = orm.select(
    (p.name, c.value)
    for p in Person for c in Contact
    if p.age > 20 and c.person == p
)
query.show()

В этом запросе мы явно указываем условие соединения: c.person == p, Запрос покажет нам следующий результат:

p.name|c.type|c.value
------+------+----------------
John  |email |john@example.com
John  |mobile|1234567
Mary  |mobile|76543321
Mary  |skype |mary123

Как видите, Артур не был включен в результат, хотя его возраст превышает 20 лет. Это связано с тем, что этот тип объединения является внутренним объединением, а результат включает только лиц, для которых удалось найти хотя бы один контакт.


Второй способ объединения - это когда мы зацикливаемся на атрибуте коллекции:

query = orm.select(
    (p.name, c.value)
    for p in Person for c in p.contacts
    if p.age > 20
)
query.show()

Этот тип соединений используется чаще всего. Это очень удобно, потому что нам не нужно явно указывать условие соединения. Результат запроса такой же, как и раньше:

p.name|c.type|c.value
------+------+----------------
John  |email |john@example.com
John  |mobile|1234567
Mary  |mobile|76543321
Mary  |skype |mary123

Артура до сих пор нет в списке по той же причине, что и раньше. Если мы хотим включить Артура в результат, нам нужно использовать другой тип объединения, а именно, левое соединение:

query = orm.left_join(
    (p.name, c.value)
    for p in Person for c in p.contacts
    if p.age > 20
)
query.show()

В этом случае результат запроса включает Артура со значением None вместо номера телефона:

p.name|c.type|c.value
------+------+----------------
Arthur|None  |None
John  |email |john@example.com
John  |mobile|1234567
Mary  |mobile|76543321
Mary  |skype |mary123

Когда вы используете left_join вам нужно перебрать коллекцию. В этом случае Pony добавляет условие соединения в ON раздел LEFT JOIN пункт команды SQL.

Вы не можете сделать явное объединение, как в самом первом запросе, если вы используете left_joinпотому что в этом случае Пони не знает, какое условие нужно поставить в ON раздел LEFT JOIN пункт.

Иногда может быть полезно указать содержание ON раздел вручную. В настоящее время Pony не поддерживает такие запросы, но эта функция может быть добавлена ​​в будущем.


При использовании PonyORM во многих случаях можно извлекать данные, не создавая вообще соединений. Например, вы можете написать следующий цикл для печати имени человека и номера телефона:

with db_session:
    for p in Person.select(lambda p: p.age > 20):
        print(p.name)
        for c in p.contacts:
            print(c.type, c.value)

В других ORM это приведет к проблеме "N+1 запрос", когда контакты каждого человека будут получены отдельным SQL-запросом. Пони пытается автоматически оптимизировать запросы, чтобы избежать шаблона "N+1 запрос".


В некоторых случаях объединения являются неявными. Например, чтобы найти все контакты человека, имя которого начинается с буквы "M", вы можете написать:

query = select(c for c in Contact if c.person.name.startswith('M'))
for c in query:
    print(c.person.name, c.type, c.value)

В этом случае таблица Person присоединяется неявно только потому, что вы выполняете обход атрибутов от контакта к человеку.

Другие вопросы по тегам