Как я могу выполнить этот запрос между связанными таблицами без использования UNION? - PullRequest
2 голосов
/ 12 мая 2010

Предположим, у меня есть две отдельные таблицы, которые я просматриваю для запроса. Обе эти таблицы имеют отношение к третьей таблице. Как я могу запросить обе таблицы с помощью одного запроса не на основе UNION?

Вот теоретический пример. У меня есть таблица пользователей. Этот пользователь может иметь как компакт-диски, так и книги. Я хочу найти все книги и компакт-диски этого пользователя с одним запросом, совпадающим со строкой (в этом примере «круто»).

Запрос на основе UNION может выглядеть так:

SELECT "book" AS model, name, ranking 
 FROM book 
WHERE name LIKE 'Awesome%' 
UNION 
SELECT "cd" AS model, name, ranking 
  FROM cd 
 WHERE name LIKE 'Awesome%' 
ORDER BY ranking DESC

Как я могу выполнить такой запрос без UNION? Если я сделаю простое левое соединение пользователя с книгами и компакт-дисками, мы получим общее количество результатов, равное количеству совпадающих компакт-дисков и числу совпадающих книг. Существует ли GROUP BY или какой-либо другой способ написания запроса, чтобы это исправить?

(РЕДАКТИРОВАТЬ: Причина, по которой я хотел бы избежать подхода Union, заключается в том, что на самом деле это запрос DQL, а Doctrine не поддерживает UNION. Если нет способа сделать это без UNION, я пойду по собственному пути SQL. Кроме того, реальный запрос содержит несколько дополнительных столбцов, которые не так хорошо сопоставляются друг с другом в приведенном выше примере.)

Ответы [ 4 ]

5 голосов
/ 12 мая 2010

Подумайте, как бы вы смоделировали это в OO-приложении. Вы создали бы суперкласс, который вы расширяете для книг и компакт-дисков, и тогда ваш пользователь будет обладать набором Collectibles. Любой данный объект в этом наборе является либо книгой, либо компакт-диском (или другим типом предмета коллекционирования), но у него есть только один из этих подтипов.

Вы можете сделать нечто похожее с SQL, создав таблицу , соответствующую супертипу:

CREATE TABLE Collectibles (
  collectible_id SERIAL PRIMARY KEY,
  user_id        INT NOT NULL,
  FOREIGN KEY (user_id) REFERENCES Users(user_id)
);

Тогда каждый подтип содержит ссылку, чтобы сделать его коллекционным:

CREATE TABLE Books (
  book_id   BIGINT UNSIGNED PRIMARY KEY
  book_name VARCHAR(100) NOT NULL,
  FOREIGN KEY (book_id) REFERENCES Collectibles(collectible_id)
);

CREATE TABLE CDs (
  cd_id   BIGINT UNSIGNED PRIMARY KEY
  cd_name VARCHAR(100) NOT NULL,
  FOREIGN KEY (cd_id) REFERENCES Collectibles(collectible_id)
);

Теперь вы можете выполнить свой запрос и быть уверенным, что не получите декартово произведение:

SELECT u.*, COALESCE(b.book_name, d.cd_name) AS media_name
FROM Users u
JOIN Collectibles c ON (u.user_id = c.user_id)
LEFT OUTER JOIN Books b ON (b.book_id = c.collectible_id)
LEFT OUTER JOIN CDs d ON (d.cd_id = c.collectible_id);
3 голосов
/ 12 мая 2010

Если вы пытаетесь избежать объединения, одним из способов является создание представления.

РЕДАКТИРОВАТЬ: создать представление у вас есть два варианта

В случае, если ваш запрос предназначен только для выбора записей, не должно быть проблем с

CREATE VIEW media AS
SELECT "book" AS model, name, ranking 
 FROM book 
WHERE name LIKE 'Awesome%' 
UNION
SELECT "cd" AS model, name, ranking 
  FROM cd 
 WHERE name LIKE 'Awesome%' 
ORDER BY ranking DESC

Если вам нужен вид, который можно обновить, он может появиться, если вы выполните рефакторинг:

  • создать таблицу, в которой будут храниться все данные И тип носителя
  • создать два представления, которые будут разделять данные по типу носителя (поскольку эти представления являются простыми запросами 1: 1 к нижележащим таблицам, они должны обновляться, и вы должны иметь возможность использовать их в отображении ORM или других запросах SQL)

EDIT2: я забыл прокомментировать тот факт, что UNION ALL является обязательным по сравнению с UNION, если только вы не хотите, чтобы MySQL запускал построение индекса на диске каждый раз, когда вы запускаете представление / запрос (спасибо HLGEM).

0 голосов
/ 12 мая 2010

Возможно, вы могли бы попробовать что-то вроде этого ... в примере предполагается, что третья таблица называется User:

$q = Doctrine_Query::create()
  ->select('c.cd, b.book')
  ->from('User u')
  ->LeftJoin('Cd c ON u.user_id = c.user_id AND c.name LIKE ?, 'Awesome%')
  ->LeftJoin('Book b ON u.user_id = b.user_id AND b.name LIKE ?, 'Awesome%');
$result = $q->execute();
0 голосов
/ 12 мая 2010

Если нет веских причин не использовать UNION, просто используйте UNION.

...