Implement Editable Order of Items in MySQL - mysql

I would like to show a table of items, where user can add, delete and reorder the items. The first way I thought to store these items in MySQL DB was to have an order column in the table, but it seems pretty inefficient because when user deletes an item, I need to update the order of all the following items. And if I just use ORDER BY with id or something, I don't know how to let user reorder the items as they want. What do you think is the best way to handle this?
The stack I use: React JS -> Node JS / Express -> MySQL

A way I have seen is to have a sperate column which states the ID of the item it is behind. Then in your Query you can order from that column when selecting to make it display in that order.
This means that if you delete a field, you only have to update the one behind it to then state its behind the one ahead of what you just deleted (That was a jumple of words...)
Does this help?

You'll definitely need a new column to do this ordering. It requires the storage of more information than you can represent with just the item ID, name, and all that.
I have done this successfully with the kind of order column you mention. Here are some tricks I used.
Don't worry about what happens when you delete a row. Things work just fine if you have gaps in the values of your order column.
Use the same data type for your order column as your autoincrementing id column (BIGINT UNSIGNED probably) but allow NULL values.
Use ORDER BY COALESCE(order, id * 1000), id so you'll use the id value for ordering newly inserted rows that have never had their order set. New rows come at the end. Intutitive enough default behavior.
To put item with id a right after the item with id b, update the order value in row a to COALESCE(b.order, id * 1000) + 1.
Like this
id name order COALESCE(order, id*1000)
1 orange NULL 1000
2 banana NULL 2000
3 apple NULL 3000
4 fig NULL 4000
This makes your order values be 1000, 2000, 3000, 4000 and so forth. To order the fourth item right after the second item you'll set its order to 2001. Like this
id name order COALESCE(order, id*1000)
1 orange NULL 1000
2 banana NULL 2000
4 fig 2001 2001
3 apple NULL 3000
If you do enough reordering that you insert more than 1000 items after any given item, you'll have to clean up your order column by rewriting it for the whole table. It's OK if you have some identical order values. The ORDER BY clause I suggested will keep them in their original ID order.

Related

How to query Top N ARRAY_MAX in SQL?

My table has a column that is in array type, and each in each row contain an array of length of 100.
I want to get the top 10 value out of each array and I only know how to get top 1 value using ARRAY_MAX(column)
How can we get top N value instead?
Getting top N out of an array is different from getting top n value out of a regular column and rows where you can do
SELECT column \\ FROM table \\ ORDER BY column DESC \\ LIMIT 10
May I suggest you correcting the tag info if the question is not MySQL based ? But if we are indeed using MySQL as the MySQL tag implies or if you are simply curious about how MySQL copes with array, I believe it does not have an array type for columns and not really needs one. This can be accomplished using a primary table with an array_id column and a second table which references the primary table's array_id and stores individual values through each row. The second table may look like this:
array_id array_value
1 443
1 80
1 8088
2 3306
2 1521
3 22
Thus the TOP N values can be easily retrieved. If by TOP N values you mean the top N highest values , we can use a similar query as the one you provided: SELECT array_value from array_table where array_id=n ORDER BY array_value DESC LIMIT 10;
Or if you mean the top n values in terms of the sequence in the array, we can use:
SELECT array_value from array_table where array_id=n LIMIT 10;
In the latter case, no ORDER BY clause is required in this case as we would like to retrieve values based on the sequence of rows being inserted. Usually it's the default sequence for a column's values to show up without index if ORDER BY clause is omitted. However, if you are really worried about the sequence without ORDER BY, you are free to add a timestamp column or PK auto_increment id column and use it in the ORDER BY clause to guarantee the sequence.

mySQL - Pagination of filtered rows

