I am faced with a problem coding my next feature. I want the user to be able to rearrange records and change the display_order value. I'm using Jquery UI's draggable and droppable to facilitate this.
I can see how a simple swap of display_order values would work. But I want to set a display order for a record and ideally have the others shuffle around so there are no repeated display_order values. Apart from not getting my head around how I would do that it seems like it would be hard to code and inefficient, shuffling every value around in the list.
So I am open to other suggestions of how this sort of thing is normally, or should be done.
I though of maybe using a value like 3.000 to represent the order and then when I want to make a record take its place make its value 3 - 0.001 so its 2.999 and will sort between 2 and 3. But I can see so many things wrong with that idea and doesn't seem like a good path to follow.
Thanks for any input!
add a sorting column to your table, smallint,
mediumint or int depending on the
expected number of total entries
A new entry is appended to the end, max(sorting) + 1
when reordering an item, get the new position it will be in and alter the higher sorting entries accordingly:
mysql_query('UPDATE yourTable set sorting = '.$yourNewposition.' where id='.$yourUniqueId .' LIMIT 1');
mysql_query('UPDATE yourTable set sorting = sorting + 1 where sorting >= '.$yourNewposition.' AND id != '.$yourUniqueId );
Related
I am trying to find a way to count the number of users until the number is reached. Here's somewhat of how my table is setup.
ID Quantity
1 10
2 30
3 20
4 28
Basically, I want to organize the row quantity to be in order from greatest to least. Then I want it to count how many rows it takes from going from the highest quantity to whatever ID you supply it with. So for example, If I was looking for the ID #4, It would look through the quantity from from greatest to least, then tell me that it is row #2 because it took only 2 rows to reach it since it contains the 2nd highest quantity.
There is another way I can code this, but I feel it is too demanding of a resource and involves PHP. I can do a loop on my database based on the greatest to least, and every time it goes through another loop, I add +1. So, that way, I could do an IF statement to determine when it reaches my value. However, when I have thousands of values it would have to go through, I feel like that would be too resource demanding.
Overall, this is a simple sort problem. Any data structure can give you the row of an item, with minor modifications in some cases.
If you are planning on using this operation multiple times, it is possible to beat the theoretical O(n log(n)) running time with an amortized O(log(n)) by maintaining a separate sorted copy of your table sorted by quantity. This reduces the problem to a binary search.
A third alternative is to maintain a virtual linked list of table entries in the new sort order. This would increase the insert times into the table to O(n), but would reduce this problem to O(1)
A fourth solution would be to maintain a virtual balanced tree, however, despite the good theoretical performance, this solution is likely to be extremely hard to implement.
It might not be the answer you are expecting but: you can't "stop" the execution of a query after you reach a certain value. MySQL always generate the full result set before you can analyse it. This is because, it order to sort the results by Quantity, MySQL needs to have all the rows.
So if you want to do this is pure MySQL, you need to count the row numbers (as explained here MySQL - Get row number on select) in a temporary table and then select your ID from there.
Example:
SET #rank = 0;
SELECT *
FROM (
SELECT Id, Quantity, #rank := #rank + 1 as rank
FROM table
ORDER BY Quantity
) as ordered_table
WHERE Id = 4;
If performance is an issue, you could probably speed this up a bit with an index on Quantity (to be tested). Otherwise the best way is to store the "rank" value in a separate table (containing only 2 columns: Id and Rank), possibly with a trigger to refresh the table on insert/update.
Since I'm moving to sphinx search engine to improove my ebsite performance I'm trying to translate the old mysql queries to new sphinx language.
The point is to sort results based on a math operation between votes to my posts and the points given for each vote (going from 1 to 5).
So for example, if i got 3 votes for a post and I got vote 1=5points vote 2=3points and vote 3=2points, my table will contain a field named votes with an integer = 3 (votes=3) and a field with an integer of 5+3+2 (points=10).
Due to this the final rating for such post will be points/votes, in this example it will be 10/3=3,333...
Assuming I'm using the sphinx api to get a list of top rated posts in DESCENDING order, this is the old mysql query i had on my php script:
mysql_query("SELECT * FROM table ORDER BY points/votes DESC LIMIT $start,$stop");
I tried to build a sphinx query, but it is not working and always giving 0 results. Please read tall the // commented lines that describe all the tries I did.
require("sphinxapi.php");
$cl = new SphinxClient;
$index = index;
$cl->setServer("localhost", 9312);
$cl->SetMatchMode(SPH_MATCH_FULLSCAN);
//$cl->SetSortMode(SPH_SORT_EXTENDED, 'IDIV(points,votes) DESC'); //not working
//$cl->SetSortMode(SPH_SORT_EXTENDED, '(points DIV votes) DESC'); //not working
//$cl->SetSortMode(SPH_SORT_EXTENDED, 'points/votes DESC'); //not working
//$cl->SetSortMode(SPH_SORT_EXTENDED, '(points/votes) DESC'); //not working
$cl->setLimits($start,$stop,$max_matches=1000);
$query = "";
Would you please help me out finding what's wrong... thanks.
You will need to use SPH_SORT_EXPR
$cl->SetSortMode(SPH_SORT_EXPR, '(points/votes) DESC');
Firstly you need points and votes to be Attributes, NOT fields. Attributes are stored in the index, can be used for sorting etc. Arithmetic can only be performed on numeric attributes (not strings)
The correct syntax for SPH_SORT_EXPR (assuming you've already got the attributes) would be
$cl->SetSortMode(SPH_SORT_EXPR, 'points/votes');
SPH_SORT_EXPR is ALWAYS descending, so you dont need it DESC on the end.
But rather than have sphinx calculate that ratio every single time, you would porbbaly be better calculating during sql_query and storing it as single number attribute. TIP: store as an integer, not float. Integers are more efficient to sort by.
Alright, let's say my MySQL table is set up with entries similar to:
id code sold
1 JKDA983J1KZMN49 0
2 JZMA093KANZB481 1
3 KZLMMA98309Z874 0
I'd like it to pick a random ID within the ranges already in the database (or just go from 1-X) and then just assign it to a variable for my own action to be taken. So let's say we want to pick a code that isn't sold (marked as 0 and not 1), then we'd pick it.
Now, it doesn't have to be 100% random, it could check if the first one is sold, if not, keep going. But I'm not 100% sure on how to go by this. Snippets would be appreciated because I can work out things easily on my own, I just need an example to see where I am going.
How about using a WHERE and ORDER BY RAND()
SELECT id, code
FROM tablename
WHERE sold = 0
ORDER BY RAND()
LIMIT 1
If you don't need the random, then don't use it. It can affect performance very negatively. Since you mentioned in your post that it wasn't necessary, I would recomment using Ezequiel's answer above and dropping the rand. See Most Efficient Way To Retrieve MYSQL data in random order PHP for more info.
It seems that your codes are already random, so why not just take the first item; if you have many unsold records in your database, doing the typical ORDER BY RAND() will hurt the database performance.
SELECT *
FROM codes
WHERE sold = 0
LIMIT 1
FOR UPDATE;
I've also added FOR UPDATE to avoid race conditions, assuming that you're using transactions, as you update the record later (to actually sell it).
Have you tried
SELECT * FROM myTable WHERE sold = 0 ORDER BY RAND() LIMIT 1
Adding ORDER BY RAND() to the rest of your SELECT query is the most straightforward way to accomplish this.
I have a column called list which is used in my order by (in MYSQL queries) and within list is numbers: (e.g. 1 to 20)
This list is then output using MYSQL order by list ASC - However, when I update my list in backend using a Jquery drag drop UI list it is supposed to update the list frontend.
My problem is that my list order sometimes conflicts with other rows as there could be two or three rows with the value of 1 in list therefore when my order updates I would like to know how I can update other rows by +1 only if the rows are >= the order number given
I do not want to make the column primary as I am not aiming to make the list column unique, the reason for this is because there is more than one category - and in each category they all start at 1 - therefore if I make it unique it would cause errors because there was multiple 1's over different categories.
I asked a friend who said I could probably try PL/SQL using a trigger function but this is new grounds to me - I don't fully understand that language and was wondering if anyone could help me do what I am trying to using MYSQL or even PL/SQL.
This is what I have so far:
<?php
$project = mysql_real_escape_string(stripslashes($_POST['pid']));
$category = mysql_real_escape_string(stripslashes($_POST['cat']));
$order = mysql_real_escape_string(stripslashes($_POST['order']));
// need to do update the moved result (list row) and update all conflicting rows by 1
mysql_query("UPDATE `projects` SET `cat`='$category',`list`='$order' WHERE `id`='$project'")or die(mysql_query());
?>
Conclusion:
I am trying to update a none unique column to have unique values for that individual category. I am not sure how to update all the rows in that category by +1
#andrewsi is right, in particular I suggest order by list ASC, last_update DESC so in the same query where you update list you can timestamp last_update and therefore you will have not need to use triggers or any other updates.
In general, what andrewsi and Luis have suggested is true. Instead of (like andrewsi said) "do messy updates" you should really consider ordering by a second column.
However, I can maybe see your point for your approach. One similar situation I know it could apply is in a CMS where you let the backend user order items by changing the order number manually in textfields next to the items, e.g.
item 1 - [ 1 ]
item 2 - [ 3 ]
item 3 - [ 2 ]
... the number in the [] would then be the new order.
So, a quite messy solution would be (many steps, but if you do not have to worry about performance it might be OK for you, I don't know):
INSERT INTO projects (cat, list, timestamp_inserted) VALUES (:cat, :list, NOW())
and then as a second step
SELECT id, list FROM projects WHERE cat=:cat ORDER BY list ASC, timestamp_inserted DESC
and then loop through the array you get from the select and foreach row update (:i is the increasing index)
UPDATE projects SET list=:i WHERE id=:id
PS: you would have to add a column timestamp_inserted with a timestamp value.
PPS: to clearly state, I would not recommend this and never said it is best practice (for those considering to downvote because of this)
I'm working on something where users can rearrange items, and at a later time, those items need to be displayed in the order chosen. As a simple example, consider a list of items:
A, B, C, D, E, F, G.
The MySQL table would be something simple: user_id, letter, sortnumber
The user is allowed to change the order in incremental steps. They might move A to after D, G to the beginning, etc. In addition to this, they can add, and remove items. So they might delete C, or add X. In each of these steps, I send data to PHP, which will process it, and set the items in MySQL.
There are two ways I see going about this:
Each time they add/remove/reorder
something, send the entire list to
PHP, delete all the data they
previous had in there, and just
insert the new list. Problem is,
this is a lot of
deletions/insertions each time they
do anything. They might move A to after B, and then suddenly I delete 7 records, and insert 7 more. On the plus side, it's dead simple.
Each "move" they do (e.g. an add, or a remove, or a reorder), send the information for that. E.g. they moved An after F, and tell me "move An after F" I now have to check that both A and F exist on the list, then I have to decrement all "sortnumber" between A and F (including F). If they say "delete Z" I have to find it on the list, delete it, and decrement all sortnumbers of records after it.
So I'm just curious... Has anybody had to deal with something where order matters, and if so, how did you go about it?
Add a Sequence column to the table - as a floating point number.
When an item is moved between Row-A and Row-B set its sequence number to the Average of those adjacent columns
Index the Sequence column :)
When the user wants to save the new order of the items, have it compare the new order to the old order and only perform updates on ones who have their sortnumber changed. If they delete an item, you don't need to shift the sortnumbers down. If you sort a list with a number that is missing in the middle, it will still be in the correct order.
The final sequence order is the only one that matters. If you move six items around to get a particular order, you don't really need to care what the sequence of the list is at the points between how it was when you started and what it's like when you're done.
Add and remove items without worrying about the sequence order, and then update the items to set the sequence value when you click a button that says, "Save Sort".
This gives you two advantages:
It's much less overhead every time you change an item, which also means it'll be faster.
It's dead simple to set the sequence -- all you have to do is send a list of the item IDs in the desired order and then iterate over it, updating the sequence value starting at 0 and going up.
Here's the same answer I gave to Thomaschaaf's question:
This is not and easy problem. If you
have a low number of sortable
elements, I would just reset all of
them to their new order.
Otherwise, it seems it would take just
as much work or more to "test-and-set"
to modify only the records that have
changed.
You could delegate this work to the
client-side. Have the client maintain
old-sort-order and new-sort-order and
determine which row[sort-order]'s
should be updated - then passes those
tuples to the PHP-mySQL interface.
You could enhance this method in the
following way (doesn't require
floats):
If all sortable elements in a list are initialized to a sort-order
according to their position in the
list, set the sort-oder of every
element to something like
row[sort-order] = row[sort-order * K]
where K is some number > average
number of times you expect the list to
be reordered. O(N), N=number of
elements, but increases insertion
capacity by at least N*K with at least
K open slots between each exiting pair
of elements.
Then if you want to insert an element between two others its as
simple as changing its sort-order to
be one that is > the lower element and
< the upper. If there is no "room"
between the elements you can simply
reapply the "spread" algorithm (1)
presented in the previous paragraph.
The larger K is, the less often it
will be applied.
The K algorithm would be selectively
applied in the PHP script while the
choosing of the new sort-order's would
be done by the client (Javascript,
perhaps).
Have a primary key and a sortnumber for each item. If you have a php array including the primary keys, you can remove items and insert items into the array using array_splice().
// base array
$items = array( 7, 11, 9, 4, 5);
// remove item 11
array_splice($items, array_search(11), 1);
// insert 11 before item 4
array_splice($items, array_search(4), 0, 11);
// input now contains 7, 9, 11, 4, 5
Then loop through the array and update sorting with primary keys
$i = 0;
foreach($items as $item) {
// UPDATE item_table SET sorting = '$i' WHERE id = '$item';
i++;
}
Just add another column. Call it order.
You have the order of rows and each row has an id or primary key. Just go through your rows one at a time, and set the order as you go. So:
UPDATE item_table SET order = 0 WHERE id="fred";
UPDATE item_table SET order = 1 WHERE id="larry";
UPDATE item_table SET order = 2 WHERE id="john";
UPDATE item_table SET order = 3 WHERE id="sydney";
I am sure there are trickery ways of doing this mathematically, but sometimes the simple answers are best.
Then when you make a query add SORT BY order.