Многократное соединение SQL для многих ко многим таблицам + разделение запятыми

У меня есть такие таблицы:

таблица мультимедиа - первичный ключ id int, uri varchar.
media_to_people - первичный ключ media_id int, первичный ключ people_id int
люди - первичный ключ id int, имя varchar, role int - роль определяет, является ли человек художником , издатель, писатель, актер и т. д. относительно СМИ и имеет диапазон (1-10)

Это отношение "многие ко многим"

Я хочу получить медиа и всех связанных с ним людей в select. Так что, если с СМИ связано 10 человек, все 10 должны прийти.

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

Заголовки результатов должны выглядеть так: media.id, media.uri, people.name (актер), people.name (исполнитель), people.name (издатель) и т. Д.

Я использую sqlite.


person jetru    schedule 10.06.2009    source источник


Ответы (2)


Я согласен с ответом Алекса Мартелли, что вы должны получить данные в нескольких строках и выполнить некоторую обработку в своем приложении.

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

Поэтому вам нужно сделать это с помощью GROUP_CONCAT() и создать скалярный подзапрос в списке выбора для каждого роль:

SELECT m.id, m.uri, 
 (SELECT GROUP_CONCAT(name) 
  FROM media_to_people JOIN people ON (people_id = id) 
  WHERE media_id = m.id AND role = 1) AS Actors,
 (SELECT GROUP_CONCAT(name) 
  FROM media_to_people JOIN people ON (people_id = id) 
  WHERE media_id = m.id AND role = 2) AS Artists,
 (SELECT GROUP_CONCAT(name) 
  FROM media_to_people JOIN people ON (people_id = id) 
  WHERE media_id = m.id AND role = 3) AS Publishers
FROM media m;

Это действительно уродливо! Не пытайтесь повторить это дома!

Воспользуйтесь нашим советом и не пытайтесь форматировать сводную таблицу, используя только SQL.

person Community    schedule 10.06.2009
comment
Это прекрасно работает! Это именно то, что мне нужно! Сильно ли это сказывается на производительности? - person jetru; 10.06.2009
comment
Да! Это сильно снижает производительность !! Вот почему я сказал тебе не делать этого! Помимо того, что это уродливый, неподдерживаемый код. - person Bill Karwin; 10.06.2009

SQLite не имеет функции "поворота", которая может понадобиться для начинающих, а часть "значения, разделенные запятыми" определенно является проблемой презентации, которую было бы абсурдно (и, возможно, невозможно) пытаться протолкнуть на любой уровень базы данных, независимо от того, может быть задействован диалект SQL - это определенно часть работы, которую вы выполняете на клиенте, например средство отчетности или язык программирования.

Используйте SQL для доступа к данным и оставьте представление другим слоям.

Как вы получаете свои данные

SELECT media.id, media.uri, people.name, people.role
FROM media
JOIN media_to_people ON (media.id = media_to_people.media_id)
JOIN people ON (media_to_people.people_id = people.id)
WHERE media.id = ?
ORDER BY people.role, people.name

(? - это один из способов указать параметр в SQLite, который будет привязан к конкретному идентификатору мультимедиа, который вы ищете, способами, которые зависят от вашего клиента); данные будут поступать из БД в ваш клиентский код в несколько строк, и ваш клиентский код может легко поместить их в желаемую форму с одним столбцом.

Нам сложно сказать, как кодировать клиентскую часть, ничего не зная о среде или языке, которые вы используете в качестве клиента. Но, например, в Python:

def showit(dataset):
  by_role = collections.defaultdict(list)
  for mediaid, mediauri, name, role in dataset:
    by_role[role].append(name)
  headers = ['mediaid', 'mediauri']
  result = [mediaid, mediauri]
  for role in sorted(by_role):
    headers.append('people(%s)' % role)
    result.append(','.join(by_role[role]))
  return ' '.join(headers) + '\n' + ' '.join(result)

даже это не совсем соответствует вашей спецификации - вы запрашиваете заголовки, такие как «люди (исполнитель)», в то же время указываете, что роль закодирована как int, и не упоминаете способ перехода от int к строке «исполнитель» , так что, очевидно, невозможно точно соответствовать вашей спецификации ... но это настолько близко, насколько может быть моя изобретательность ;-).

person Alex Martelli    schedule 10.06.2009
comment
+1 за рекомендацию постобработки строк на языке клиентского приложения! - person Bill Karwin; 10.06.2009
comment
Хм, так что нет возможности получить столбцы people.name специально для разных ролей? Я думал примерно так: ВЫБЕРИТЕ media.id, media.uri, artist.name, publishers.name FROM media ПРИСОЕДИНЯЙТЕСЬ к media_to_people НА media_to_people.media_id = media.id ПРИСОЕДИНЯЙТЕСЬ к людям КАК художникам, людям КАК издателям ВКЛЮЧЕНО (artist.id = media_to_people .people_id И artist.role = 2) ИЛИ (publishers.role = 8 AND publishers.id = media_to_people.people_id) Но это работает не так, как задумано ... - person jetru; 10.06.2009
comment
Ну не так важно название заголовка, как вывести его подряд. Я хочу, чтобы все было сделано одним запросом. Я пишу C. Вы можете себе представить, почему я хочу избежать постобработки моих результатов. ;) - person jetru; 10.06.2009
comment
@Alex: Мне очень жаль, что я написал свой ответ. Мое намерение состояло в том, чтобы показать OP, насколько неуместно было бы писать один SQL-запрос для выполнения всей работы. Я предположил, что если я помечу свой ответ как «community wiki», jetru не сможет его принять. - person Bill Karwin; 10.06.2009
comment
@ Билл, хех, НП - в твоем ответе был отличный код и объяснение, в каком режиме ты можешь? -) - person Alex Martelli; 10.06.2009
comment
Хм. есть ли возможность принять два ответа? : D Мне нужен этот запрос, потому что мне нужно, чтобы модуль работал быстро (потому что есть другие части модуля, которые полагаются на этот оператор), и этот запрос точно выполняет свою работу. Кроме того, для каждого пользователя база данных находится в локальной системе. Возможно, позже, когда у меня будет куча времени, я заменю это пост-обработкой данных. :) - person jetru; 11.06.2009