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
Related
I want to get all posts in my database and have them returned, grouped by date (year -> month -> day -> posts for this day).
My current database structure:
ID, author, title, content, created_at
And possible values can be:
1, 1, "hello world", "This is the content", "2020-07-27 09:15:57"
2, 1, "Another post", "Another post content", "2020-07-27 11:09:55"
3, 1, "Third post", "No content", "2020-07-28 08:15:20"
...etc
What i want to achieve is, getting all these posts, but group them by date (year, month, day).
i.e.:
[2020] => [
[7] => [
[27] => [
[ID] => 1,
[ID] => 2,
],
[28] => [
[ID] => 3,
]
]
]
What i've tried so far:
SELECT id FROM my_posts_table WHERE author = 1 GROUP BY created_at ORDER BY created_at ASC
AND
SELECT id, YEAR(created_at) as year, MONTH(created_at) as month, DAY(created_at) as day FROM my_posts_table WHERE author = 1 GROUP BY year, month, day ORDER BY year, month, day ASC
My expected result would be the above example of what i want to achieve. But what i'm actually getting is:
[0] => [
[ID] => 1,
[year] => 2020,
[month] => 7,
[day] => 27
],
[1] => [
[ID] => 3,
[year] => 2020,
[month] => 7,
[day] => 28
]
I am completely confused at this moment because i thought 'GROUP BY' was working as i expected, but it appears to be working in a different way.
Can you guys point me in the right direction? I hope it's clear what i'm trying to achieve.
But if it's not, here is it again: "I want to get all the posts from my_posts_table where author_id = 1 and have them returned by year -> month -> day -> posts", So every post on day "27" should be under the [27] key..
EDIT: I made a SQLFiddle as requested
Use GROUP_CONCAT
For your SQLFiddle example try this:
SELECT GROUP_CONCAT(id),
YEAR(created_at) as year, MONTH(created_at) as month, DAY(created_at) as day
FROM my_posts_table
WHERE author = 1
GROUP BY year, month, day ORDER BY year, month, day ASC
A database query always returns a "flat" resultset, made of rows and columns. If you want some a nested datastructure, then one approach is to write a scalar query, that returns just one rows and one column containing a JSON object.
This would involve several levels of aggregation, and JSON functions JSON_ARRAYAGG() and JSON_OBJECT():
select json_object(created_year, json_arrayagg(obj)) obj
from (
select
created_year,
json_object(created_month, json_arrayagg(obj)) obj
from (
select
year(created_at) created_year,
month(created_at) created_month,
json_object(day(created_at), json_arrayagg(id)) obj
from my_post_table
group by year(created_at), month(created_at), day(created_at)
) t
group by created_year, created_month
) t
I want to get a list of distinct values for each user limited by 3 values per user:
id, user_id, value
1, 1, a
2, 1, b
3, 2, c
4, 1, b
5, 1, d
6, 1, e
expected result:
user_id, values
1, [a,b,d]
2, [c]
is there some way to do this with GROUP BY user_id and DISTINCT?
Edit (based on comments):
We can use user-defined variables to assign row number to various value within a partition of user_id. Eventually, we will filter out this result-set to consider upto 3 rows per user_id only.
SELECT
dt2.user_id,
dt2.value
FROM
(
SELECT
#rn := CASE WHEN #ui = dt.user_id THEN #rn + 1
ELSE 1
END AS row_no,
#ui = dt.user_id,
dt.value
FROM
(
SELECT DISTINCT
user_id,
value
FROM your_table
ORDER BY user_id
) AS dt
CROSS JOIN (SELECT #rn := 0, #ui := null) AS user_init_vars
) AS dt2
WHERE dt2.row_no <= 3
Previous question's answer:
Group_Concat(Distinct...) all the unique value for a user_id.
We can then use Substring_Index() function to consider string upto 3rd comma. This will then result in consideration of upto 3 values only.
At the end, we can use Concat() function to enclose the resultant string in square brackets.
Values is Reserved keyword in MySQL. You can consider naming the resultant column into something else.
Try the following:
SELECT user_id,
CONCAT('[',
SUBSTRING_INDEX(GROUP_CONCAT(DISTINCT value), ',', 3),
']') AS user_values
FROM your_table
GROUP BY user_id
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
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
I have a table of items that users are allowed to vote on. In this table, there is a votes column, that holds the number of votes that item has accumulated, and a rank column, that is a ranking of all items based on the number of votes they have (i.e. most votes gets rank 1, second most gets rank 2, etc.)
Currently, I'm recalculating the rank of every item after every vote. That is, when a user votes, I add one to that item's votes column, and then update every rank with the following query:
SET #rank = 0
UPDATE items SET rank = #rank := #rank + 1 ORDER BY votes DESC
This works for the most part, but doesn't take in to account voting ties. If I have votes [10, 4, 3, 0], I would expect ranks [1, 2, 3, 4]. However, if I have votes [10, 10, 3, 0], I would like ranks [1, 1, 3, 4]. This doesn't happen; I still get ranks [1, 2, 3, 4].
How can I incorporate ties like I've described above?
I wouldn't save the rank in database. You can calculate it while showing the result.
$rank = 1;
$lastVotes = -1;
$lastAdd = 0;
$query = mysql_query("SELECT * FROM table WHERE * ORDER BY votes DESC", $link);
while( $row = mysql_fetch_array( $query ) ) {
// local variable with votes
$votes = $row['votes'];
// check if we have a tie
if( $lastVotes == $votes ) {
// don't change rank if there is a tie but inc $lastAdd
$lastAdd += 1;
} else {
// there is no tie: save last votes, adjust $rank and reset $lastAdd
$lastVotes = $votes;
$rank += $lastAdd;
$lastAdd = 1;
}
// $rank is your rank
}
This inelegant solution would return what you desire:
update ITEMS I1
set rank =
(select count(*)
from ITEMS I2
where I2.VOTES >= I1.VOTES)
- (select count(*) - 1
from ITEMS I3
where I3.VOTES = I1.VOTES)
No mysql instance to try this out on, but how about something like this:
SELECT v.id, v.votes, r.rank
FROM votes v
,(SELECT votes, #rownum = #rownum + 1 AS rank
FROM votes
GROUP BY votes
ORDER BY votes DESC
) r
WHERE v.votes = r.votes
ORDER BY rank
Figure out the distinct set of "votes", order them, give each a number, then use that to associate the number (rank) back to each vote.
Really depends on the table size, and on the freq of updates, but how about a trigger? MySQL- Trigger updating ranking