I am trying to create a query that selects 2 random rows for each parent_id from my table.
At the moment my query always returns the first 2 id's for each parent_id. (For example: 1,2 of parent_id=1).
My table is currently as follows:
id, title , parent_id
1, Title1 , 1
2, Title2 , 1
3, Title3 , 1
4, Title4 , 2
5, Title5 , 2
6, Title6 , 2
7, Title7 , 2
8, Title8 , 3
9, Title9 , 3
10, Title10, 3
My current query is:
SELECT id,title,parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
meals.*
FROM meals CROSS JOIN
(SELECT #rn := 0, #parent_id := '') params
ORDER BY rand()
) meals
WHERE rn <= 2
ORDER BY id ASC
I would like my result to change on each query so for example one result will return the id's 1,3 for parent_id=1 and one will return 2,3 and so on...
Try moving the order by rand() clause into its own derived table
SELECT id,title,parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
t1.*
FROM ( SELECT * FROM meals CROSS JOIN
(SELECT #rn := 0, #parent_id := '') params
ORDER BY rand() ) t1
ORDER BY parent_id
) meals
WHERE rn <= 2
ORDER BY id ASC
http://sqlfiddle.com/#!9/3310f/1
I think you just need to add parent_id into the order by:
SELECT id, title, parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
meals.*
FROM meals CROSS JOIN
(SELECT #rn := 0, #parent_id := -1) params
ORDER BY parent_id, rand()
) meals
WHERE rn <= 2
ORDER BY id ASC;
And, if parent_id is a number, there is no reason to make #parent_id a string.
Related
I have mysql table called ware_stock_transaction and it has order_no, order_type, created_date, item_no.
I want to get the last 10 record from each item, like this:
item A (10 records)
item B (10 records)
item C (10 records)
In MySQL, you can use variables:
select wst.*
from (select wst.*,
(#rn := if(#i = item_no, #rn + 1,
if(#i := item_no, 1, 1)
)
) as rn
from ware_stock_transaction wst cross join
(select #rn := 0, #i := '') params
order by item_no, created_date desc
) wst
where rn <= 10;
I am trying to return a limited of number of products per brand. The tables are
brands:
id, name
products:
id, brand_id, name
What I am trying to achieve is a query that will output brands.name and products_name 10 times for each brand.
I have tried joining the two tables but when it comes to applying the limit I can't see the next step. Is this possible or will I have to opt to do the brand query first and then query again on a foreach this being more processor intensive?
Get 10 records per product from the second table by the following query:
SELECT *
FROM(
SELECT id, brand_id, name, #n := IF(#r = brand_id, #n + 1, 1) AS rownum,
#r := brand_id
FROM product, (SELECT #r := 0, #n := 0) a
ORDER BY brand_id, id
) a
WHERE a.rownum <= 10;
And join it with brand table, e.g.:
SELECT *
FROM brand b
JOIN (SELECT *
FROM(
SELECT id, brand_id, name, #n := IF(#r = brand_id, #n + 1, 1) AS rownum,
#r := brand_id
FROM product, (SELECT #r := 0, #n := 0) a
ORDER BY brand_id, id
) a
WHERE a.rownum <= 10
) p on b.id = p.brand_id;
Here's the SQL Fiddle.
I have a below table and wants to select only last 2 entries of all users.
Source table:
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 10 2016-5-12
2 10 2016-5-12
1 11 2016-6-12
2 12 2016-8-12
3 12 2016-8-12
2 13 2016-8-12
1 14 2016-9-12
3 14 2016-9-12
3 11 2016-6-12
Expected output is like, (should list only recent 2 quizid entries for all users)
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 14 2016-9-12
1 11 2016-6-12
2 13 2016-8-12
2 12 2016-8-12
3 14 2016-9-12
3 12 2016-8-12
Any idea's to produce this output.
Using MySQL user defined variables you can accomplish this:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2
Working Demo
Note: If you want at most x number of entries for each user then change the condition in where clause like below:
WHERE t.row_number <= x
Explanation:
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC;
This query sorts all the data in ascending order of userId and descending order of quizendtime(AID).
Now take a walk on this (multi) sorted data.
Every time you see a new userId assign a row_number (1). If you see the same user again then just increase the row_number.
Finally filtering only those records which are having row_number <= 2 ensures the at most two latest entries for each user.
EDIT: As Gordon pointed out that the evaluation of expressions using user defined variables in mysql is not guaranteed to follow the same order always so based on that the above query is slightly modified:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF (
#sameUser = UserId,
#a := #a + 1,
IF(#sameUser := UserId, #a := 1, #a:= 1)
)AS row_number
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2;
WORKING DEMO V2
User-defined variables are the key to the solution. But, it is very important to have all the variable assignments in a single expression. MySQL does not guarantee the order of evaluation of expressions in a select -- and, in fact, sometimes processes them in different orders.
select t.*
from (select t.*,
(#rn := if(#u = UserId, #rn + 1,
if(#u := UserId, 1, 1)
)
) as rn
from t cross join
(select #u := -1, #rn := 0) params
order by UserId, quizendtime desc
) t
where rn <= 2;
I have a table:
id name
1 a
2 a
3 a
4 b
5 b
6 c
I am looking for an update statement that will update name column to:
id name
1 a
2 a-2
3 a-3
4 b
5 b-2
6 c
In SQL Server I would use:
;with cte as(select *, row_number() over(partition by name order by id) rn from table)
update cte set name = name + '-' + cast(rn as varchar(10))
where rn <> 1
I am not strong in MySQL nonstandard queries.
Can I do something like this in MySQL?
You can do this:
UPDATE YourTable p
JOIN(SELECT t.id,t.name,count(*) as rnk
FROM YourTable t
INNER JOIN YourTable s on(t.name = s.name and t.id <= s.id)
GROUP BY t.id,t.name) f
ON(p.id = f.id)
SET p.name = concat(p.name,'-',f.rnk)
WHERE rnk > 1
This will basically use join and count to get the same as ROW_NUMBER() , and update only those who have more then 1 result(meaning the second,third ETC excluding the first)
In MySQL you can use variables in order to simulate ROW_NUMBER window function:
SELECT id, CONCAT(name, IF(rn = 1, '', CONCAT('-', rn))) AS name
FROM (
SELECT id, name,
#rn := IF(name = #n, #rn + 1,
IF(#n := name, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #n := '') AS vars
ORDER BY name, id) AS t
To UPDATE you can use:
UPDATE mytable AS t1
SET name = (
SELECT CONCAT(name, IF(rn = 1, '', CONCAT('-', rn))) AS name
FROM (
SELECT id, name,
#rn := IF(name = #n, #rn + 1,
IF(#n := name, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #n := '') AS vars
ORDER BY name, id) AS t2
WHERE t1.id = t2.id)
Demo here
You can also use UPDATE with JOIN syntax:
UPDATE mytable AS t1
JOIN (
SELECT id, rn, CONCAT(name, IF(rn = 1, '', CONCAT('-', rn))) AS name
FROM (
SELECT id, name,
#rn := IF(name = #n, #rn + 1,
IF(#n := name, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #n := '') AS vars
ORDER BY name, id) AS x
) AS t2 ON t2.rn <> 1 AND t1.id = t2.id
SET t1.name = t2.name;
The latter is probably faster than the former because it performs less UPDATE operations.
The next query will do it with less effort for the database:
UPDATE
tab AS tu
INNER JOIN
-- result set containing only duplicate rows that must to be updated
(
SELECT
t.id,
COUNT(*) AS cnt
FROM
tab AS t
-- join the same table by smaller id and equal value. That way you will exclude rows that are not duplicated
INNER JOIN
tab AS tp
ON
tp.name = t.name
AND
tp.id < t.id
GROUP BY
t.id
) AS tc
ON
tu.id = tc.id
SET
tu.name = CONCAT(tu.name, '-', tc.cnt + 1)
How can I limit result per each id in WHERE clause?
My query is:
SELECT name
FROM location_areas
WHERE parent IN ("1,2,3")
ORDER BY popularity,name
Parent is not unique.
I need to get 10 results for each parent id in WHERE clause.
for example table structure is:
id name parent
1 name 0
2 name 1
3 name 1
4 name 80
5 name 80
6 name 80
7 name 80
8 name 1
Try this:
SELECT
T.name,
T.popularity,
T.parent,
T.rank
FROM
(
SELECT
L.name,
L.popularity,
L.parent,
#rank := IF(#parent = parent, #rank + 1, 1) rank,
#parent := parent
FROM location_areas L,
(SELECT #rank := 1, #parent := NULL) R
) T
WHERE T.rank <= 10
EDIT
SELECT T.name, T.popularity, T.parent, T.level, T.rank
FROM (
SELECT L.name, L.popularity,
L.parent, L.level,
#rank := IF(#parent = parent, #rank + 1, 1) rank,
#parent := parent
FROM location_areas L,
(SELECT #rank := 1, #parent := NULL) R
WHERE L.parent IN (".$ids.")
) T WHERE T.rank <= 10;
You can simply do it lie this
SET #level = 0;
SET #group = '';
SELECT
name
FROM (
SELECT
name ,
parent
#level := IF(#group = parent, #level+1, 1) AS level,
#group := parent as EGroup
FROM test
WHERE parent IN ("1,2,3")
ORDER BY parent
) rs
WHERE level < 11