2015-07-17 1 views
1

Я хочу объединить четыре таблицы с значениями и без дубликатов, а затем перевести их в запрос SQLAlchemy.Соедините четыре таблицы с участием LEFT JOIN без дубликатов

Таблицы (упрощенно):

Category(id, name) 
Task(id, category.id, name) 
User(id, name) 

И многие-ко-многим таблице:

Solved(task.id, user.id) 

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

+---------------+-----------+-----------+ 
| category.name | task.name | user.name | 
+---------------+-----------+-----------+ 
| abc   | abctask1 | <null> | 
| abc   | abctask2 | luke  | 
| def   | deftask1 | <null> | 
| ghi   | ghitask1 | <null> | 
| ghi   | ghitask2 | luke  | 
+---------------+-----------+-----------+ 

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

До сих пор я получил:

SELECT DISTINCT 
    cat.name, t.name, u.name 
FROM 
    Task t 
JOIN 
    Category cat ON cat.id = t.category_id 
LEFT JOIN 
    Solved s ON s.task_id = t.id 
LEFT JOIN 
    User u ON s.user_id = u.id AND 
    u.name = 'luke' 
ORDER BY 
    cat.name 

Но, хотя DISTINCT, я получил дубликаты из всех строк с заданным пользователем:

+---------------+-----------+-----------+ 
| category.name | task.name | user.name | 
+---------------+-----------+-----------+ 
| abc   | abctask1 | <null> | 
| abc   | abctask2 | luke  | 
| abc   | abctask2 | <null> | <-- duplicate 
| def   | deftask1 | <null> | 
| ghi   | ghitask1 | <null> | 
| ghi   | ghitask2 | luke  | 
| ghi   | ghitask2 | <null> | <-- duplicate 
+---------------+-----------+-----------+ 

Есть ли возможность получить эту таблицу один запрос и перевести его в SQLAlchemy?

+0

Для записи: DISTINCT будет только сливать строки, которые являются * точно такими же *. Он не удалит «почти дубликаты», как те, которые показаны в OP. – Kevin

+0

В отображаемом результате дубликатов нет. Похоже, вы действительно хотите отличные * задачи * в результате (ровно одну строку за задачу) - с данным пользователем, только если s (он) решил это? BTW, 'User' не является допустимым именем таблицы без двойных кавычек (зарезервированное слово). –

ответ

1

У вас есть два LEFT JOINS:

  • 1-й слева присоединиться могут присоединиться к нескольким строкам из solved. Скажем, «Джейн» и «Люк» решили задачу.
  • Второе левое соединение может присоединяться только к пользователям с именем luke ('luke' в условии соединения!).

Вы все еще получаете как строки, «джейн» просто не показано, условие соединения фильтрует ее, но LEFT JOIN сохраняет строку в результате все равно и присоединяет NULL значения.

Вы можете добиться того, что вы после с помощью скобок и в [INNER] JOIN вместо LEFT JOIN между solved и users. The documentation:

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

SELECT c.name AS cat_name, t.name AS task_name, u.name AS user_name 
FROM task t 
JOIN category c ON cat.id = t.category_id 
LEFT JOIN 
     (solved s JOIN users u ON u.id = s.user_id AND u.name = 'luke') ON s.task_id = t.id 
ORDER BY 1, 2, 3; 
  • Используя имя таблицы users вместо зарезервированное слово user .

  • Предполагая, что users.name определено уникальным или у вас может быть несколько пользователей с именем 'luke'.

  • Если (task.id, users.id) в solved определяется UNIQUE или PRIMARY KEY, вам не нужно DISTINCT вообще.

Полученный запрос является не только правильным, но и более быстрым.


SQLAlchemy версия вышеупомянутого запроса: (contributed by @van)
Это предполагает, что Category, Task и User отображаются классы, в то время как solved является экземпляром Table (только таблицу ассоциации, как показано в примере кода Many to Many):

user_name = 'luke' 
q = (session.query(Category.name, Task.name, User.name) 
    .select_from(Task) 
    .join(Category) 
    .outerjoin(
     join(solved, User, 
       (solved.c.user_id == User.id) & (User.name == user_name), 
     )) 
    .order_by(Category.name, Task.name, User.name) 
    ) 
+0

Хорошее объяснение и решение работает отлично, спасибо. – Topiks

+0

@van: Конечно, вперед! –

1

Проблема возникает из ваших данных, то есть у вас, вероятно, есть 2 задачи, называемые abctask2/ghitask2. Возможно, вы должны установить ограничение на имя задачи. Вы запросили хорошо.

http://sqlfiddle.com/#!9/c4647c/4

Попробуйте проверить с

SELECT category_id, name ,count(*) from TASK GROUP BY category_id, name HAVING COUNT(*)<>1 
+1

В данных есть объяснение без дубликатов. –