Хм... Вытащить из базы записи по категориям, по 3 штуки, одним запросом

Spear

почемучка
Хм... Вытащить из базы записи по категориям, по 3 штуки, одним запросом

Всем привет.
Извините за название темы - не знаю как описать то, что хочу спросить.
Суть проблемы такова:

в БД есть таблица, в ней записи так хранятся:
id | cat_id | name | ..... | date |

айди - уникален для каждой записи.
кат_ид - номер категории, к которой относится запись, всего таких категорий - около 20 может быть.

хочу сделать так:
вывести из базы по ТРИ или меньше названий относящихся к каждой категории,
сортировать по времени.
почему по "три или меньше" - в некоторых категориях не сразу будут все три статьи.

Конечно желательно сделать это все одним запросом в БД,
т.к. делать для каждой категории отдельный запрос - слишком жирно будет.. нагрузка и т.п. итак по мимо этих запросов уже используется 8 (движок сайта). Да и не факт что через пару месяцев этих категорий не станет 40.

потом нужно сформировать страницу такого вида примерно:

категория 1:
статья1
статья2
статья3

категория 2:
статья1
статья2

...

категория n:
статья1

пока что не знаю как оешить такую задачку.
Может быть у вас появятся какие-то идеи? Буду рад помощи
 

SVOLOCH

Guest
SELECT c.*, i.*
FORM `таблица категорий` as c, `таблица` as i
WHERE c.cat_id = t.cat_id
ORDER BY t.cat_id
ASK LIMIT 0, 3

Если правильно понял... =\
А вообще, пожалуйста, конкретизируйте ваш вопрос... Я мало что понял
 

python

Новичок
SVOLOCH
думаю результат будет далек от ожидаемого автором...

наверное, одним запросом этого не сделать. в голову приходит решение с двумя запросами - один выбирает id категорий и максимумы, а второй строится динамически со многими union all и limit 0,2 . но MySQL штука умная, вряд ли это намного быстрее многих одинарных запросов (или я заблуждаюсь?)
 

die_hard

Новичок
UNION спасет отца советской демократии? // или как там вы обычно говорите ... :)

http://dev.mysql.com/doc/mysql/en/union.html
 

Spear

почемучка
Впринципе делать это все одним запросом не обязательно, можно и 2-3.. но не как не 20 :)
 

Profic

just Profic (PHP5 BetaTeam)
[sql]SET @prev := 0;
SELECT id
FROM (SELECT id, IF(@prev <> cat_id, @cnt := (@prev := cat_id) - cat_id, @cnt := @cnt + 1) AS catnum FROM tbl ORDER BY cat_id ASC, `date` DESC) AS temp
WHERE catnum <=2;[/sql]
Для mysql < 4.1 переписать самостоятельно :)
 

Spear

почемучка
Profic
спасибо , конечно, за код ;)
но, честно говоря, я мало что понял.. точнее так - вообще не понял что убдет браться из базы и как :(
 

die_hard

Новичок
Как храниццо у тебя инфа?
Если ты
типа того
TABLE category:
id INT
name VARCHAR(X)

TABLE article
id INT
category_id INT
header VARCHAR(X)
body TEXT
...

То максимум можно вытащить 2-мя запросами только есть у тебя не 4.* MySQL. Если не ошибаюсь в нем мона делать вложенные запросы.

Хотя можно конечно и LEFT JOIN-нами замутить. то тогода фомат выхлопа на запрос будет не


категория 1:
статья1
статья2
статья3

категория 2:
статья1
статья2

...

категория n:
статья1
а будет он ясен пень
категория 1: статья1
категория 1: статья2
категория 1: статья3

категория 2: статья1
категория 2: статья2

...

категория n: статья1
Если же ты еще не определился как хранить все это дело(категории и статьи) ..тогда это совсем другая сказка :)))
 

Spear

почемучка
народ, вы наверное меня неправильно поняли.
мне не нужно попутно брать ещё и названия категорий. Вообще в таблицу категорияй обращаться не нужно (разве что этого НУЖНО будет для решения задачИ).

ещё раз:
таблица со статьями (это не пря\мо статьи по 5к символов.. это я так для примера.. )
id (int) | cat_id | name | date
кат_айди - категория к которой относится статья.
мне нужно на главнубю старницу сайта вывести по 3 новые статьи для каждой категории.
 

Profic

