Выберите последние 3 новости из каждой категории (MySQL - PHP) [дубликат]

URL-фрагменты (# бит) НЕ отправляются на сервер (или, по крайней мере, их не должно быть). Ваш браузер отправляет обычный запрос GET, минус # бит. Когда получен ответ на страницу и отобразится страница, ваш браузер автоматически прокручивается до этого идентификатора. Таким образом, ваша схема не будет работать.

Кроме того, я уверен, что вы не используете ReWrite правильно. Насколько я понимаю, это используется для сопоставления запрошенных активов с каким-либо местоположением физического диска, оно не выдаёт перенаправление.

Изменить: ReWrite может используется для выдачи переадресации с флагом [R], по комментарию @ Хакре ниже. Таким образом, в этом случае вы хотите перенаправить с / page / six на / page # six, что позволяет Apache не задушить #six, если я думаю, что работа.

295
задан Salman A 15 May 2015 в 11:27
поделиться

13 ответов

Для этого требуется ряд подзапросов для ранжирования значений, их ограничения, затем выполнения суммы при группировке

@Rnk:=0;
@N:=2;
select
  c.id,
  sum(c.val)
from (
select
  b.id,
  b.bal
from (
select   
  if(@last_id=id,@Rnk+1,1) as Rnk,
  a.id,
  a.val,
  @last_id=id,
from (   
select 
  id,
  val 
from list
order by id,val desc) as a) as b
where b.rnk < @N) as c
group by c.id;
7
ответ дан Brian L Cartwright 17 August 2018 в 08:50
поделиться

Нет, вы не можете LIMIT подзапросы произвольно (вы можете сделать это в ограниченной степени в новых MySQL, но не для 5 результатов для каждой группы).

Это запрос типа с групповым максимумом, что нетрудно сделать в SQL. Существуют различные способы для решения того, что может быть более эффективным для некоторых случаев, но для top-n вообще вы хотите посмотреть на ответ Билла на аналогичный предыдущий Вопрос.

Как и в большинстве решений этой проблемы, он может возвращать более пяти строк, если имеется несколько строк с тем же значением rate, поэтому вам может потребоваться некоторое количество пост-обработки для проверки для этого.

8
ответ дан Community 17 August 2018 в 08:50
поделиться

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

DELIMITER $$
CREATE PROCEDURE count_limit200()
BEGIN
    DECLARE a INT Default 0;
    DECLARE stop_loop INT Default 0;
    DECLARE domain_val VARCHAR(250);
    DECLARE domain_list CURSOR FOR SELECT DISTINCT domain FROM db.one;

    OPEN domain_list;

    SELECT COUNT(DISTINCT(domain)) INTO stop_loop 
    FROM db.one;
    -- BEGIN LOOP
    loop_thru_domains: LOOP
        FETCH domain_list INTO domain_val;
        SET a=a+1;

        INSERT INTO db.two(book,artist,title,title_count,last_updated) 
        SELECT * FROM 
        (
            SELECT book,artist,title,COUNT(ObjectKey) AS titleCount, NOW() 
            FROM db.one 
            WHERE book = domain_val
            GROUP BY artist,title
            ORDER BY book,titleCount DESC
            LIMIT 200
        ) a ON DUPLICATE KEY UPDATE title_count = titleCount, last_updated = NOW();

        IF a = stop_loop THEN
            LEAVE loop_thru_domain;
        END IF;
    END LOOP loop_thru_domain;
END $$

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

1
ответ дан Dev-Ria 17 August 2018 в 08:50
поделиться

Вы можете использовать агрегированную функцию GROUP_CONCAT , чтобы получить все годы в один столбец, сгруппированный по id и упорядоченный по rate:

SELECT   id, GROUP_CONCAT(year ORDER BY rate DESC) grouped_year
FROM     yourtable
GROUP BY id

Результат:

-----------------------------------------------------------
|  ID | GROUPED_YEAR                                      |
-----------------------------------------------------------
| p01 | 2006,2003,2008,2001,2007,2009,2002,2004,2005,2000 |
| p02 | 2001,2004,2002,2003,2000,2006,2007                |
-----------------------------------------------------------

И тогда вы можете использовать FIND_IN_SET , который возвращает позицию первого аргумента во втором, например.

SELECT FIND_IN_SET('2006', '2006,2003,2008,2001,2007,2009,2002,2004,2005,2000');
1

SELECT FIND_IN_SET('2009', '2006,2003,2008,2001,2007,2009,2002,2004,2005,2000');
6

Используя комбинации GROUP_CONCAT и FIND_IN_SET и фильтрации по позиции, возвращаемой find_in_set, вы можете использовать этот запрос, который возвращает только первые 5 лет для каждого id:

SELECT
  yourtable.*
FROM
  yourtable INNER JOIN (
    SELECT
      id,
      GROUP_CONCAT(year ORDER BY rate DESC) grouped_year
    FROM
      yourtable
    GROUP BY id) group_max
  ON yourtable.id = group_max.id
     AND FIND_IN_SET(year, grouped_year) BETWEEN 1 AND 5
ORDER BY
  yourtable.id, yourtable.year DESC;

См. скрипку здесь .

Обратите внимание, что если более чем одна строка может иметь одинаковую скорость, вы должны рассмотреть возможность использования GROUP_CONCAT (DISTINCT rate ORDER BY rate) в столбце скорости вместо столбца года .

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

82
ответ дан fthiella 17 August 2018 в 08:50
поделиться
  • 1
    Умный трюк и приятное объяснение! – user10089632 21 January 2018 в 17:26
  • 2
    Это красиво исполнитель, сравнительно простое и отличное объяснение; Спасибо большое. К вашему последнему пункту. Если разумная максимальная длина может быть вычислена, можно использовать SET SESSION group_concat_max_len = <maximum length>;. В случае OP не проблема (поскольку по умолчанию - 1024), но в качестве примера group_concat_max_len должен быть не менее 25: 4 (максимальная длина строки года) + 1 (символ разделителя), раз 5 (первые 5 лет). Строки усекаются, а не бросают ошибку, поэтому следите за предупреждениями, такими как 1054 rows in set, 789 warnings (0.31 sec). – Timothy Johns 16 March 2018 в 00:35
  • 3

Пожалуйста, попробуйте ниже хранимую процедуру. Я уже проверял. Я получаю правильный результат, но не используя groupby.

CREATE DEFINER=`ks_root`@`%` PROCEDURE `first_five_record_per_id`()
BEGIN
DECLARE query_string text;
DECLARE datasource1 varchar(24);
DECLARE done INT DEFAULT 0;
DECLARE tenants varchar(50);
DECLARE cur1 CURSOR FOR SELECT rid FROM demo1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    SET @query_string='';

      OPEN cur1;
      read_loop: LOOP

      FETCH cur1 INTO tenants ;

      IF done THEN
        LEAVE read_loop;
      END IF;

      SET @datasource1 = tenants;
      SET @query_string = concat(@query_string,'(select * from demo  where `id` = ''',@datasource1,''' order by rate desc LIMIT 5) UNION ALL ');

       END LOOP; 
      close cur1;

    SET @query_string  = TRIM(TRAILING 'UNION ALL' FROM TRIM(@query_string));  
  select @query_string;
PREPARE stmt FROM @query_string;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

END
0
ответ дан Himanshu Patel 17 August 2018 в 08:50
поделиться

Принял некоторые работы, но я решил, что мое решение будет разделяться, так как оно кажется элегантным и довольно быстрым.

SELECT h.year, h.id, h.rate 
  FROM (
    SELECT id, 
      SUBSTRING_INDEX(GROUP_CONCAT(CONCAT(id, '-', year) ORDER BY rate DESC), ',' , 5) AS l
      FROM h
      WHERE year BETWEEN 2000 AND 2009
      GROUP BY id
      ORDER BY id
  ) AS h_temp
    LEFT JOIN h ON h.id = h_temp.id 
      AND SUBSTRING_INDEX(h_temp.l, CONCAT(h.id, '-', h.year), 1) != h_temp.l

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

3
ответ дан John 17 August 2018 в 08:50
поделиться

Попробуйте следующее:

SET @num := 0, @type := '';
SELECT `year`, `id`, `rate`,
    @num := if(@type = `id`, @num + 1, 1) AS `row_number`,
    @type := `id` AS `dummy`
FROM (
    SELECT *
    FROM `h`
    WHERE (
        `year` BETWEEN '2000' AND '2009'
        AND `id` IN (SELECT `rid` FROM `table2`) AS `temp_rid`
    )
    ORDER BY `id`
) AS `temph`
GROUP BY `year`, `id`, `rate`
HAVING `row_number`<='5'
ORDER BY `id`, `rate DESC;
1
ответ дан MLF 17 August 2018 в 08:50
поделиться

Создайте виртуальные столбцы (например, RowID в Oracle)

table:

`
CREATE TABLE `stack` 
(`year` int(11) DEFAULT NULL,
`id` varchar(10) DEFAULT NULL,
`rate` float DEFAULT NULL) 
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`

data:

insert into stack values(2006,'p01',8);
insert into stack values(2001,'p01',5.9);
insert into stack values(2007,'p01',5.3);
insert into stack values(2009,'p01',4.4);
insert into stack values(2001,'p02',12.5);
insert into stack values(2004,'p02',12.4);
insert into stack values(2005,'p01',2.1);
insert into stack values(2000,'p01',0.8);
insert into stack values(2002,'p02',12.2);
insert into stack values(2002,'p01',3.9);
insert into stack values(2004,'p01',3.5);
insert into stack values(2003,'p02',10.3);
insert into stack values(2000,'p02',8.7);
insert into stack values(2006,'p02',4.6);
insert into stack values(2007,'p02',3.3);
insert into stack values(2003,'p01',7.4);
insert into stack values(2008,'p01',6.8);

SQL вот так:

select t3.year,t3.id,t3.rate 
from (select t1.*, (select count(*) from stack t2 where t1.rate<=t2.rate and t1.id=t2.id) as rownum from stack t1) t3 
where rownum <=3 order by id,rate DESC;

, если удалить предложение where в t3, оно выглядит следующим образом:

GET «TOP N Record» - > добавьте «rownum & lt; = 3» в where where (предложение where t3);

ВЫБЕРИТЕ «год» -> добавьте «МЕЖДУ 2000 И 2009» в разделе where ( где - положение t3);

4
ответ дан Patrick Tucci 17 August 2018 в 08:50
поделиться
  • 1
    Если у вас есть ставки, которые повторяются для одного и того же идентификатора, это не будет работать, потому что ваш счетчик rowNum будет увеличиваться выше; вы не получите 3 за строку, вы можете получить 0, 1 или 2. Можете ли вы придумать какое-либо решение этого? – starvator 23 June 2016 в 14:52
  • 2
    @starvator изменяет значение "t1.rate & lt; = t2.rate" до «t1.rate & lt; trate», если наилучшая скорость имеет одинаковые значения в одном и том же идентификаторе, все они имеют одинаковый рон, но не будут увеличиваться выше; как «скорость 8 в id p01», если она повторяется, используя «t1.rate & lt; trate», как «скорость 8 в id p01», имеет тот же rownum 0; если используется «t1.rate & lt; = t2.rate», rownum равно 2; – Wang Wen'an 12 July 2016 в 07:11
SELECT year, id, rate
FROM (SELECT
  year, id, rate, row_number() over (partition by id order by rate DESC)
  FROM h
  WHERE year BETWEEN 2000 AND 2009
  AND id IN (SELECT rid FROM table2)
  GROUP BY id, year
  ORDER BY id, rate DESC) as subquery
WHERE row_number <= 5

Подзапрос почти идентичен вашему запросу. Только изменение добавляет

row_number() over (partition by id order by rate DESC)
1
ответ дан Ricky Moreno 17 August 2018 в 08:50
поделиться
  • 1
    Это хорошо, но MySQL не имеет оконных функций (например, ROW_NUMBER()). – ypercubeᵀᴹ 7 January 2013 в 18:06
  • 2
    Начиная с MySQL 8.0, row_number() доступен . – fantasticfears 27 November 2017 в 14:47

Попробуйте следующее:

SELECT h.year, h.id, h.rate 
FROM (SELECT h.year, h.id, h.rate, IF(@lastid = (@lastid:=h.id), @index:=@index+1, @index:=0) indx 
      FROM (SELECT h.year, h.id, h.rate 
            FROM h
            WHERE h.year BETWEEN 2000 AND 2009 AND id IN (SELECT rid FROM table2)
            GROUP BY id, h.year
            ORDER BY id, rate DESC
            ) h, (SELECT @lastid:='', @index:=0) AS a
    ) h 
WHERE h.indx <= 5;
8
ответ дан Saharsh Shah 17 August 2018 в 08:50
поделиться

Исходный запрос использовал пользовательские переменные и ORDER BY в производных таблицах; поведение обоих причуд не гарантируется. Пересмотренный ответ следующим образом.

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

SELECT testdata.id, testdata.rate, testdata.year, COUNT(lesser.rate) AS rank
FROM testdata
LEFT JOIN testdata AS lesser ON testdata.id = lesser.id AND testdata.rate < lesser.rate
GROUP BY testdata.id, testdata.rate, testdata.year
HAVING COUNT(lesser.rate) < 5
ORDER BY testdata.id, testdata.rate DESC

Обратите внимание, что:

  1. COUNT на нулевом уровне
  2. Для сортировки по убыванию меньшая строка - с более высокой скоростью
  3. Возвращаются все строки, привязанные для последнего места

Результат:

+------+-------+------+------+
| id   | rate  | year | rank |
+------+-------+------+------+
| p01  |  8.00 | 2006 |    0 |
| p01  |  7.40 | 2003 |    1 |
| p01  |  6.80 | 2008 |    2 |
| p01  |  5.90 | 2001 |    3 |
| p01  |  5.30 | 2007 |    4 |
| p02  | 12.50 | 2001 |    0 |
| p02  | 12.40 | 2004 |    1 |
| p02  | 12.20 | 2002 |    2 |
| p02  | 10.30 | 2003 |    3 |
| p02  |  8.70 | 2000 |    4 |
+------+-------+------+------+
67
ответ дан Salman A 17 August 2018 в 08:50
поделиться
  • 1
    Я думаю, что стоит упомянуть, что ключевой частью является ORDER BY id, так как любое изменение значения id возобновит подсчет в ранге. – ruuter 27 August 2015 в 23:23
  • 2
    Зачем мне запускать его дважды, чтобы получить ответ от WHERE rank <=5? Впервые я не получаю 5 строк от каждого идентификатора, но после этого я могу получить, как вы сказали. – Brenno Leal 1 August 2016 в 17:37
  • 3
    @BrennoLeal Я думаю, что вы забываете инструкцию SET (см. Первый запрос). Это необходимо. – Salman A 1 August 2016 в 18:12
  • 4
    В более новых версиях ORDER BY в производной таблице может и часто игнорируется. Это побеждает цель. Эффективные групповые найдены здесь здесь . – Rick James 10 February 2018 в 05:21
  • 5
    @RickJames, вы правы. Переписал ответ. – Salman A 28 February 2018 в 10:46

Следующая публикация: sql: selcting top N записей на группу описывает сложный способ достижения этого без подзапросов.

Он улучшает другие решения, предлагаемые здесь:

  • Выполнение всего в одном запросе
  • Возможность правильно использовать индексы
  • Избегание подзапросов, как известно, для создания плохих планов выполнения в MySQL

Это, однако, не очень. Хорошим решением было бы возможно, если в MySQL были включены функции Window (aka Analytic Functions), но это не так. В трюке, используемой в указанном сообщении, используется GROUP_CONCAT, который иногда описывается как «Функции окна для пользователя для MySQL».

2
ответ дан Shlomi Noach 17 August 2018 в 08:50
поделиться

Для меня что-то вроде

SUBSTRING_INDEX(group_concat(col_name order by desired_col_order_name), ',', N) 

работает отлично. Нет сложного запроса.


например: get top 1 для каждой группы

SELECT 
    *
FROM
    yourtable
WHERE
    id IN (SELECT 
            SUBSTRING_INDEX(GROUP_CONCAT(id
                            ORDER BY rate DESC),
                        ',',
                        1) id
        FROM
            yourtable
        GROUP BY year)
ORDER BY rate DESC;
13
ответ дан user1691427 17 August 2018 в 08:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: