There is a table of the form:
t_news
id
title
cat
Categories are not consistent for each news. That is, it may be first 2 news first category, then a third, then a fifth, etc.
Need to get to 5 news every category, sorted by date.
What is the output should be similar to the following (example, 3 news, Category 3)
id title cat
1 News1 1
2 News2 1
3 News3 1
4 News4 2
5 News5 2
6 News6 2
7 News7 3
8 News8 3
9 News9 3
In MySQL you can use variables to create a row number column:
SELECT id, title, cat
FROM ( SELECT id,
title,
cat,
#r:=IF(#cat = cat, #r+1, 1) AS RowNum ,
#cat:= cat AS Cat2
FROM t_news,
(SELECT #cat:= 0) AS cat,
(SELECT #r:= 0) AS r
ORDER BY cat, id
) t
WHERE RowNum <= 5;
The key is at each row if the cat column is the same as the #cat variable (set from the previous row), then the row number increments by one. Otherwise it resets to 0. The order of the increment is set by the order by clause in the subquery (I have used ID since the schema you posted does not include a date column).
Example on SQL Fiddle
Total code:
SELECT id, title, cat, from_unixtime(date) `date` FROM (
SELECT id, title, cat, `date`
FROM
(
SELECT id, title, cat, `date`, #r:=IF(#cat = cat, #r+1, 1) AS RowNum , #cat:= category AS Cat2
FROM news, (SELECT #cat:= 0) AS cat, (SELECT #r:= 0) AS r
WHERE hide=0
ORDER BY cat, `date` DESC, id
) t
WHERE RowNum <= 4 LIMIT 16
) t2
ORDER BY `date` DESC;
#GarethD, Thank you =)
Related
I have a table like this:
id name
0 Bob
1 Alice
2 Bob
3
4 Bob
5 Mary
6 Alice
I need to assign a group_id to each distinct name:
id name group_id
0 Bob 0 -- Bob's group
1 Alice 1 -- Alice's group
2 Bob 0 -- Bob's group
3 -- no group (NULL)
4 Bob 0 -- Bob's group
5 Mary 2 -- Mary's group
6 Alice 1 -- Alice's group
Can this be done in one line in MySQL?
I know I could find the unique names with an autoincrement column, then JOIN back with the original table based on the name -- but I was wondering if there exists a simpler/faster solution...
Yes, use case. Just add a new computed column based on an expression that outputs the appropriate value for each string value of the name column
Select id, name,
case name
when 'Bob' then 0
when 'Alice' then 1
when 'Mary' then 2
-- etc.
end GroupId
From table
If you don't know the names in advance, or if there are too many, try this:
Select id, name,
(select count(distinct name)
from table
where name < t.Name) groupId
From table t
Unless you add an index on the name column, this will be very slow on a large table.
To output a null instead of a 0 for rows with name = null, use this:
Select id, name,
case when name is null then null
else (select count(distinct name)
from table
where name < t.Name) end groupId
From table t
One method is -- as you suggest -- group by and join. If the numbers do not have to be sequential:
select t.*, minid as group_id
from t join
(select name, min(id) as minid
from t
group by name
) tt
on not t.name <=> tt.name; -- to handle `NULL`
If they do, use variables:
select t.*, minid as group_id
from t join
(select name, min(id) as minid, (#grp := #grp + 1) as group_id
from t cross join
(select #grp := -1) params
group by name
) tt
on not t.name <=> tt.name; -- to handle `NULL`;
You could also do the whole operation with two sorts and variables:
select t.*
from (select t.*,
(#grp := if(not #n <=> name, #grp,
if(#n := name, 1, 1)
)
) as group_id
from t
(select #grp := -1, #n := '') params
order by name
) t
order by id;
The following query orders votes based on how many times users voted... I would like to know the # in the queue of the specific user.
SELECT #s:=#s+1 serial_number, user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`,(SELECT #s:= 0) AS s
GROUP BY slug_owner
ORDER BY cnt DESC
serial_number | user_id | cntÂ
3 | 19 | 8
2 | 14 | 4
1 | 13 | 2
Essentially i need the numbers in the serial_number column to be reversed so I can tell that user 13 is #3 based on votes ..
Assign the serial numbers after generating the ordered count:
SELECT #s:=#s+1 serial_number, temp.*
FROM (
SELECT user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`,
GROUP BY slug_owner
ORDER BY cnt DESC
) temp, (SELECT #s:= 0) AS s
The serial numbers are not reversed because GROUP BY and variables don't mix. An ORDER BY is fine. You can use #hjpotter92's solution, but the following also fixes the problem:
SELECT (#rn := #rn + 1) as serial_number, t.*
FROM (SELECT serial_number, user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`
GROUP BY slug_owner
) t CROSS JOIN
(SELECT #rn := 0) params
ORDER BY cnt DESC;
I think the performance is the same. I am offering this answer just to clarify what the actual problem is.
How to group rows in mysql to be as chunks with a specific size ?
I have a table called users
Which has these fields id name
I want to make a query which group users into chucks with the size of 3 names per row.
Sample table:
1 name1
2 name2
3 name4
4 name5
5 name5
6 name6
7 name7
result should be
name1,name2,name3
name4,name5,name6
name7
Usign GROUP_CONCAT of course to do that
http://sqlfiddle.com/#!9/a6b42/5
SELECT GROUP_CONCAT(name),
(IF(#i = 3 OR #i IS NULL, #i:=1, #i:=#i+1 ))as idx,
(IF(#gr_idx IS NULL, #gr_idx:=1, IF(#i = 1, #gr_idx:=#gr_idx+1,#gr_idx) )) as gr_idx
FROM users
GROUP BY gr_idx
You can use modulo arithmetic and conditional aggregation to get three columns:
select max(case when mod(id, 3) = 1 then name end),
max(case when mod(id, 3) = 2 then name end),
max(case when mod(id, 3) = 0 then name end)
from users u
group by floor((id - 1) / 3);
Alternatively, if you only want one column, use group_concat():
select group_concat(name) as names
from users u
group by floor((id - 1) / 3);
Both these solutions assume that id increases by 1 with no gaps. If not, there are ways to assign a sequential number using variables.
SELECT
GROUP_CONCAT(name SEPARATOR ',')
FROM users
GROUP BY
floor((id - 1) / 3);
Here is an SQL fiddle demonstrating this: http://sqlfiddle.com/#!9/f3158/2/0
If the IDs are not ascending or not succeeding then this query may be it:
SELECT names from (
SELECT GROUP_CONCAT(name) as names,
#rownum := #rownum + 1 AS rank
FROM users u,
(SELECT #rownum := 0) r
GROUP BY floor(#rownum / 3)
) _users ;
Fiddle again: http://sqlfiddle.com/#!9/f3158/13/0
I have this table
id fruit
---------
1 apple
2 banana <--
3 apple
4 apple
5 apple
6 apple
7 banana <----
8 apple
9 banana
10 apple
And I want to select rows until 2 bananas are found, like
SELECT id FROM table_fruit UNTIL number_of_bananas = 2
So the result would be 1,2,3,4,5,6,7
How could I achieve this?
thanks
I wish I could give credits to all of you who answered my question. I'v tested all of them, and they all work perfectly (got the expected result).
Though answers of Devart and ypercube seem a little bit complex and difficult for me to understand.
And since AnandPhadke was the first one provided a working solution, I'll choose his answer as accepted.
You guys are awesome, thanks!
Try this query -
SELECT id, fruit FROM (
SELECT
b.*, #b:=IF(b.fruit = 'banana', 1, 0) + #b AS banana_number
FROM
bananas b,
(SELECT #b := 0) t
ORDER BY id) t2
WHERE
banana_number < 2 OR banana_number = 2 AND fruit = 'banana'
SQLFiddle demo
select * from tables where id <=
(
select id from (
select id from tables where fruit='banana'
order by id limit 2) a order by id desc limit 1
)
SQLFIDDLE DEMO
#Devart's answer is perfect but it's an alternative option to we can use:
SELECT * FROM table_fruit WHERE id <=
(
SELECT id FROM
(SELECT id FROM table_fruit WHERE fruit='banana' ORDER BY id LIMIT 2) a
ORDER BY ID DESC LIMIT 1
);
Or using MAX
SELECT * FROM table_fruit WHERE id <=
(
SELECT MAX(id) FROM
(SELECT id FROM table_fruit WHERE fruit='banana' ORDER BY id LIMIT 2) a
);
See this SQLFiddle
select * from table_fruit where id <=
(
select max(id) from
(select id from table_fruit where fruit='banana' order by id limit 2) t
)
If there are less than 2 rows with 'banana', this will return all rows of the table:
SELECT t.*
FROM table_fruit AS t
JOIN
( SELECT MAX(id) AS id
FROM
( SELECT id
FROM table_fruit
WHERE fruit = 'banana'
ORDER BY id
LIMIT 1 OFFSET 1
) AS lim2
) AS lim
ON t.id <= lim.id
OR lim.id IS NULL ;
Okay i am trying to create a mysql query that does this:
show 3 random records from table then after the 3th record show TEXT
and then show the same 3 items but other field (equaling to the items ofcourse) from same table.
eg table info:
--ids | titles------
10 | one
20 | two
30 | three
and the query results from the given example:
30 10 20 TEXT three one two
if anyone understand what i am asking,post your suggestion/asnwer
thanks for your time all :)
Just for kicks..
select t1.id, t2.id, t3.id, 'TEXT', t1.title, t2.title, t3.title
FROM
(
select #r := #r + 1 rownum, id
from (select #r:=0) initvar, (
select id
from tbl
order by rand()
limit 3
) X
) Y
join tbl t1 on Y.rownum=1 and t1.id = Y.id
join tbl t2 on Y.rownum=2 and t2.id = Y.id
join tbl t3 on Y.rownum=3 and t3.id = Y.id
You should really just do the query below, and do whatever display processing using the 3 rows returned, in whatever programming environment you use (Java/PHP/.Net etc).
select id, title
from tbl
order by rand()
limit 3
EDIT
To get the data in 7 different rows, you can use the below. I stress again that this is front-end display code. I will not use such SQL code in a production system.
select display
from
(
select sorter, rownum,
case when sorter=3 then title else id end display
from
(
select #r := #r + 1 rownum, id, title
from (select #r:=0) initvar,
(
select id, title
from tbl
order by rand()
limit 3
) X
) Y, (select 1 sorter union all select 3) dup
union all
select 2, 0, 'TEXT'
) Z
order by sorter, rownum
Example Output
7
2
1
TEXT
test 7 << title for id=7
test 2
test 1