just Profic (PHP5 BetaTeam)
Spear
А выполнить этот запрос не пробовал?
Для mysql >= 4.1 [sql]SET @prev := 0;
SELECT name, `date`
FROM (
SELECT name, `date`, IF(@prev <> cat_id, @cnt := (@prev := cat_id) - cat_id, @cnt := @cnt + 1) AS catnum
FROM tbl
ORDER BY cat_id ASC, `date` DESC
) AS temp
WHERE catnum <=2;[/sql]
Для mysql < 4.1 [sql]SET @prev := 0;
CREATE TEMPORARY TABLE temp
SELECT name, `date`, IF(@prev <> cat_id, @cnt := (@prev := cat_id) - cat_id, @cnt := @cnt + 1) AS catnum
FROM tbl
ORDER BY cat_id ASC, `date` DESC;
SELECT name, `date`
FROM temp
WHERE catnum <=2;[/sql]
 

die_hard

Новичок
Опачки :) не прочитал нормально первый пост этого топика.

Карочи :)

Делаешь две колонки в которых будешь хранить id текущей счетчик статей для одной директории и текущую категорию. Его значения равно примерно (уточнишь сам по синтаксису)


[ТЕКУЩАЯ КАТЕГОРИЯ] = cat_id

IF([ТЕКУЩАЯ КАТЕГОРИЯ] == 0 OR [ТЕКУЩАЯ КАТЕГОРИЯ] != cat, (@cnt:= 0), (@cnt:= @cnt + 1)) AS counter

тра-ля-ля все поля которые нужны [ORDER BY DATE]
WHERE [СЧЕТЧИК] <= 3;


Другими словами В ответе на запрос у тебя хранится и обновляется(когда нужно), обнуляется(когда нужно) и соответственно показыается счетчик статей для одной категории. Значение текущей категории обновляешь ясен пень перед обновлением счетчика.

и в WHERE добавляешь условие что [СЧЕТЧИК] <= 3; все. Должно получится :) самому лень проверять какой имеено должен получистя запрос. Думаю, идея ясна.

-~{}~ 17.09.05 20:48:

"хранить id" => "хранить"
 

Profic

just Profic (PHP5 BetaTeam)
die_hard
А вот бы не поленился и проверил. И понял бы, что одним запосом это в mysql не сделаешь. Все что у тебя расписано словами и есть мои запросы. Только они проверенно рабочие.
 

die_hard

Новичок
Автор оригинала: Profic
die_hard
А вот бы не поленился и проверил. И понял бы, что одним запосом это в mysql не сделаешь. Все что у тебя расписано словами и есть мои запросы. Только они проверенно рабочие.
А ну да, конечно на словах можно все сказать. Это я понимаю. А еще я понимаю, что одним запросом можно разложить дерево папок бесконечной вложенности и посчитать количество документов группируя по папкам первого уровня. Правда тормозить будет на большой базе - мама не горюй :cool:. Сам делал. Пример пока не могу найти. А уж о том, что в выхлопе запроса создать две переменные и группировать по ним и сортировать по ним - об этом я ваще молчу.

-~{}~ 19.09.05 10:23:

И еще одно. Я автору указал направление - решение для нетривиальной, как мне кажется, задачи. Если надо и хочется - попробуйте решить задачу моим способом. Буду я еще за человека жевать и проглатывать. Делать мне больше нечего. Я и так потратил время, чтобы помочь.
И замечу, что у меня решение без явного создания временных таблиц.
 

Profic

just Profic (PHP5 BetaTeam)
die_hard
А уж о том, что в выхлопе запроса создать две переменные и группировать по ним и сортировать по ним - об этом я ваще молчу.
Хорошая у тебя травка. Где ты видишь в приведенных запросах группировку и сортировку по вычисляемым столбцам. К тому же она все равно работать не будет, почему? См. ниже.

Если надо и хочется - попробуйте решить задачу моим способом
Все что у тебя расписано словами и есть мои запросы. Только они проверенно рабочие.
И замечу, что у меня решение без явного создания временных таблиц
одним запосом это в mysql не сделаешь
Почему? Потому! Читайте мануалы, они рулез:
Note: In a SELECT statement, each expression is evaluated only when sent to the client. This means that in a HAVING, GROUP BY, or ORDER BY clause, you cannot refer to an expression that involves variables that are set in the SELECT list. For example, the following statement does not work as expected:
[sql]SELECT (@aa:=id) AS a, (@aa+3) AS b FROM tbl_name HAVING b=5;[/sql]
А уж о том, что использовать вычисляемый столбец в WHERE нельзя - об этом я вообще молчу.
 

die_hard

Новичок
Вкури, мегамозг Profic, и возрадуйся!

Предисловие - ради принципа, все на блюдечке с голубой каемочкой


