Fetching 10 items each of child table in mysql - mysql

I have a table categories (id, cat_name) and a table recipes (id, cat_id, recipe_text).
Now I want to write a query, that fetches from each category 10 recipes.
SELECT cat_name, recipe_text
FROM categories c
JOIN recipes r ON c.id=r.cat_id
would fetch ALL recipes, but I want a maximum of 10 recipes per category.
(How) could it be done with a SQL-Query?

Taken from mySQL Returning the top 5 of each category:
SELECT cat_name, recipe_text
FROM
(
SELECT c.cat_name AS cat_name, r.recipe_text AS recipe_text,
#r:=case when #g=c.id then #r+1 else 1 end r,
#g:=c.id
FROM (select #g:=null,#r:=0) n
CROSS JOIN categories c
JOIN recipes r ON c.id = r.cat_id
) X
WHERE r <= 10

You may try the below code
function recipe($cat_id='',$new_ar=array())
{
if($cat_id!="")
{
$new_ar1=array();
$db="select recipe_text from recipes where cat_id=".$cat_id." order by cat_id limit 10";
$sq=mysql_query($db);
while($fe=mysql_fetch_object($sq))
{
array_push($new_ar1,$fe->recipe_text);
}
return $new_ar1;
}
else
{
$db="select id,cat_name from categories order by id";
$sq=mysql_query($db);
while($fe=mysql_fetch_object($sq))
{
array_push($new_ar,$fe->cat_name);
$new_ar[$fe->id]=array($fe->cat_name);
array_push($new_ar[$fe->id],recipe($fe->id,$new_ar));
}
}
}
recipe();
it will give output like below
Array
(
[0] => cat 1
[1] => Array
(
[0] => cat 1
[1] => Array
(
[0] => rice
[1] => curry
[2] => mutton
[3] => A recipe
[4] => B recipe
[5] => C recipe
[6] => D recipe
[7] => E recipe
[8] => F recipe
[9] => G recipe
)
)
[2] => Array
(
[0] => cat 2
[1] => Array
(
[0] => dal
[1] => fish
)
)
[3] => Array
(
[0] => cat 3
[1] => Array
(
)
)
[4] => Array
(
[0] => cat 4
[1] => Array
(
)
)
)
Thanks
Ripa Saha

this will do for u...
SET #rank = 0;
SELECT cat_name,recipe_text FROM (SELECT
id,cat_id AS P,recipe_text,
CASE
WHEN #rowdata != pid
THEN #rank := 0 & #rowdata := cat_id
ELSE #rank := #rank + 1
END AS rank
FROM
recipes
GROUP BY cat_id,
id
) X ,t
WHERE X.rank <= 3 AND X.p = t.cat_id;

Try below SQL
select
cat_name,
recipe_text
from categories as c,recipes as r
where c.id=r.cat_id limit 10

We finally made a stored procedure:
DELIMITER $$
CREATE PROCEDURE `test`()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE cur_id INT;
DECLARE cur CURSOR FOR
SELECT id FROM category;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TABLE tmp(catname VARCHAR(255), recipeId INT);
OPEN cur;
the_loop: LOOP
FETCH cur INTO cur_id;
IF done THEN
LEAVE the_loop;
END IF;
INSERT INTO tmp(catname, recipeId)
SELECT catname, recipeId
FROM category c LEFT JOIN recipes r ON c.id=r.cat_Id
WHERE c.id=cur_id
LIMIT 0, 10;
END LOOP;
SELECT * FROM tmp;
DROP TABLE tmp;
END

SET #num :=0, #cat_id := '';
SELECT recipe_text,cat_name,
#num := if(#cat_id = cat_id, #num + 1, 1) as row_number,
#cat_id := cat_id as cat_id
FROM recipes r
JOIN categories c ON c.id = r.cat_id
group by cat_name,recipe_text
having row_number <= 10

Related

How to insert a max value plus one in mysql in opencart

I have the following:
Model File
$max = $this->db->query("SELECT MAX( sort ) FROM lc_menu");
print_r($max);
Printed Values
stdClass Object ( [num_rows] => 1 [row] => Array ( [MAX( sort )] => 64 )
[rows] => Array ( [0] => Array ( [MAX( sort )] => 64 ) ) )
How can I insert max+1 in MySQL query?
You can use subquery:
$this->db->query('INSERT INTO ' . DB_PREFIX . ' menu SET
menuname = "$this->db->escape($data['menuname'])",
start_date = "$this->db->escape($data['start_date'])",
start_time = "$this->db->escape($data['start_time'])",
end_date = "$this->db->escape($data['end_date'])",
end_time = "$this->db->escape($data['end_time'])",
link_value = "$this->db->escape($data['link_value'])",
link = "$this->db->escape($alpha['link'])",
sort = ((SELECT MAX(sort) FROM lc_menu) + 1)');

Get Two matches for each jobs

I have two tables like below
Jobs:
id | user_id | job_title
Matches: id| job_id | user_id
There are one to many relationship between jobs and matches table. One jobs have multiple matches.
I want list of jobs with it's two matches like below :
Jobs => [
'0' => [
'job_title' => 'abc',
'Matches' => [
'0' => [
'id',
'job_id',
'user_id',
],
'0' => [
'id',
'job_id',
'user_id',
]
]
],
'1' => [
'job_title' => 'abc',
'Matches' => [
'0' => [
'id',
'job_id',
'user_id',
],
'0' => [
'id',
'job_id',
'user_id',
]
]
]
]
Please help me on this concern. Thanks in advance.
I have search a lot for this issue and most of the blogs give me same solution like below but it is not work for me.
set #num := 0, #group := '';
select person, `group`, age
from
(
select person, `group`, age,
#num := if(#group = `group`, #num + 1, 1) as row_number,
#group := `group` as dummy
from mytable
order by `Group`, Age desc, person
) as x
where x.row_number <= 2;
You need to extract that data using a join and then to create a variable and save on it the row number(#num).
We also need another variable to save the "group" id (job id in this case) as its necessary to restart the row count when a different job appears.
Finally we filter by #num with as much rows you want. Remember matchNO is a calculated field so you cant filter in a where statment, use having instead.
set #num := 0, #job := 0;
select j.job_title, m.id, m.job_id, m.user_id,IF(#job = j.id,#num:=#num+1,#num:=1) as matchNO,#job:=j.id
from jobs j
left join matches m on j.id = m.job_id
having matchNO <= 2;
The row number is necessary to figure out in what row you are.
The job variable tell you the job in the last row so you can compare and then set #num to 1 again when necesary.
I hope it helps

Store values in different variables in SQL, separated by (Comma) ","

I need to separate values and store them in different variables in SQL,
for example
a='3100,3101,3102,....'
And the output should be
x=3100
y=3101
z=3102
.
.
.
create function [dbo].[udf_splitstring] (#tokens varchar(max),
#delimiter varchar(5))
returns #split table (
token varchar(200) not null )
as
begin
declare #list xml
select #list = cast('<a>'
+ replace(#tokens, #delimiter, '</a><a>')
+ '</a>' as xml)
insert into #split
(token)
select ltrim(t.value('.', 'varchar(200)')) as data
from #list.nodes('/a') as x(t)
return
end
GO
declare #cad varchar(100)='3100,3101,3102'
select *,ROW_NUMBER() over (order by token ) as rn from udf_splitstring(#cad,',')
token rn
3100 1
3101 2
3102 3
The results of the Parse TVF can easily be incorporated into a JOIN, or an IN
Declare #a varchar(max)='3100,3101,3102'
Select * from [dbo].[udf-Str-Parse](#a,',')
Returns
RetSeq RetVal
1 3100
2 3101
3 3102
The UDF if needed (much faster than recursive, loops, and xml)
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
I suggest you to use following query, it's much faster than other functions like cross apply and udf.
SELECT
Variables
,S_DATA
FROM (
SELECT
Variables
,CASE WHEN LEN(LIST2)>0 THEN LTRIM(RTRIM(SUBSTRING(LIST2, NUMBER+1, CHARINDEX(',', LIST2, NUMBER+1)-NUMBER - 1)))
ELSE NULL
END AS S_DATA
,NUMBER
FROM(
SELECT Variables
,','+COMMA_SEPARETED_COLUMN+',' LIST2
FROM Tb1
)DT
LEFT OUTER JOIN TB N ON (N.NUMBER < LEN(DT.LIST2)) OR (N.NUMBER=1 AND DT.LIST2 IS NULL)
WHERE SUBSTRING(LIST2, NUMBER, 1) = ',' OR LIST2 IS NULL
) DT2
WHERE S_DATA<>''
and also you should create a table 'NUMBER' before running the above query.
CREATE TABLE TB (Number INT)
DECLARE #I INT=0
WHILE #I<1000
BEGIN
INSERT INTO TB VALUES (#I)
SET #I=#I+1
END

List all 'feeds' and only 2 users with relationship

I need to make a list of all the items in the table feed and show only the first 2 users who subscribe to the content, but I can not put together a query that does the list only 2 users limit 2.
I've tried N querys and subquery, but could not get the expected result. The nearest was using group_concat, but if it concatenates all users and does not allow limited only to two initial, and would have to usesubstring_index for this purpose.
Query
select
feed.id
, feed.type
, user.name
from feed
inner join user on user.id = feed.user
group by feed.type
Result
Array(
[0] => Array(
[id] => 1
[type] => Comedy
[name] => Mike
)
[1] => Array(
[id] => 3
[type] => War
[name] => John
)
[2] => Array(
[id] => 6
[type] => Terror
[name] => Sophia
)
)
Expected
Array(
[0] => Array(
[id] => 1
[type] => Comedy
[name] => Mike, Papa
)
[1] => Array(
[id] => 3
[type] => War
[name] => John, Alex
)
[2] => Array(
[id] => 6
[type] => Terror
[name] => Sophia, Jessica
)
)
set #rn=0;
select id, type, name
from
(
select
#rn:=#rn+1 AS r_n
,feed.id
,feed.type
,user.name
from feed
inner join user on user.id = feed.user
group by feed.id
order by feed.id) t
where t.r_n <= 2
You can generate row numbers per group and then select the first 2 rows per feed id.
I don't know exactly the schema of your tables but try the same approach you already tried with group_concat but join to a subquery like:
...
inner join
(
select user.id, user.name from user limit 2
) as x on x.id = feed.user
...
You can use variables to simulate row_number() to give each user per feed a "rank" and only select rows with number <= 2 before doing the grouping in order to only get 2 users per group:
select id, type, group_concat(name) from (
select * from (
select *, #rn := if(#prevFeedId = id, #rn+1, 1) as rn,
#prevFeedId := id
from (
select
feed.id
, feed.type
, user.name
from feed
inner join user on user.id = feed.user
) t1 order by id
) t1 where rn <= 2
) t1 group by id, type

MySql Query: Select top 3 rows from table for each category

I have a table with records and it has a row called category. I have inserted too many articles and I want to select only two articles from each category.
I tried to do something like this:
I created a view:
CREATE VIEW limitrows AS
SELECT * FROM tbl_artikujt ORDER BY articleid DESC LIMIT 2
Then I created this query:
SELECT *
FROM tbl_artikujt
WHERE
artikullid IN
(
SELECT artikullid
FROM limitrows
ORDER BY category DESC
)
ORDER BY category DESC;
But this is not working and is giving me only two records?
LIMIT only stops the number of results the statement returns. What you're looking for is generally called analytic/windowing/ranking functions - which MySQL doesn't support but you can emulate using variables:
SELECT x.*
FROM (SELECT t.*,
CASE
WHEN #category != t.category THEN #rownum := 1
ELSE #rownum := #rownum + 1
END AS rank,
#category := t.category AS var_category
FROM TBL_ARTIKUJT t
JOIN (SELECT #rownum := NULL, #category := '') r
ORDER BY t.category) x
WHERE x.rank <= 3
If you don't change SELECT x.*, the result set will include the rank and var_category values - you'll have to specify the columns you really want if this isn't the case.
SELECT * FROM (
SELECT VD.`cat_id` ,
#cat_count := IF( (#cat_id = VD.`cat_id`), #cat_count + 1, 1 ) AS 'DUMMY1',
#cat_id := VD.`cat_id` AS 'DUMMY2',
#cat_count AS 'CAT_COUNT'
FROM videos VD
INNER JOIN categories CT ON CT.`cat_id` = VD.`cat_id`
,(SELECT #cat_count :=1, #cat_id :=-1) AS CID
ORDER BY VD.`cat_id` ASC ) AS `CAT_DETAILS`
WHERE `CAT_COUNT` < 4
------- STEP FOLLOW ----------
1 . select * from ( 'FILTER_DATA_HERE' ) WHERE 'COLUMN_COUNT_CONDITION_HERE'
2. 'FILTER_DATA_HERE'
1. pass 2 variable #cat_count=1 and #cat_id = -1
2. If (#cat_id "match" column_cat_id value)
Then #cat_count = #cat_count + 1
ELSE #cat_count = 1
3. SET #cat_id = column_cat_id
3. 'COLUMN_COUNT_CONDITION_HERE'
1. count_column < count_number
4. ' EXTRA THING '
1. If you want to execute more than one statement inside " if stmt "
2. IF(condition, stmt1 , stmt2 )
1. stmt1 :- CONCAT(exp1, exp2, exp3)
2. stmt2 :- CONCAT(exp1, exp2, exp3)
3. Final "If" Stmt LIKE
1. IF ( condition , CONCAT(exp1, exp2, exp3) , CONCAT(exp1, exp2, exp3) )
share
Use group by instead of order by.