Я пытаюсь выяснить, как определить тип соединения в sqlalchemy ORM. Как использовать левое соединение и левое внешнее соединение? Как насчет внутреннего соединения?

Это для запроса, который может выбрать все crm_lead без связанной с ним crm_task. Я пробовал существующий фильтр, но не смог отфильтровать существующие crm_leads с этим условием.

Желаемый SQL:

select *
from crm_lead l
join crm_task t on l.id = t.lead_id
left outer join crm_pipeline_status cps on l.pipeline_status_id = cps.id
where l.pipeline_status_id not in (142, 143) 
  and (t.id is null or t.is_completed is false);

ИЛИ: (если существует предложение, лучше для этого случая)

select *
from crm_lead l
left outer join crm_pipeline_status cps on l.pipeline_status_id = cps.id
where cps.crm_id not in (142, 143)
  and not exists (select id from crm_task t where l.id = t.lead_id and t.is_completed is false);

Моя лучшая попытка была:

session = sessionmaker(bind=engine, autocommit=True)()
with session.begin():
    leads = session.query(CrmLead).outerjoin(CrmTask).outerjoin(CrmPipelineStatus).filter(
        and_(CrmLead.account_id == 2,
        CrmPipelineStatus.crm_id not in (142, 143),
        or_(CrmTask.is_completed is False, CrmTask.id is None))
    )

Но это превращается в:

SELECT *
FROM crm_lead 
LEFT OUTER JOIN crm_task ON crm_lead.id = crm_task.lead_id 
LEFT OUTER JOIN crm_pipeline_status ON crm_pipeline_status.id = crm_lead.pipeline_status_id 
WHERE false

АЛЬТЕРНАТИВНОЕ РЕШЕНИЕ: Мой случай может быть решен с помощью необработанного SQL, как показано здесь [ https://stackoverflow.com/a/22084672/2822537]

Примере:

    query_text = '''
    select *
    from crm_lead l
    left outer join crm_pipeline_status cps on l.pipeline_status_id = cps.id
    where cps.crm_id not in (:success_final_status, :failed_final_status)
      and l.account_id = :account_id
      and not exists (select id from crm_task t where l.id = t.lead_id and t.is_completed is false);
    '''
    leads = session.execute(query_text, {
        'account_id': crm_configuration["instance_id"],
        'success_final_status': 142,
        'failed_final_status': 143
    })
1
Sviatoslav Pikh 26 Июн 2019 в 16:35

3 ответа

Лучший ответ

Выражение

CrmPipelineStatus.crm_id not in (142, 143)

Оценивается как False или True в Python, поскольку __contains__() нельзя использовать так же, как некоторые другие перегрузки. Если это False, вся включающая конструкция and_() компилируется в простой false. В этом случае правильным способом является использование метода notin_():

CrmPipelineStatus.crm_id.notin_([142, 143])

Возникает несколько похожая проблема

or_(CrmTask.is_completed is False, CrmTask.id is None)

Как оператор идентификации is не может быть перегружен, и поэтому вы эффективно

or_(False, False)

Который снова компилируется в false. Для первого вы должны просто использовать логическое значение в качестве логического (учитывая, что это NOT NULL), а проверку NULL можно записать с использованием метода is_() или специального случая в операторе перегрузки :

or_(not_(CrmTask.is_completed), CrmTask.id == None)
1
Ilja Everilä 8 Июл 2019 в 10:04

Посмотрите, может быть, этот пост

q = session.query(Table1.field1, Table1.field2)\
.outerjoin(Table2)\ # use in case you have relationship defined
# .outerjoin(Table2, Table1.id == Table2.table_id)\ # use if you do not have relationship defined
.filter(Table2.tbl2_id == None)

Должен сделать это, предполагая, что field1 и field2 взяты из Table1, и что вы определяете отношение:

class Table2(Base):
# ...
table1 = relationship(Table1, backref="table2s")
1
Tom Leboeuf 26 Июн 2019 в 13:47

CamelCase используется для сопоставленных классов имен таблиц в нижнем регистре:

from sqlalchemy.sql import exists

Session().query(CrmLead).join(CrmTask).outerjoin(CrmPipelineStatus).filter(CrmLead.pipeline_status_id == CrmPipelineStatus.id).filter(CrmPipelineStatus.crm_id.notin_([142, 143])).filter(~exists().where(and_(CrmTask.is_completed==False, CrmLead.id==CrmTask.lead_id)
0
ipaleka 26 Июн 2019 в 15:11