Limit the amount of results from mySQL conditionally? - php

Here is my query:
SELECT * FROM Photos WHERE Event_ID IN ($eventidstring)
I know I can limit the total amount of results from this query using LIMIT 5
I need the Limit the amount of results Per value in $eventidstring.
So if $eventidstring = 23,41,23*
*And there are 10 results WHERE Event_ID = 23, I want to limit this amount to 5. The same for all the other values in $eventidstring.

You may have some joy doing something similar to Oracle's RANK by PARITION in MySQL.
Sadly this feature is not available in MySQL though you can work around it using this method
Dump that in an inline view and then select those rows with rank <= 5;
Hence:
SELECT t.* FROM (
SELECT (
CASE Event_id
WHEN #curEventId
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curEventId := Event_Id END
) AS rank,
p.*
FROM Photos p INNER JOIN (SELECT #curRow := 0, #curEventId := '') r
ORDER BY p.Event_Id DESC
) t
WHERE t.rank <= 5 ORDER BY t.Event_Id asc;
Consider how you are going to 'choose' the top five by Event_Id too. You can always add in more after the ORDER BY p.Event_Id DESC to decide this.

I take it you're writing that query somewhere inside your PHP, so you need to split the $eventidstring into it's component values, form a SELECT for each and UNION all after the first one.
You sould do this with a loop of some sort, and concatenate the query strings in the loop...

If I understand correctly and you want to get five of each, you can use this:
(SELECT * FROM Photos WHERE Event_ID = 23 LIMIT 5)
UNION
(SELECT * FROM Photos WHERE Event_ID = 41 LIMIT 5)
UNION
(SELECT * FROM Photos WHERE Event_ID = ... LIMIT 5)
...

Maybe with a SELECT UNION but you need a new select for each value:
SELECT * FROM Photos WHERE Event_ID = 23 LIMIT 5
UNION SELECT * FROM Photos WHERE Event_ID = 41 LIMIT 5
UNION SELECT ...

Related

Limit each element in "IN" operator Mysql [duplicate]

I am struggling to find an optimal solution for the following problem.
Suppose I have a table 'Table' like this:
id name report_id
1 name1 1
2 name2 3
3 name3 5
4 name1 7
5 name3 8
....................
I want to select for each value in a set: ('name1', 'name2') 10 random unique rows.
Of course it is possible to do with union like:
(SELECT * FROM Table
WHERE
name='name1'
ORDER BY RAND() LIMIT 10)
UNION
(SELECT * FROM Table
WHERE
name='name2'
ORDER BY RAND() LIMIT 10)
But if I have 100 unique names for which I have to select 10 random records - this query is going to be a bit large.
SQLFiddle demo
select ID,NAME,REPORT_ID
from
(
select *, #row:=if(name=#name,#row,0)+1 as rn, #name:=name from
(select *,RAND() as trand from t) t1,
(select #row:=0,#name:='') tm2
order by name,trand
) t2
where rn<=10
Try this:
SELECT
id,
name,
report_id
FROM
(
SELECT id,
report_id,
name,
CASE WHEN #name != name THEN #rn := 1 ELSE #rn := #rn + 1 END rn,
#name:=name
FROM (SELECT * FROM tbl ORDER BY RAND()) a,
(SELECT #rn:=0, #name := NULL) r
ORDER BY name
) s
WHERE rn <= 10;
SQL FIDDLE DEMO
This doesn't work in MySQL, but in PostgreSQL you can use partition by
select name,report_id from
(select name,report_id,row_number()
over
(partition by name order by random())
as rn from Table) a
where rn<=10
I had this same question and found this answer from a colleague.

How break ties after getting rank

I have a query that gets me a users rank in a table of scores.
SELECT
*
FROM
(SELECT
*, #rank:=#rank + 1 rank
FROM
(SELECT
user_id, SUM(round_total) TotalPoints
FROM
sx14sp_mem_picks
GROUP BY user_id) s, (SELECT #rank:=0) init
ORDER BY TotalPoints DESC) r
WHERE
user_id = 22234
There is a problem with ties. I have a table field "pick_date" that i would like to use to break ties with. The user who made his picks first beats the tie.
Any ideas?
If sx14sp_mem_picks.pickdate is the field to break ties then in the order by sx14sp_mem_picks subquery, add
min( pickdate) asc
This will put the earliest pickdate first - you have to use MIN() bc you need to use an aggregate function given the use of "group by".
You need to order by the pick date in addition to the total points. However, you are talking about multiple rows per user. So, let's take the last pick date:
SELECT *
FROM (SELECT *, (#rank:=#rank + 1) as rank
FROM (SELECT user_id, SUM(round_total) as TotalPoints, max(pick_date) as max_pick_date
FROM sx14sp_mem_picks
GROUP BY user_id
) s CROSS JOIN
(SELECT #rank := 0) init
ORDER BY TotalPoints DESC, max_pick_date asc
) r
WHERE user_id = 22234;

GROUP BY this OR that

I've got a web app that uses either Facebook ID or Contact # as an entrant identifier. This is because the client doesn't want those without Facebook to miss out on entering (so there's a web app as well as a Facebook app)
So I'm trying to:
SELECT *, COUNT(*)
FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY fbid OR contact_no
HAVING COUNT(*) <=4
Basically, what I'm trying to achieve is selecting entries with is_daily_winner = 2 is less than or equal to 4 rows per fbid or contact_no.
Currently I only get one row back, rather than the 3 I was expecting.
What am I doing wrong? or can I even GROUP BY x OR y ?
EDIT: I'm expanding this question, as I forgot to include another clause.
The HAVING COUNT(*) <=4 should be across all entries, but I only need to get those entered within the last day.
I've just tried:
SELECT * FROM `entries_table`
WHERE `timestamp` >= (CURDATE() - INTERVAL 1 DAY) IN
(SELECT * FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY fbid
HAVING COUNT(*) <=4
UNION
SELECT * FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY contact_no
HAVING COUNT(*) <= 4)
And I get the error Operand should contain 1 column(s)
This one should do what you want:
SELECT * FROM (
SELECT *, COUNT(*) AS nr FROM `entries_table`
WHERE is_daily_winner = 2
AND `timestamp` >= (CURDATE() - INTERVAL 1 DAY)
GROUP BY fbid
UNION ALL
SELECT *, COUNT(*) AS nr FROM `entries_table`
WHERE is_daily_winner = 2
AND `timestamp` >= (CURDATE() - INTERVAL 1 DAY)
GROUP BY contact_no
) sq
WHERE nr <= 4
I changed UNION to UNION ALL, because UNION implies a DISTINCT. I don't think, that's what you want (or even need, UNION ALL is also faster, because it doesn't have to check if there are duplicate rows).
And note, that selecting * and grouping by something is actually bad practice. By grouping and not having aggregate functions on the columns that are not mentioned in the group by, random rows are picked for each group to display. Just wanted to mention that.
You can Better use UNION like
SELECT *, COUNT(*)
FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY fbid
HAVING COUNT(*) <=4
UNION
SELECT *, COUNT(*)
FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY contact_no
HAVING COUNT(*) <=4

Not In mysql subquery Limit

Does anybody have any ideas how I can get around a #1235 - This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' error?
My query is below ( I've read that I can upgrade mysql but this isn't possible):
$query = #mysql_query("SELECT * FROM posts
WHERE postid NOT IN
( SELECT postid FROM log
ORDER BY posted DESC
LIMIT 10)
ORDER BY (RAND() * Multiplier)
LIMIT 1");
According to this bug, you can use this ugly workaround:
SELECT * FROM t1 WHERE s1 NOT IN
(SELECT * FROM (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1) AS alias)
You can rewrite your query using JOIN:
SELECT *
FROM posts NATURAL LEFT JOIN (
SELECT postid FROM log ORDER BY posted DESC LIMIT 10
) t
WHERE t.postid IS NULL
ORDER BY RAND()
LIMIT 1
Be aware, however, that ORDER BY RAND() is very expensive. Not only must a random value be calculated for each record, but then a sort must be performed on the results. Indexes are of no use.
You would fare better if you had a column col containing unique integers, then with an index on col you can very rapidly obtain a random record with:
SELECT *
FROM posts NATURAL LEFT JOIN (
SELECT postid FROM log ORDER BY posted DESC LIMIT 10
) t JOIN (
SELECT RAND() * MAX(col) AS rand FROM posts
) r ON posts.col >= r.rand
WHERE t.postid IS NULL
LIMIT 1
Note that the uniformity of such "randomness" will depend on the distribution of the integers within col after any other filtering has taken place.

Different ORDER BY for each SELECT in a UNION with MySQL

Using PHP and MySQL, is there a way to use a different ORDER BY for each of the SELECT statements in a UNION?
SELECT * FROM the_table WHERE color = 'blue' ORDER BY price ASC LIMIT 5
UNION ALL
SELECT * FROM the_table WHERE color = 'red' ORDER BY RAND() LIMIT 10
The above statement does not work. It seems you can only do an ORDER BY on the final result set. Is there a way to do an ORDER BY on the first SELECT then a different ORDER BY on the second SELECT using UNION?
(SELECT * FROM the_table WHERE color = 'blue' ORDER BY price ASC LIMIT 5)
UNION ALL
(SELECT * FROM the_table WHERE color = 'red' ORDER BY RAND() LIMIT 10)
Please note that this does not work if you don't specify a LIMIT (though you can specify a very large dummy limit). See mysql documentation (13.2.7.3. UNION Syntax):
"Use of ORDER BY for individual SELECT statements implies nothing about the order in which the rows appear in the final result because UNION by default produces an unordered set of rows...
"To cause rows in a UNION result to consist of the sets of rows retrieved by each SELECT one after the other, select an additional column in each SELECT to use as a sort column and add an ORDER BY following the last SELECT:
"(SELECT 1 AS sort_col, col1a, col1b, ... FROM t1)
UNION
(SELECT 2, col2a, col2b, ... FROM t2) ORDER BY sort_col;
To additionally maintain sort order within individual SELECT results, add a secondary column to the ORDER BY clause:
"(SELECT 1 AS sort_col, col1a, col1b, ... FROM t1)
UNION
(SELECT 2, col2a, col2b, ... FROM t2) ORDER BY sort_col, col1a;"

Categories