CREATE TABLE `article` (
`id` int(11) NOT NULL auto_increment,
`cat_id` int(11) NOT NULL default '0',
`name` varchar(80) default NULL,
`date` int(11) NOT NULL default '0',
PRIMARY KEY (`id`)
) TYPE=MyISAM;


INSERT INTO article (cat_id, name, date) VALUES (3,'number 1', 1127121620);
INSERT INTO article (cat_id, name, date) VALUES (3,'number 2', 1127121621);
INSERT INTO article (cat_id, name, date) VALUES (1,'number 3', 1127121622);
INSERT INTO article (cat_id, name, date) VALUES (1,'number 4', 1127121623);
INSERT INTO article (cat_id, name, date) VALUES (2,'number 1', 1127121624);
INSERT INTO article (cat_id, name, date) VALUES (2,'number 2', 1127121625);
INSERT INTO article (cat_id, name, date) VALUES (2,'number 3', 1127121626);
INSERT INTO article (cat_id, name, date) VALUES (2,'number 4', 1127121627);
INSERT INTO article (cat_id, name, date) VALUES (1,'number 1', 1127121628);
INSERT INTO article (cat_id, name, date) VALUES (1,'number 2', 1127121629);

SET @tmp_article_curr_cat_id = 0, @tmp_article_cnt = 0;
SELECT
IF(@tmp_article_curr_cat_id = cat_id, (@tmp_article_cnt := @tmp_article_cnt + 1), (@tmp_article_cnt := 1)) AS cnt
, (@tmp_article_curr_cat_id := cat_id) AS curr_cat_id
, cat_id
, id
, name
, date
FROM
article
HAVING cnt <= 3
ORDER BY
cat_id, date, cnt;
SET @tmp_article_curr_cat_id = 0, @tmp_article_cnt = 0;

Кстати, Spear тоже может возрадоваться :)

P.S. mysql version - 4.0.24
 

Profic

just Profic (PHP5 BetaTeam)
die_hard
Этот запрос на 4.1.8 возвращает 9 записей, а должен 8:
Код:
+------+-------------+--------+----+----------+------------+
| cnt  | curr_cat_id | cat_id | id | name     | date       |
+------+-------------+--------+----+----------+------------+
|    2 |           1 |      1 | 10 | number 2 | 1127121629 |
|    1 |           1 |      1 |  9 | number 1 | 1127121628 |
|    2 |           1 |      1 |  4 | number 4 | 1127121623 |
|    1 |           1 |      1 |  3 | number 3 | 1127121622 |
|    3 |           2 |      2 |  7 | number 3 | 1127121626 |
|    2 |           2 |      2 |  6 | number 2 | 1127121625 |
|    1 |           2 |      2 |  5 | number 1 | 1127121624 |
|    2 |           3 |      3 |  2 | number 2 | 1127121621 |
|    1 |           3 |      3 |  1 | number 1 | 1127121620 |
+------+-------------+--------+----+----------+------------+
9 rows in set (0.00 sec)
Мой же как и положено 8:
Код:
+----+--------+----------+------------+--------+
| id | cat_id | name     | date       | catnum |
+----+--------+----------+------------+--------+
| 10 |      1 | number 2 | 1127121629 |      0 |
|  9 |      1 | number 1 | 1127121628 |      1 |
|  4 |      1 | number 4 | 1127121623 |      2 |
|  8 |      2 | number 4 | 1127121627 |      0 |
|  7 |      2 | number 3 | 1127121626 |      1 |
|  6 |      2 | number 2 | 1127121625 |      2 |
|  2 |      3 | number 2 | 1127121621 |      0 |
|  1 |      3 | number 1 | 1127121620 |      1 |
+----+--------+----------+------------+--------+
8 rows in set (0.02 sec)
Что я делаю не так?
 

die_hard

Новичок
Пока нет желание и возможности выяснить что не так. Извините.
 

SelenIT

IT-лунатик :)
Чисто ради спортивного интереса - нечто похожее в один запрос:
[sql]SELECT a1. * , count( a2.id ) AS catnum
FROM `article` a1, article a2
WHERE a1.cat_id = a2.cat_id AND a2.date >= a1.date
GROUP BY a1.id HAVING catnum <=3
ORDER BY a1.cat_id, a1.date DESC[/sql]
Но это, видимо, все-таки уже извращение ;)
 

die_hard

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

>> The general rule is to never assign!! and use the same variable in the same statement.
 

Spear

почемучка
а в два можно? Просто главное не использовать по новому запросу для каждой категории (то есть чтобы не делдать 30 запорсов)
 
Сверху