Ignore the database duplicates when setting LIMIT with PDO - php

I want to ignore the duplicates in my database when I will set my "LIMIT 0, 50", then "LIMIT 50, 50" then LIMIT..... I will need to scan the duplicates on only 1 column of my table, not all the columns at once. I can't merge the duplicates because they are different in a way : these duplicates have different prices.
more precisely, I will need to show a list of these items, but to show their different prices at their right.
I need a precise number (50) per pages, so I cant load less then go to the next page. I could therefore load more from the beginning (changing the max and previous offsets if i'm on a far page) in a way that if i ignore the duplicates, I will got exactly 50 per pages and I will get the good number of pages shown at the end.
I'm a bit beginner with PHP and I have no idea about how to do that. Maybe pre-scan all the table and then start writing my code, by being flexible with my scan's variables of LIMIT and everything ? what functions I need ? how ?
Else, do something pre-programmed or a function of php that I don't know it exists can solve this problem ? Or I really need to get an headhache xD

I am not entirely certain of what you are asking, but I think you might want to do a aggregate statement along these lines:
select
itemID,
group_concat(itemPrice)
from
yourTable
group by
itemID
limit 50
This will bring back a list of 50 items and a second column where all the prices are grouped together. Then in your PHP code, you can either explode() that second column keep it as is.
Edit: If you select every field, you can't then use an aggregate function. If you want to select other columns that won't be different, add them to both the select and the group by sections like this:
select
itemID,
itemName,
itemSomething,
itemSomethingElse,
group_concat(itemPrice)
from
yourTable
group by
itemID,
itemName,
itemSomething,
itemSomethingElse
limit 50

Probably you can group by item, and use GROUP_CONCAT to show different prices list? In this way you can still use LIMIT 50. If the price column is numeric, cast it to VCHAR.

I admit I borrowed the group_concat() function from the other answers :)
After reading this paragraph from the docs:
The default behavior for UNION is that duplicate rows are removed from the result.
The optional DISTINCT keyword has no effect other than the default because it also
specifies duplicate-row removal. With the optional ALL keyword, duplicate-row removal
does not occur and the result includes all matching rows from all the SELECT statements.
Assume the following table (testdb.test):
ID Name Price
1 Item-A 10
2 Item-A 15
3 Item-A 9.5
4 Item-B 5
5 Item-B 4
6 Item-B 4.5
7 Item-C 50
8 Item-C 55
9 Item-C 40
You can page this table rows (9 rows) or groups (3 groups, based on the item's name).
If you would like to page your items based on the item groups, this should help:
SELECT
name, group_concat(price)
FROM
testdb.test
GROUP BY name
LIMIT 1 , 3
UNION SELECT
name, group_concat(price)
FROM
testdb.test
GROUP BY name
LIMIT 0 , 3; -- Constant 0, range is the same as the first limit's
If you would like to page your items based on all the items (I don't think that's what you were asking for, but just in case it helps someone else), this should help:
SELECT
name, price
FROM
testdb.test
LIMIT 1 , 5
UNION SELECT
name, price
FROM
testdb.test
LIMIT 0 , 5; -- Constant 0, range is the same as the first limit's
A very important thing to note is how you'll have to modify the limits. The first limit is your key, you can start from any limit you'd like as long as it's <= count(*) but you will have to have the same range as the second limit (i.e 3 in the first example and 5 in the second example). And the second limit will always start from 0 as shown.
I enjoyed working on this, hope this helps.

Related

How to Limit MySQL query based on total records count percentage [duplicate]

Let's say I have a list of values, like this:
id value
----------
A 53
B 23
C 12
D 72
E 21
F 16
..
I need the top 10 percent of this list - I tried:
SELECT id, value
FROM list
ORDER BY value DESC
LIMIT COUNT(*) / 10
But this doesn't work. The problem is that I don't know the amount of records before I do the query. Any idea's?
Best answer I found:
SELECT*
FROM (
SELECT list.*, #counter := #counter +1 AS counter
FROM (select #counter:=0) AS initvar, list
ORDER BY value DESC
) AS X
where counter <= (10/100 * #counter);
ORDER BY value DESC
Change the 10 to get a different percentage.
In case you are doing this for an out of order, or random situation - I've started using the following style:
SELECT id, value FROM list HAVING RAND() > 0.9
If you need it to be random but controllable you can use a seed (example with PHP):
SELECT id, value FROM list HAVING RAND($seed) > 0.9
Lastly - if this is a sort of thing that you need full control over you can actually add a column that holds a random value whenever a row is inserted, and then query using that
SELECT id, value FROM list HAVING `rand_column` BETWEEN 0.8 AND 0.9
Since this does not require sorting, or ORDER BY - it is O(n) rather than O(n lg n)
You can also try with that:
SET #amount =(SELECT COUNT(*) FROM page) /10;
PREPARE STMT FROM 'SELECT * FROM page LIMIT ?';
EXECUTE STMT USING #amount;
This is MySQL bug described in here: http://bugs.mysql.com/bug.php?id=19795
Hope it'll help.
I realize this is VERY old, but it still pops up as the top result when you google SQL limit by percent so I'll try to save you some time. This is pretty simple to do these days. The following would give the OP the results they need:
SELECT TOP 10 PERCENT
id,
value
FROM list
ORDER BY value DESC
To get a quick and dirty random 10 percent of your table, the following would suffice:
SELECT TOP 10 PERCENT
id,
value
FROM list
ORDER BY NEWID()
I have an alternative which hasn't been mentionned in the other answers: if you access from any language where you have full access to the MySQL API (i.e. not the MySQL CLI), you can launch the query, ask how many rows there will be and then break the loop if it is time.
E.g. in Python:
...
maxnum = cursor.execute(query)
for num, row in enumerate(query)
if num > .1 * maxnum: # Here I break the loop if I got 10% of the rows.
break
do_stuff...
This works only with mysql_store_result(), not with mysql_use_result(), as the latter requires that you always accept all needed rows.
OTOH, the traffic for my solution might be too high - all rows have to be transferred.

Group SQL rows by column value without flattening [duplicate]

Is it possible to sort in MySQL by "order by" using a predefined set of column values (ID) like order by (ID=1,5,4,3) so I would get records 1, 5, 4, 3 in that order out?
UPDATE: Why I need this...
I want my records to change sort randomly every 5 minutes. I have a cron task to update the table to put different, random sort order in it.
There is just one problem! PAGINATION.
I will have visitors who come to my page, and I will give them the first 20 results. They will wait 6 minutes, go to page 2 and have the wrong results as the sort order has already changed.
So I thought that if I put all the IDs into a session on page 2, we get the correct records even if the sorting had already changed.
Is there any other better way to do this?
You can use ORDER BY and FIELD function.
See http://lists.mysql.com/mysql/209784
SELECT * FROM table ORDER BY FIELD(ID,1,5,4,3)
It uses Field() function, Which "Returns the index (position) of str in the str1, str2, str3, ... list. Returns 0 if str is not found" according to the documentation. So actually you sort the result set by the return value of this function which is the index of the field value in the given set.
You should be able to use CASE for this:
ORDER BY CASE id
WHEN 1 THEN 1
WHEN 5 THEN 2
WHEN 4 THEN 3
WHEN 3 THEN 4
ELSE 5
END
On the official documentation for mysql about ORDER BY, someone has posted that you can use FIELD for this matter, like this:
SELECT * FROM table ORDER BY FIELD(id,1,5,4,3)
This is untested code that in theory should work.
SELECT * FROM table ORDER BY id='8' DESC, id='5' DESC, id='4' DESC, id='3' DESC
If I had 10 registries for example, this way the ID 1, 5, 4 and 3 will appears first, the others registries will appears next.
Normal exibition
1
2
3
4
5
6
7
8
9
10
With this way
8
5
4
3
1
2
6
7
9
10
There's another way to solve this. Add a separate table, something like this:
CREATE TABLE `new_order` (
`my_order` BIGINT(20) UNSIGNED NOT NULL,
`my_number` BIGINT(20) NOT NULL,
PRIMARY KEY (`my_order`),
UNIQUE KEY `my_number` (`my_number`)
) ENGINE=INNODB;
This table will now be used to define your own order mechanism.
Add your values in there:
my_order | my_number
---------+----------
1 | 1
2 | 5
3 | 4
4 | 3
...and then modify your SQL statement while joining this new table.
SELECT *
FROM your_table AS T1
INNER JOIN new_order AS T2 on T1.id = T2.my_number
WHERE ....whatever...
ORDER BY T2.my_order;
This solution is slightly more complex than other solutions, but using this you don't have to change your SELECT-statement whenever your order criteriums change - just change the data in the order table.
If you need to order a single id first in the result, use the id.
select id,name
from products
order by case when id=5 then -1 else id end
If you need to start with a sequence of multiple ids, specify a collection, similar to what you would use with an IN statement.
select id,name
from products
order by case when id in (30,20,10) then -1 else id end,id
If you want to order a single id last in the result, use the order by the case. (Eg: you want "other" option in last and all city list show in alphabetical order.)
select id,city
from city
order by case
when id = 2 then city else -1
end, city ASC
If i had 5 city for example, i want to show the city in alphabetical order with "other" option display last in the dropdown then we can use this query.
see example other are showing in my table at second id(id:2) so i am using "when id = 2" in above query.
record in DB table:
Bangalore - id:1
Other - id:2
Mumbai - id:3
Pune - id:4
Ambala - id:5
my output:
Ambala
Bangalore
Mumbai
Pune
Other
SELECT * FROM TABLE ORDER BY (columnname,1,2) ASC OR DESC

Get previous 10 row from specific WHERE condition

Im currently working on a project that requires MySql database and im having a hard time constructing the query that i want get.
i want to get the previous 10 rows from the specific WHERE condition on my mysql query.
for example
My where is date='December';
i want the last 10 months to as a result.
Feb,march,april,may,june,july,aug,sept,oct,nov like that.
Another example is.
if i have a 17 strings stored in my database. and in my where clause i specify that WHERE strings='eyt' limit 3
Test
one
twi
thre
for
payb
six
seven
eyt
nayn
ten
eleven
twelve
tertin
fortin
fiftin
sixtin
the result must be
payb
six
seven
Thanks in advance for your suggestions or answers
If you are using PDO this is the right syntax:
$objStmt = $objDatabase->prepare('SELECT * FROM calendar ORDER BY id DESC LIMIT 10');
You can change ASC to DESC in order to get either the first or the last 10.
Here's a solution:
select t.*
from mytable t
inner join (select id from mytable where strings = 'eyt' order by id limit 1) x
on t.id < x.id
order by t.id desc
limit 3
Demo: http://sqlfiddle.com/#!9/7ffc4/2
It outputs the rows in descending order, but you can either live with that, or else put that query in a subquery and reverse the order.
Re your comment:
x in the above query is called a "correlation name" so we can refer to columns of the subquery as if they were columns of a table. It's required when you use a subquery as a table.
I chose the letter x arbitrarily. You can use anything you like as a correlation name, following the same rules you would use for any identifier.
You can also optionally define a correlation name for any simple table in the query (like mytable t above), so you can refer to columns of that table using a convenient abbreviated name. For example in t.id < x.id
Some people use the term "table alias" but the technical term is "correlation name".

Advanced sorting and searching a mysql database using php and mysql

Im facing a unique challenge.
I got a table with 100 numbers called HUNDREDNUMBERS.
I want to select the best quarter (75 to 100 numbers),
and place them into another table called BESTQUARTER.
I also want to select the worst quarter (1 to 25 numbers)
I want to place these into another table called WORSTQUARTER.
here's my Mysql code, so far,
$Extract_Data = "
CREATE TABLE $BESTQUARTER
SELECT
HUNDREDNUMBERS.number
FROM
HUNDREDNUMBERS order by
HUNDREDNUMBERS.number desc LIMIT 25 ";
$QuerySuccess = mysql_query($Extract_Data, $connection);
and for the other table....
$Extract_Data = "
CREATE TABLE $WORSTQUARTER
SELECT
HUNDREDNUMBERS.number
FROM
HUNDREDNUMBERS order by
HUNDREDNUMBERS.number asc LIMIT 25 ";
$QuerySuccess = mysql_query($Extract_Data, $connection);
The problem is that this script is not 100% correct every time.
Notice the ASC and the DESC in the two queries.
It's an ingenious way of trying to sort the numbers.
BTW, some of the numbers in the HUNDREDNUMBERS table have decimal points.
I need the data in the two new tables BESTQUARTER and WORSTQUARTER for further processing.
Any help is greatly appreciated
You're doing string comparisons and those follow different rules than numeric data types; I would suggest to change your sort expressions:
ORDER BY CAST(HUNDREDNUMBERS.number AS UNSIGNED) DESC|ASC
Instead of UNSIGNED you could also use SIGNED or DECIMAL(M, N) if you need to support negative numbers or floating points respectively.
Alternatively (and preferably), you could change the number column to a type that sorts properly by itself; VARCHAR should mostly be used for text.
You should check the data types. Make sure the the numbers are stored as at least a decimal. Other data types can cause the sorting to be off (and is a quite common mistake). It seems simple, but your code actually looks to be correct from what my understanding is of the question.
If you have only 100 numbers, I would suggest that you create a view with a rank, and use that for subsequent processing. Using intermediate tables seems like overkill:
select hn.*,
(select count(*) from hundrednumbers hn2 where hn2.number <= hn.number
) as rank
from HundredNumbers hn
With an index on hundrednumbers(number), this will even have decent performance.
It is possible that the problem you are encountering is duplicates in the original data. If so, looking at the ranks can help you figure out what to do in this situation.
After long hours of thinking and testing, i believe i finally cracked it.
1) I changed the fieldname "numbers" to DOUBLE UNSIGNED.
(initially i was using VARCHAR(50) )
2) Whenever you are using two or more tables that have the same field names, prefix EVERY fieldname with its tablename.
I did that and it worked, as you shall see in the full query below.
3) the original data had multiple occurrences of the same numbers,
ie there were several instances of rows with the value 100.
MySQL transferred only a single row with the value 100, into the table BESTQUARTER. (i don't know why).
uniqueid | id | numbers
1 200 100
2 6 100
3 76 100
4 64 99.009987655
5 10 95.98765432
6 11 11.98765432
7 12 25.12
8 13 53.173543
9 153 72.87676
10 32 99
So i added "GROUP By" and used the ID field.
(nb: "uniqueid" column is the primary key, "id" is a unique key that uniquely identifies each number)
Here's the new code
create table BESTQUATER
select
HUNDREDNUMBERS.uniqueid ,
HUNDREDNUMBERS.id,
HUNDREDNUMBERS.numbers
FROM
HUNDREDNUMBERS
group by HUNDREDNUMBERS.id
ORDER BY HUNDREDNUMBERS.numbers DESC LIMIT 25

Select is much slower when selecting latest records

A table with about 70K records is displayed on a site, showing 50 records per page.
Pagination is done with limit offset,50 on the query, and the records can be ordered on different columns.
Browsing the latest pages (so the offset is around 60,000) makes the queries much slower than when browsing the first pages (about 10x)
Is this an issue of using the limit command?
Are there other ways to get the same results?
With large offsets, MySQL needs to browse more records.
Even if the plan uses filesort (which means that all records should be browsed), MySQL optimizes it so that only $offset + $limit top records are sorted, which makes it much more efficient for lower values of $offset.
The typical solution is to index the columns you are ordering on, record the last value of the columns and reuse it in the subsequent queries, like this:
SELECT *
FROM mytable
ORDER BY
value, id
LIMIT 0, 10
which outputs:
value id
1 234
3 57
4 186
5 457
6 367
8 681
10 366
13 26
15 765
17 345 -- this is the last one
To get to the next page, you would use:
SELECT *
FROM mytable
WHERE (value, id) > (17, 345)
ORDER BY
value, id
LIMIT 0, 10
, which uses the index on (value, id).
Of course this won't help with arbitrary access pages, but helps with sequential browsing.
Also, MySQL has certain issues with late row lookup. If the columns are indexed, it may be worth trying to rewrite your query like this:
SELECT *
FROM (
SELECT id
FROM mytable
ORDER BY
value, id
LIMIT $offset, $limit
) q
JOIN mytable m
ON m.id = q.id
See this article for more detailed explanations:
MySQL ORDER BY / LIMIT performance: late row lookups
It's how MySQL deals with limits. If it can sort on an index (and the query is simple enough) it can stop searching after finding the first offset + limit rows. So LIMIT 0,10 means that if the query is simple enough, it may only need to scan 10 rows. But LIMIT 1000,10 means that at minimum it needs to scan 1010 rows. Of course, the actual number of rows that need to be scanned depend on a host of other factors. But the point here is that the lower the limit + offset, the lower that the lower-bound on the number of rows that need to be scanned is...
As for workarounds, I would optimize your queries so that the query itself without the LIMIT clause is as efficient as possible. EXPLAIN is you friend in this case...

Categories