I have a REST service which return rows from a database table depending on the current page and results per page.
When not filtering the results, it's pretty easy to do, I just do a SELECT WHERE id >= (page - 1) * perPage + 1 and LIMIT to perPage.
The problem is when trying to use pagination on filtered results, e.g. if I choose to filter only the rows WHERE type = someType.
In that case, the first match of the first page can start in id 7, and the last can be in id 5046. Then the first match of the second page can start at 7302 and end at 12430, and so on.
For the first page of filtered results, I'd be able to simply start from id 1 and LIMIT to perPage, but for the second page, etc, I need to know the index of the last matched row in the previous page, or even better - the first matched row in the current page, or some other indication.
How do I do it efficiently? I need to be able to do it on tables with millions of rows, so obviously fetching all the rows and taking it from there is not an option.
The idea is something like this:
SELECT ... FROM ... WHERE filterKey = filterValue AND id >= id_of_first_match_in_current_page
with id_of_first_match_in_current_page being the mystery.
You can't know what the first id on a given page is, because id numbers are not necessarily sequential. In other words, there could be gaps in the sequence, so rows on the fifth page of 100 rows doesn't necessarily start at id 500. It could start on id 527 for example, It's impossible to know.
Stated yet another way: id is a value, not a row number.
One possible solution if your client is advancing through pages in ascending order is that each REST request fetches data, notes the greatest id value on that page, then uses that in the next REST request so it queries id values that are larger.
SELECT ... FROM ... WHERE filterKey = filterValue
AND id > id_of_last_match_of_previous_page
But if your REST request can fetch any random page, this solution doesn't work. It depends on having fetched the prior page already.
Another solution is to use the LIMIT <x> OFFSET <y> syntax. This allows you to request any arbitrary page. LIMIT <y>, <x> works the same, but for some reason x and y are reversed in the two different syntax forms, so keep that in mind.
Using LIMIT...OFFSET isn't very efficient when you request a page that is many pages into the result. Say you request the 5,000th page. MySQL has to generate a result on the server-side of 5,000 pages, then discard 4,999 of them and return the last page in the result. Sorry, but that's how it works.
Re your comment:
You must understand that WHERE applies conditions on values in rows, but pages are defined by the position of rows. These are two different ways of determining rows!
If you have a column that is guaranteed to be a row-number, then you can use that value like a row position. You can even put an index on it, or use it as the primary key.
But primary key values may change, and may not be consecutive, for example if you update or delete rows, or rollback some transactions, and so on. Renumbering primary key values is a bad idea because other tables or external data may reference primary key values.
So you could add another column that is not the primary key, but only a row-number.
ALTER TABLE MyTable ADD COLUMN row_number BIGINT UNSIGNED, ADD KEY (row_number);
Then fill the values when you need to renumber the rows.
SET #row := 0;
UPDATE MyTable SET row_number = (#row := #row + 1) ORDER BY id;
You'd have to re-number the rows if you ever delete some, for example. It's not efficient to do this frequently, depending on the size of the table.
Also, new inserts cannot create correct row number values without locking the table. This is necessary to prevent race conditions.
If you have a guarantee that row_number is a sequence of consecutive values, then it's both a value and a row position, so you can use it for high-performance index lookups for any arbitrary page of rows.
SELECT * FROM MyTable WHERE row_number BETWEEN 401 AND 500;
At least until the next time the sequence of row numbers is put into doubt by a delete or by new inserts.
You're using the ID column for the wrong purpose. ID is the identifier of a record, not the sequence number of a record for any given set of results.
The LIMIT keyword extends to basic pagination. If you just wanted the first 10 records, you'd do something like:
LIMIT 10
To paginate, if you wanted the second 10 records, you'd do:
LIMIT 10,10
The 10 after that:
LIMIT 20,10
And so on.
The LIMIT clause is independent of the WHERE clause. Use WHERE to filter your results, use LIMIT to paginate them.

How can I add a "group" of rows and increment their "group id" in MySQL?

I have the following table my_table with primary key id set to AUTO_INCREMENT.
id group_id data_column
1 1 'data_1a'
2 2 'data_2a'
3 2 'data_2b'
I am stuck trying to build a query that will take an array of data, say ['data_3a', 'data_3b'], and appropriately increment the group_id to yield:
id group_id data_column
1 1 'data_1a'
2 2 'data_2a'
3 2 'data_2b'
4 3 'data_3a'
5 3 'data_3b'
I think it would be easy to do using a WITH clause, but this is not supported in MySQL. I am very new to SQL, so maybe I am organizing my data the wrong way? (A group is supposed to represent a group of files that were uploaded together via a form. Each row is a single file and the the data column stores its path).
The "Psuedo SQL" code I had in mind was:
INSERT INTO my_table (group_id, data_column)
VALUES ($NEXT_GROUP_ID, 'data_3a'), ($NEXT_GROUP_ID, 'data_3b')
LETTING $NEXT_GROUP_ID = (SELECT MAX(group_id) + 1 FROM my_table)
where the made up 'LETTING' clause would only evaluate once at the beginning of the query.
You can start a transaction do a select max(group_id)+1, and then do the inserts. Or even by locking the table so others can't change (insert) to it would be possible
I would rather have a seperate table for the groups if a group represents files which belong together, especially when you maybe want to save meta data about this group (like the uploading user, the date etc.). Otherwise (in this case) you would get redundant data (which is bad – most of the time).
Alternatively, MySQL does have something like variables. Check out http://dev.mysql.com/doc/refman/5.1/en/set-statement.html

How does mysql order rows with the same value?

In my database I have some records where I am sorting by a column that contains identical values:
| col1 | timestamp |
| row1 | 2011-07-01 00:00:00 |
| row2 | 2011-07-01 00:00:00 |
| row3 | 2011-07-01 00:00:00 |
SELECT ... ORDER BY timestamp
It looks like the result is in random order.
Is the random order consistent? I have these data in two mysql servers can I expect the same result?
I'd advise against making that assumption. In standard SQL, anything not required by an explicit ORDER BY clause is implementation dependent.
I can't speak for MySQL, but on e.g. SQL Server, the output order for rows that are "equal" so far as the ORDER BY is concerned may vary every time the query is run - and could be influenced by practically anything (e.g. patch/service pack level of the server, workload, which pages are currently in the buffer pool, etc).
So if you need a specific order, the best thing you can do (both to guarantee it, and to document your query for future maintainers) is explicitly request the ordering you want.
Lot's of answers already, but the bottom line answer is NO.
If you want rows returned in a particular sequence, consistently, then specify that in an ORDER BY. Without that, there absolutely NO GUARANTEE what order rows will be returned in.
I think what you may be missing is that there can be multiple expressions listed in the ORDER BY clause. And you can include expressions that are not in the SELECT list.
In your case, for example, you could use ORDER BY timestamp, id.
(Or some other columns or expressions.)
That will order the rows first on timestamp, and then any rows that have the same value for timestamp will be ordered by id, or whatever the next expression in this list is.
The answer is: No, the order won't be consistent. I faced the same issue and solved it by adding another column to the order section. Be sure that this column is unique for each record like 'ID' or whatever it is.
In this case, you must add the 'ID' field to your table which is unique for each record. You can assign it 'AI' (auto increment) so that you are not going to deal with the maintenance.
After adding the 'ID' column, update the last part of your query like:
SELECT mt.*
FROM my_table mt
ORDER BY mt.timestamp ASC, mt.id DESC
In ORDER BY condition if the rows are same values or if you want to arrange the data by selecting ORDER BY statement. CASE : You want to ORDER BY the values of column are frequency of words. And two words in the table may have the same frequency value in the frequency occurrence column.. So in the frequency column you will have two same frequencies of two different words. So, in "select * from database_name ORDER BY frequency" you may find any of one the two words having the same frequency showing up just before its latter. And in second run the other word which was showing after the first word showing up earlier now. It depends on buffer memory,pages being in and out at the moment etc..
That depends on storage engine used. In MyISAM they'll be ordered in natural order (i.e. in order they're stored on the disk - which can be changed using ALTER TABLE ... ORDER BY command). In InnoDB they'll be ordered by PK. Other engines can have their own rules.

