MySQL get n rows per key - mysql

Given a very simple table bits:
ID | bit_id | timestamp | percent | delta
where bitid is varchar(255) and timestamp is a bigint.
There are many, many rows with identical bit_id's. ID is primary and timestamp is not unique.
With the following SQL i get the rows for a specific set of bit_id's:
SELECT bits.bit_id, bits.timestamp, bits.percent
FROM bits
WHERE bits.bit_id IN ( '00e04c0353bc', '00e04c02c749' )
AND bits.timestamp>1480075040
ORDER BY bits.timestamp DESC
What i want is only the 5 latest rows, per bit_id that match the WHERE-statement. So for each given bit_id in the subset, i want the 5 newest rows.
So simply adding LIMIT n won't do.
How? My MySQL-version does not work with LIMIT in sub-selects.

If you have only two values, then the easiest way is union all:
(SELECT b.bit_id, b.timestamp, b.percent
FROM bits b
WHERE b.bit_id = '00e04c0353bc'
b.timestamp > 1480075040
ORDER BY b.timestamp DESC
LIMIT 5
) UNION ALL
(SELECT b.bit_id, b.timestamp, b.percent
FROM bits b
WHERE b.bit_id = '00e04c02c749'
b.timestamp > 1480075040
ORDER BY b.timestamp DESC
LIMIT 5
);
I'm not sure if the WHERE condition on timestamp is necessary.
If you wanted to do this for more bit_ids or all of them, then variables might be simpler:
select b.*
from (select b.*,
(#rn := if(#b = bit_id, #rn + 1,
if(#b = bit_id, 1, 1)
)
) as rn
from bits b cross join
(select #b := '', #rn := 0) params
order by b.bit_id, b.timestamp desc
) b
where rn <= 5;

Related

Ordering records by rand() in sql that has multiple union of 2 tables

I am making multiple unions on the same tables
however i need to order the records of the second table by rand()
keeping in mind that I DO NOT want to have duplicate records since Iam using order by rand()
Example:
news table has the following data: (test1,test2,test3)
ads table has the following data: (ads1,ads2,ads3)
The result should be like this:
news are sorted by id
ads are sorted by rand() : which means ads2 may comes in the top of the list, and maybe ads1 comes in the top of the list and so on..
This is my sql statement:
(select news.title
from news
order by news.id desc limit 6) union
(select
advertisements.title
from advertisements
order rand() limit 1,1)
union
(select
news.title,
from news
order by news.id desc limit 6,6)
union
(select
advertisements.title
from advertisements
order by rand() limit 2,1)
Near as I can tell, you seem to want 6 news articles followed by an advertising one, and then repeated again. This is not what your query does, but I'm guessing that is the intention in using union.
I would suggest enumerating the values and then doing the sort outside:
select title
from ((select n.title, #rn := #rn + 1, 'n' as which, id
from news n cross join (select #rn := 0) params
order by n.id desc
limit 12
)
union all
(select a.title, (#rna := #rna + 1) as rn, 'a', NULL
from advertisements a cross join (select #rna := 0) params
order rand()
limit 2
)
) na
order by (case when which = 'n' and rn <= 6 then 1
when which = 'a' and rn = 1 then 2
when which = 'n' and rn <= 12 then 3
when which = 'a' and rn = 1 then 4
end),
id desc;

mysql - Get top 5 records for each group of grouped results

select *,COUNT(feed_id) from
(SELECT feed_contents.*, feed.feed_url, feed.lang_direction, feed.feed_title
FROM feed_contents
INNER JOIN feed ON feed_contents.feed_id = feed.feed_id
INNER JOIN user_feeds ON feed_contents.feed_id = user_feeds.feed_id
WHERE user_feeds.user_id = 13
AND DATE(feed_contents.content_date) >= CURDATE() - INTERVAL 90 DAY
ORDER BY feed_contents.content_date desc) as tbl
group by feed_id
order by content_date desc
limit 0,20
i have this query to get results from multiple tables,
in result it returning one record against feed_id,
i want 5 records against each feed_is
output is like
http://screencast.com/t/HHxNOOSdSX4S
i want max 5 from each
You can use variables for this:
SELECT feed_id, content_date, feed_url, lang_direction, feed_title
FROM (
SELECT feed_id, content_date, feed_url, lang_direction, feed_title,
#rn := IF(#fid = feed_id, #rn + 1,
IF(#fid := feed_id, 1, 1)) AS rn
FROM (
SELECT feed_contents.feed_id,
feed_contents.content_date,
feed.feed_url,
feed.lang_direction,
feed.feed_title
FROM feed_contents
INNER JOIN feed ON feed_contents.feed_id = feed.feed_id
INNER JOIN user_feeds ON feed_contents.feed_id = user_feeds.feed_id
WHERE user_feeds.user_id = 13 AND
DATE(feed_contents.content_date) >= CURDATE() - INTERVAL 90 DAY) AS tbl
CROSS JOIN (SELECT #rn := 0, #fid := 0) AS vars
ORDER BY feed_id, content_date DESC) AS s
WHERE s.rn <= 5
Variable #rn is used to enumerate records within each feed_id partition. Once feed_id value changes, #rn is being reset to 1, so as to start counting for the next partition. Records are numbered in descending order according to field content_date.

SELECT Current and Previous row WHERE condition

id value
---------
1 a
2 b
3 c
4 a
5 t
6 y
7 a
I want to select all rows where the value is 'a' and the row before it
id value
---------
1 a
3 c
4 a
6 y
7 a
I looked into
but I want to get all such rows in one query.
Please help me start
Thank you
I think the easiest way might be to use variables:
select t.*
from (select t.*,
(rn := if(value = 'a', 1, #rn + 1) as rn
from table t cross join
(select #rn := 0) params
order by id desc
) t
where rn in (1, 2)
order by id;
An alternative method uses a correlated subquery to get the previous value and then uses this in the where clause:
select t.*
from (select t.*,
(select t2.value
from table t2
where t2.id < t.id
order by t2.id desc
limit 1
) as prev_value
from table t
) t
where value = 'a' or prev_value = 'a';
With an index on id, this might even be faster than the method using variables.

MySql - How get value in previous row and value in next row? [duplicate]

This question already has answers here:
How to get next/previous record in MySQL?
(23 answers)
Closed 4 years ago.
I have the following table, named Example:
id(int 11) //not autoincriment
value (varchar 100)
It has the following rows of data:
0 100
2 150
3 200
6 250
7 300
Note that id values are not contiguous.
I've written this SQL so far:
SELECT * FROM Example WHERE id = 3
However, I don't know how to get the value of previous id and value of the next id...
Please help me get previous value and next value if id = 3 ?
P.S.: in my example it will be: previous - 150, next - 250.
Select the next row below:
SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1
Select the next row above:
SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1
Select both in one query, e.g. use UNION:
(SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1)
UNION
(SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1)
That what you mean?
A solution would be to use temporary variables:
select
#prev as previous,
e.id,
#prev := e.value as current
from
(
select
#prev := null
) as i,
example as e
order by
e.id
To get the "next" value, repeat the procedure. Here is an example:
select
id, previous, current, next
from
(
select
#next as next,
#next := current as current,
previous,
id
from
(
select #next := null
) as init,
(
select
#prev as previous,
#prev := e.value as current,
e.id
from
(
select #prev := null
) as init,
example as e
order by e.id
) as a
order by
a.id desc
) as b
order by
id
Check the example on SQL Fiddle
May be overkill, but it may help you
please try this sqlFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.value < e1.value
ORDER BY value DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.value > e1.value
ORDER BY value ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
Edit: OP mentioned to grab value of previous id and value of next id in one of the comments so the code is here SQLFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.id < e1.id
ORDER BY id DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.id > e1.id
ORDER BY id ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
SELECT *,
(SELECT value FROM example e1 WHERE e1.id < e.id ORDER BY id DESC LIMIT 1 OFFSET 0) as prev_value,
(SELECT value FROM example e2 WHERE e2.id > e.id ORDER BY id ASC LIMIT 1 OFFSET 0) as next_value
FROM example e
WHERE id=3;
And you can place your own offset after OFFSET keyword if you want to select records with higher offsets for next and previous values from the selected record.
Here's my solution may suit you:
SELECT * FROM Example
WHERE id IN (
(SELECT MIN(id) FROM Example WHERE id > 3),(SELECT MAX(id) FROM Example WHERE id < 3)
)
Demo: http://sqlfiddle.com/#!9/36c1d/2
A possible solution if you need it all in one row
SELECT t.id, t.value, prev_id, p.value prev_value, next_id, n.value next_value
FROM
(
SELECT t.id, t.value,
(
SELECT id
FROM table1
WHERE id < t.id
ORDER BY id DESC
LIMIT 1
) prev_id,
(
SELECT id
FROM table1
WHERE id > t.id
ORDER BY id
LIMIT 1
) next_id
FROM table1 t
WHERE t.id = 3
) t LEFT JOIN table1 p
ON t.prev_id = p.id LEFT JOIN table1 n
ON t.next_id = n.id
Sample output:
| ID | VALUE | PREV_ID | PREV_VALUE | NEXT_ID | NEXT_VALUE |
|----|-------|---------|------------|---------|------------|
| 3 | 200 | 2 | 150 | 4 | 250 |
Here is SQLFiddle demo
This query uses a user defined variable to calculate the distance from the target id, and a series of wrapper queries to get the results you want. Only one pass is made over the table, so it should perform well.
select * from (
select id, value from (
select *, (#x := ifnull(#x, 0) + if(id > 3, -1, 1)) row from (
select * from mytable order by id
) x
) y
order by row desc
limit 3
) z
order by id
See an SQLFiddle
If you don't care about the final row order you can omit the outer-most wrapper query.
If you do not have an ID this has worked for me.
Next:
SELECT * FROM table_name
WHERE column_name > current_column_data
ORDER BY column_name ASC
LIMIT 1
Previous:
SELECT * FROM table_name
WHERE column_name < current_column_data
ORDER BY column_name DESC
LIMIT 1
I use this for a membership list where the search is on the last name of the member. As long as you have the data from the current record it works fine.

Trying to use ID in MySQL SubSubQuery

So I'll show you what I'm trying to do and explain my problem, there may be an answer different to the approach I'm trying to take.
The query I'm trying to perform is as follows:
SELECT *
FROM report_keywords rk
WHERE rk.report_id = 231
AND (
SELECT SUM(t.conv) FROM (
SELECT conv FROM report_keywords t2 WHERE t2.campaign_id = rk.campaign_id ORDER BY conv DESC LIMIT 10
) t
) >= 30
GROUP BY rk.campaign_id
The error I get is
Unknown column 'rk.campaign_id' in 'where clause'
Obviously this is saying that the table alias rk is not making it to the subsubquery. What I'm trying to do is get all of the campaigns where the sum of the top 10 conversions is greater than or equal to 30.
The relevant table structure is:
id INT,
report_id INT,
campaign_id INT,
conv INT
Any help would be greatly appreciated.
Update
Thanks to Kickstart I was able to do what I wanted. Here's my final query:
SELECT campaign_id, SUM(conv) as sum_conv
FROM (
SELECT campaign_id, conv, #Sequence := if(campaign_id = #campaign_id, #Sequence + 1, 1) AS aSequence, #campaign_id := campaign_id
FROM report_keywords
CROSS JOIN (SELECT #Sequence := 0, #campaign_id := 0) Sub1
WHERE report_id = 231
ORDER BY campaign_id, conv DESC
) t
WHERE aSequence <= 10
GROUP BY campaign_id
HAVING sum_conv >= 30
Possibly use a user variable to add a sequence number to get the latest 10 records for each one, then use SUM to get the count of those.
Something like this:-
SELECT rk.*
FROM report_keywords rk
INNER JOIN
(
SELECT campaign_id, SUM(conv) AS SumConv
FROM
(
SELECT campaign_id, conv, #Sequence := if(campaign_id = #campaign_id, #Sequence + 1, 1) AS aSequence, #campaign_id := campaign_id
FROM report_keywords
CROSS JOIN (SELECT #Sequence := 0, #campaign_id := "") Sub1
ORDER BY campaign_id, conv
) Sub2
WHERE aSequence <= 10
GROUP BY campaign_id
) Sub3
ON rk.campaign_id = Sub3.campaign_id AND Sub3.SumConv >= 30
WHERE rk.report_id = 231