Ограничение MySQL для 2-го соединения в запросе - PullRequest
2 голосов
/ 22 апреля 2019

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

Таблица:

id | word
----------
1  | xyz
2  | zzz

таблица b:

name  | word
------------
Peter | xyz
John  | xyz
Jane  | xyz

таблица c:

name  | product
------------
Peter | blah
Peter | blah2
Peter | blah3
Peter | blah4
Peter | blah5
Peter | blah6
John | hello
John | world
John | blah

Желаемый результат: первые две записи таблицы b (пользователь Петр и Иоанн) и первые две записи объединены в таблице c для каждого пользователя.

name  | word | product
----------------------
Peter | xyz | blah
Peter | xyz | blah2
John  | xyz | hello
John  | xyz | world

Мой текущий запрос

select c.name, first_join.word, c.product from (
select b.* from a
left join b on b.word=a.word
where a.id=1
limit 2
) first_join
left join c on c.name=first_join.name

Это дает результат:

name  | word | product
------------------------------
Peter | xyz  | blah
Peter | xyz  | blah2
Peter | xyz  | blah3
Peter | xyz  | blah4
Peter | xyz  | blah5
Peter | xyz  | blah6
John  | xyz  | hello
John  | xyz  | world
John  | xyz  | blah

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

Ответы [ 2 ]

0 голосов
/ 23 апреля 2019

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

Следующая хранимая процедура дает вам нужный результат (это работает на сервере MySQL 5.6, но не в sqlfiddle):

drop procedure if exists fetch_names;

DELIMITER $$

CREATE PROCEDURE fetch_names()

    BEGIN 
    DECLARE v_ready INTEGER DEFAULT 0;
    DECLARE v_name varchar(100) DEFAULT "";
    -- first fetch all distinct names
    DECLARE names_cursor CURSOR FOR select distinct c.name from c;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_ready = 1;

    -- use a temporary table to store results
    CREATE TEMPORARY TABLE names_temp ( `name` varchar(255),  `word` varchar(255),  `product` varchar(255));

    OPEN names_cursor;

    -- loop through all names
    get_names: LOOP
    FETCH names_cursor INTO v_name;

    IF v_ready = 1 THEN 
     LEAVE get_names;
     END IF;

    -- get 2 results for each name (you may add some order parameter here)
    INSERT INTO names_temp SELECT c.name, b.word, c.product from a
    LEFT JOIN b on a.word = b.word
    LEFT JOIN c on b.name = c.name
    WHERE c.name = v_name
    LIMIT 2;

    END LOOP get_names;
    CLOSE names_cursor;

    -- output the collected result and drop the temporary table
    SELECT * FROM names_temp;
    DROP TEMPORARY TABLE IF EXISTS names_temp;

END$$
DELIMITER ;

-- call the procedure and get the results
call fetch_names();
0 голосов
/ 23 апреля 2019

Рассмотрим ваш запрос:

SELECT b.* FROM a
LEFT JOIN b ON b.word=a.word
WHERE a.id=1
LIMIT 2
-- This sub-query returns the following result:
+-------+------+
| name  | word |
+-------+------+
| Peter | xyz  |
| John  | xyz  |
+-------+------+

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

SELECT c.name, first_join.word, c.product FROM first_join
LEFT JOIN c ON c.name=first_join.name;

Давайте заменим c.name, first_join.word, c.product на *. Тогда вы получите такой результат:

+-------+------+-------+---------+
| name  | word | name  | product |
+-------+------+-------+---------+
| Peter | xyz  | Peter | blah    |
| Peter | xyz  | Peter | blah2   |
| Peter | xyz  | Peter | blah3   |
| Peter | xyz  | Peter | blah4   |
| Peter | xyz  | Peter | blah5   |
| Peter | xyz  | Peter | blah6   |
| John  | xyz  | John  | hello   |
| John  | xyz  | John  | world   |
| John  | xyz  | John  | blah    |
+-------+------+-------+---------+

Теперь, вот где вы спрашиваете себя, хотите ли вы этого или нет? Если это не так, вам нужно убедиться, что вы понимаете логику того, что вы пытаетесь сделать здесь. Как, например, почему ваш подзапрос first_join возвращает две строки, а когда вы LEFT JOIN, он возвращает все строки из table c? Ну, ответ на этот вопрос из-за вашего ON c.name=first_join.name. Он соответствует всем вхождениям Питера и Джона в table c независимо от ограничения, которое вы наложили в подзапросе. Если вы установите ограничение для внешнего запроса, он также не будет работать.

ТЛ; др Если вы используете MySQL 8.0, вы можете попробовать этот запрос ниже:

SELECT name,word,product FROM
(SELECT 
    b.name, b.word,c.product,
    ROW_NUMBER() OVER (PARTITION BY c.name ORDER BY c.name DESC) AS wRow
FROM 
a JOIN
b ON a.word=b.word JOIN
c ON c.name=b.name
WHERE a.id=1) r
WHERE wRow IN (1,2)
ORDER BY name DESC;

Fiddle

Два других примера:

-- First: by using INNER JOIN and UNION (as suggested in the comment).
SELECT b.name,b.word,r.product FROM a JOIN b ON a.word=b.word JOIN
((SELECT * FROM c WHERE c.name='peter' LIMIT 2) UNION
(SELECT * FROM c WHERE c.name='john' LIMIT 2)) r
ON b.name=r.name 
WHERE a.id=1;

-- Second: by using LEFT JOIN.    
SELECT b.Name,b.Word,IF(p.product IS NULL,j.product,p.product) AS 'Product' 
FROM a JOIN b ON a.word=b.word LEFT JOIN
(SELECT * FROM c WHERE NAME='peter' LIMIT 2) p ON b.name=p.name LEFT JOIN
(SELECT * FROM c WHERE NAME='john' LIMIT 2) j ON b.name=j.name
WHERE a.id=1 AND b.name IN (p.name,j.name);

ОБНОВЛЕНИЕ: ОК. Не красиво, но этот запрос может работать. Я тестировал на MySQL 5.7. ;)

SELECT b.Name,b.Word,c.Product FROM a 
JOIN b ON a.word=b.word 
JOIN c ON b.name=c.name 
JOIN (SELECT c.name,
REPLACE(SUBSTRING_INDEX(GROUP_CONCAT(product SEPARATOR ' '),' ',2),' ',',') prod 
FROM c GROUP BY NAME) r
ON b.name=r.name WHERE FIND_IN_SET(c.product,prod);

Но будет гораздо проще, если у вас есть столбец со значением автоинкремента, по крайней мере.

...