Creating a linked list or similar queue in MySQL?

I have a table of items that need to be displayed in a certain order, but that order can be changed. Items can be added at the beginning, end, or in the middle, and items can be rearranged. How can I set up the table to keep track of that order in such a way that it's easy to modify but the list can also be fetched in order with a single query?
For example, I could have a "NEXT_ID" column to do it linked list-style, but then how would I run a SELECT query to get the rows in order of the NEXT_ID chain?
Apologies in advance for the super-obvious solution I'm probably missing.
I have this problem often, and I solved it with a simple solution : an extra column called Sort Order (or DisplayOrder, whatever floats your boat really) . This allows me the flexibility to use auto-generated, auto-incremented ID column and have a special pre-defined sort.
In my case, I need them to come out of the database with an alphabetical order except that some items like "Other" and "N/A" are always last.
ProdID ProdText SortOrder
2 "Anchovies" 1
3 "Rivet" 2
4 "N/A" 4
5 "Other" 3
SELECT ProdID, ProdText ORDER BY Sort Order
Create a column in the table that represents the sort order. Put an index on this column so that the MySQL engine can retrieve based on this column quickly. When you change the order, update the values in this field for all records to keep it consistent.
For example, when you insert a new record in the middle:
UPDATE table SET sort_order = sort_order + 1 WHERE sort_order >= 5;
INSERT INTO table (sort_order, column1, column2) VALUES (5, 'value1', 'value2');
Something more complicated, like moving #3 down to #6 and sliding all others up:
UPDATE table
SET sort_order = Case sort_order When 3 Then 6 Else sort_order - 1 End
WHERE sort_order BETWEEN 3 AND 6;
If you want to avoid the sort order, you can try a "parent" column and consider the linked list to be a special case of a tree like structure. This article may help.
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
There are other articles out there:
http://ferdychristant.com/blog//archive/DOMM-7QJPM7
Keep in mind that selecting a long linked list may degrade performance though.
Unless I am misunderstanding what you are looking for it would seem you could just add a DISPLAY_ORDER column that is a numeric index of how things should be returned. This can easily be changed and rearranged. Plus value can be used in order by.
easiest solution: use a column named "display_order" in which you set 1,2,3 and so on. The query would be sorted with "ORDER BY display_order".
To edit this (if youre in a website related environment) use javascript with + and - buttons for example. everytime you do + the number increments and if something with the number exists itll get decreased so they switch positions.