получить результаты, сгруппированные по одному значению поля в SQLAlchemy - PullRequest
0 голосов
/ 27 июня 2018

У меня есть 3 таблицы MySQL: companies, activities, association_company_activities association_company_activitiestable ссылки companies и activities, таким образом, у него есть 3 поля: 1 автоинкремент ID, company_id как первичный ключ и activity_id как первичный ключ. У меня есть этот запрос:

SELECT
    C.id,
    C.label,
    A.name
FROM
    companies C
JOIN activities A JOIN association_company_activities S ON
    C.identifier = S.company_id AND A.identifier = S.activitiy_id
ORDER BY
    C.label

Поскольку я использую скрипт на python, приведенный выше запрос соответствует этому: (Я также возвращаю результат как JSON)

def search(args, items):
    args = request.args.to_dict()
    if len(args) > 0:
      for param, value in args.iteritems():
        items = [v for v in items if v.has_key(param) and v[param] == value]
    return items

A = aliased(model.Activity, name='A')
S = aliased(model.AssocCompaniesActivities, name='S')
C = aliased(model.Company, name='C')
activity_area = A.name.label("activities_area")

results = session.query(C.id, C.label, activity_area) \
                 .join(S) \
                 .join(A) \
                 .filter(C.identifier == S.company_id) \
                 .filter(A.identifier == S.activity_id) \
                 .order_by(C.label) \
                 .all()
session.close()
args = request.args.to_dict()
results = search(args, results)
return jsonify({"results": results})

Это дает мне это:

{
  "results": [
    {
      "activities_area": "luxury", 
      "id": "company1", 
      "label": "first company"
    }, 
    {
      "activities_area": "banks", 
      "id": "company2", 
      "label": "second company"
    }, 
    {
      "activities_area": "paper", 
      "id": "company2", 
      "label": "second company"
    }
  ]
}

Я хочу вернуть компании с несколькими действиями только один раз и получить action_area в виде массива, подобного этому:

{
  "results": [
    {
      "activities_area": "luxury", 
      "id": "company1", 
      "label": "first company"
    }, 
    {
      "activities_area": [
           "paper",
           "banks"
      ],
      "id": "company2", 
      "label": "second company"
    }
  ]
}

МОДЕЛЬ:

class Companies(Base):
    __tablename__ = 'companies'

    identifier = Column(Integer, primary_key=True)
    id = Column(String)
    label = Column(String)

    def __init__(self, id, label):
        self.id = id
        self.label = label
class Activities(Base):
    __tablename__ = 'activities_area'

    identifier = Column(Integer, primary_key=True)
    name = Column(String)

    def __init__(self, name):
        self.name = name
class AssocCompaniesActivities(Base):
    __tablename__ = 'assoc_companies_activities'

    identifier = Column(Integer, primary_key=True)
    company_id = Column(Integer, ForeignKey("companies.identifier"), nullable=True)
    activities_area_id = Column(Integer, ForeignKey("activities_area.identifier"), nullable=True)

    def __init__(self, company_id , activities_area_id):
        self.company_id = organization_id
        self.activities_area_id = activities_area_id

Как это сделать?

1 Ответ

0 голосов
/ 27 июня 2018

Поскольку вы уже используете функции ORM, он действительно похож на SQLAlchemy с множественными отношениями "многие ко многим" , и в этом сценарии есть учебник *1003*, однако с небольшим Разница, что вы хотите класс для AssocCompaniesActivities.

Преамбула для импорта и других настроек, чтобы ваш исходный код работал:

from sqlalchemy import *
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
Base = declarative_base()

Это обновленное определение класса:

from sqlalchemy.orm import relationship

class Companies(Base):
    __tablename__ = 'companies'

    identifier = Column(Integer, primary_key=True)
    id = Column(String)
    label = Column(String)

    activities_area = relationship("Activities", secondary='assoc_companies_activities', back_populates='companies')

    def __init__(self, id, label):
        self.id = id 
        self.label = label

class Activities(Base):
    __tablename__ = 'activities_area'

    identifier = Column(Integer, primary_key=True)
    name = Column(String)

    companies = relationship("Companies", secondary='assoc_companies_activities', back_populates='activities_area')

    def __init__(self, name):
        self.name = name

class AssocCompaniesActivities(Base):
    __tablename__ = 'assoc_companies_activities'

    identifier = Column(Integer, primary_key=True)
    company_id = Column(Integer, ForeignKey("companies.identifier"), nullable=True)
    activities_area_id = Column(Integer, ForeignKey("activities_area.identifier"), nullable=True)
    # Should declare primary key for company_id and activities_area_id,
    # or better yet, just create a simple un-mapped table like in the docs

    def __init__(self, company_id , activities_area_id):
        self.company_id = company_id
        self.activities_area_id = activities_area_id

Настройка БД, сеанс:

session = Session()
Base.metadata.create_all(engine)

Наконец, добавьте пример данных:

company1 = Companies(id='company1', label='first company')
company2 = Companies(id='company2', label='second company')
banks_activity = Activities('banks')
luxury_activity = Activities('luxury')
paper_activity = Activities('paper')
session.add(company1)
session.add(company2)
session.add(banks_activity)
session.add(luxury_activity)
session.add(paper_activity)
session.commit()

Также отношения (после установления идентификатора компании и деятельности)

company1_luxury = AssocCompaniesActivities(company1.identifier, luxury_activity.identifier)
company2_banks = AssocCompaniesActivities(company2.identifier, banks_activity.identifier)
company2_paper = AssocCompaniesActivities(company2.identifier, paper_activity.identifier)
session.add(company1_luxury)
session.add(company2_banks)
session.add(company2_paper)
session.commit()

После обновления модели мы можем выполнить запрос соединения, используя одну из техник загрузки отношений для быстрой загрузки. Это та часть, в которой она отличается от других вопросов, и что существует не так много примеров, которые объединяют отношения «многие ко многим» с этой конкретной техникой, чтобы создать некоторые для объединенного запроса. Мы можем достичь того, что вы хотите, используя joinedload:

from sqlalchemy.orm import joinedload

companies = session.query(Companies).options(
    joinedload(Companies.activities_area).load_only('name')).all()

, который генерирует этот запрос:

SELECT companies.identifier AS companies_identifier, companies.id AS companies_id, companies.label AS companies_label, activities_area_1.identifier AS activities_area_1_identifier, activities_area_1.name AS activities_area_1_name 
FROM companies LEFT OUTER JOIN (assoc_companies_activities AS assoc_companies_activities_1 JOIN activities_area AS activities_area_1 ON activities_area_1.identifier = assoc_companies_activities_1.activities_area_id) ON companies.identifier = assoc_companies_activities_1.company_id

Наконец, превратите результаты в желаемую структуру данных:

print(json.dumps([{
    'id': c.id,
    'label': c.label,
    'activities_area': [a.name for a in c.activities_area]
} for c in companies], indent=4))

Выход:

[
    {
        "id": "company1",
        "label": "first company",
        "activities_area": [
            "luxury"
        ]
    },
    {
        "id": "company2",
        "label": "second company",
        "activities_area": [
            "banks",
            "paper"
        ]
    }
]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...