I have a table with a custom sort order column.
ID NAME ORDER
1 Jack 4
2 Jill 2
3 Mike 5
4 Mark 1
5 Bill 3
I would like to insert a new record with an ID of 6, a NAME of Jane, and an ORDER of 3. I would like to insert it such that the old records are incremented to make room for the new record, resulting in something like this:
ID NAME ORDER
1 Jack 5
2 Jill 2
3 Mike 6
4 Mark 1
5 Bill 4
6 Jane 3
Can this be done using a SQL script? I was looking at this answer, but I'm not sure it can be made to work in my case. Plus it requires an extra table to temporarily hold values, which I would like to avoid.
Thanks.
[edit]
I forgot to add that there is a unique constraint non the third column. Though I understand there are ways of getting around this.
You can do this with two queries:
update mytable set order = order + 1 where order >= 3;
insert into mytable(id, name, order) values(6, 'Jane', 3);
Note, however, that this create a race condition, and might not behave properly under concurency stress.
A better solution would be not to store the customer ordering, but compute it on the fly in your queries (you can create a view to make it easier). For this, you would need to describe the logic behind the ordering.
Try this
INSERT INTO mytable(id, name, order) values (6, 'Jane', 0);
UPDATE mytable SET order = CASE WHEN id % 2 <> 0 THEN order + 1 ELSE order / 2;
Related
He's a beginner, and I've encountered this programming conundrum:
We have sample records in the table:
ID
VALUE
1
5
1
4
2
3
2
4
3
3
3
5
I would like to retrieve records with VALUE (3,4) values with the same ID, so as a result I would like to get ID 2
How to write such a mysql query to the database?
You can use HAVING.
SELECT id FROM table_name WHERE value IN (3, 4) GROUP BY id HAVING COUNT(*) = 2;
I have prepared an example for you to see how you work.
I have two tables shown below:
TableOwner:
UserID Name Initials
1 Peter Pet1
2 Mary Mar1
3 Petra Pet2
TableAsset
AssetID AssetName OwnerUserID
1 Samsung 3
2 Apple 1
3 Huawei 2
Now I want to insert into TableAsset these records:
AssetID AssetName OwnerUserID
4 Doro 2
5 Sony 1
How to use insert query and select query in one step?
You can do something like this in a single query. Passing the parameter will depend on you how you do it.
insert into TableAsset(AssetName, OwnerUserID)
select 'Doro', (select UserID from TableOwner where Initials = 'Mar1')
union all
select 'Sony', (select UserID from TableOwner where Initials = 'Pet1');
THIS ANSWERS THE ORIGINAL VERSION OF THE QUESTION.
You can look them up:
insert into tableAsset(AssetName, OwnerUserID)
select #AssetName, o.UserId
from tableOwner o
where o.initials = #Initials;
This basic structure will work for any database, although the method for passing parameters may differ among databases.
I have a table like so:
categoryID categoryName
----------------------------
1 A
2 B
3 C
Now I want the user to be able to order this data according to his will. I want to remember his preferred order for future. So I thought I'd add a column order to the table above and make it of type INT and AUTO_INCREMENT. So now I get a table like this:
categoryID categoryName order
-------------------------------------
1 A 1
2 B 2
3 C 3
4 D 4
My problem is - the user now decides, to bring categoryName with order 4 (D in example above) up to 2 (above B in example above) such that the table would now look like:
categoryID categoryName order
-------------------------------------
1 A 1
2 B 3
3 C 4
4 D 2
My question is - How should I go about assigning new values to the order column when a reordering happens. Is there a way to do this without updating all rows in the table?
One approach that comes to mind is to make the column a FLOAT and give it an order of 1.5 if I want to bring it between columns with order 1,2. In this case I keep loosing precision as I reorder items.
EDIT:
Another is to update all rows between (m, n) where m, n are the source and destination orders respectively. But this would mean running (m-n) separate queries wouldn't it?
Edit 2:
Assuming I take the FLOAT approach, I came up with this sql to compute the order value for an item that needs to be inserted after item with id = 2 (for example).
select ((
select `order` as nextHighestOrder
from `categories`
where `order` > (
select `order` as targetOrder
from `categories`
where `categoryID`=2)
limit 1) + (
select `order` as targetOrder
from `categories`
where `categoryID`=2)) / 2;
This gives me 3.5 which is what I wanted to achieve.
Is there a better way to write this? Notice that select order as targetOrder from categories where categoryID=9 is executed twice.
If the number of changes is rather small you can generate a clumsy but rather efficient UPDATE statement if the you know the ids of the involved items:
UPDATE categories
JOIN (
SELECT 2 as categoryID, 3 as new_order
UNION ALL
SELECT 3 as categoryID, 4 as new_order
UNION ALL
SELECT 4 as categoryID, 2 as new_order) orders
USING (categoryId)
SET `order` = new_order;
or (which I like less):
UPDATE categories
SET `order` = ELT (FIND_IN_SET (categoryID, '2,3,4'),
3, 4, 2)
WHERE categoryID in (2,3,4);
UPD:
Assuming that you know the current id of the category (or its name), its old position, and its new position you can use the following query for moving a category down the list (for moving up you will have to change the between condition and new_rank computation to rank+1):
SET #id:=2, #cur_rank:=2, #new_rank:=4;
UPDATE t1
JOIN (
SELECT categoryID, (rank - 1) as new_rank
FROM t1
WHERE rank between #cur_rank + 1 AND #new_rank
UNION ALL
SELECT #id as categoryID, #new_rank as new_rank
) as r
USING (categoryID)
SET rank = new_rank;
The idea with Float sounds reasanoble, just don't show these numbers to a user -)
Whenever user moves an entry up or down, you can figure out entries above and below. Just take their Order number and find mean value - that is a new order for the entry that has been moved.
You could keep order as integer and renumber all the items between a drag's source index and destination index because they can't drag that far, especially as only 20 odd categories. Mulit-item drags make this more complicated however.
Float is easier, but each time they move you find the middle you could very quickly run out of precission, I would write a test for this to check it doesn't finally give up working if you keep moving the 3rd item to the 2nd pos over and over.
Example:
1,2,3
Move 3rd to 2nd
1,1.5,2
Move 3rd to 2nd
1,1.25,1.5
Move 3rd to 2nd
1,1.125,1.25
Do that in an excel spread sheet and you'll find the number becomes too small for floats to deal with in about 30 iterations.
Ok, here's the same that #newtover suggests, but these 2 simple queries can be much easier understood by any other developer, even unexperienced.
Let's say we have a table t1:
id name position
-------------------------------------
1 A 1
2 B 2
3 C 3
4 D 4
5 -E- 5
6 F 6
Let's move item 'E' with id=5 to 2nd position:
1) Increase positions for all items between the old position of item 'E' and the desired position of 'E' (positions 2, 3, 4)
UPDATE t1 SET position=position+1 WHERE position BETWEEN 2 AND 4
2) Now there is no item at position 2, so 'E' can take it's place
UPDATE t1 SET position=2 WHERE id=5
Results, ordered by 'position'
id name position
-------------------------------------
1 A 1
5 -E- 2
2 B 3
3 C 4
4 D 5
6 F 6
Just 2 simple queries, no subqueries.
Restriction: column 'position' cannot be UNIQUE. But perhaps with some modifications it should work as well.
Haven't tested this on large datasets.
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
Is it possible to do a SELECT statement with a predetermined order, ie. selecting IDs 7,2,5,9 and 8 and returning them in that order, based on nothing more than the ID field?
Both these statements return them in the same order:
SELECT id FROM table WHERE id in (7,2,5,9,8)
SELECT id FROM table WHERE id in (8,2,5,9,7)
I didn't think this was possible, but found a blog entry here that seems to do the type of thing you're after:
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY FIND_IN_SET(id,"7,2,5,9,8");
will give different results to
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY FIND_IN_SET(id,"8,2,5,9,7");
FIND_IN_SET returns the position of id in the second argument given to it, so for the first case above, id of 7 is at position 1 in the set, 2 at 2 and so on - mysql internally works out something like
id | FIND_IN_SET
---|-----------
7 | 1
2 | 2
5 | 3
then orders by the results of FIND_IN_SET.
Your best bet is:
ORDER BY FIELD(ID,7,2,4,5,8)
...but it's still ugly.
Could you include a case expression that maps your IDs 7,2,5,... to the ordinals 1,2,3,... and then order by that expression?
All ordering is done by the ORDER BY keywords, you can only however sort ascending and descending. If you are using a language such as PHP you can then sort them accordingly using some code but I do not believe it is possible with MySQL alone.
This works in Oracle. Can you do something similar in MySql?
SELECT ID_FIELD
FROM SOME_TABLE
WHERE ID_FIELD IN(11,10,14,12,13)
ORDER BY
CASE WHEN ID_FIELD = 11 THEN 0
WHEN ID_FIELD = 10 THEN 1
WHEN ID_FIELD = 14 THEN 2
WHEN ID_FIELD = 12 THEN 3
WHEN ID_FIELD = 13 THEN 4
END
You may need to create a temp table with an autonumber field and insert into it in the desired order. Then sort on the new autonumber field.
Erm, not really. Closest you can get is probably:
SELECT * FROM table WHERE id IN (3, 2, 1, 4) ORDER BY id=4, id=1, id=2, id=3
But you probably don't want that :)
It's hard to give you any more specific advice without more information about what's in the tables.
It's hacky (and probably slow), but you can get the effect with UNION ALL:
SELECT id FROM table WHERE id = 7
UNION ALL SELECT id FROM table WHERE id = 2
UNION ALL SELECT id FROM table WHERE id = 5
UNION ALL SELECT id FROM table WHERE id = 9
UNION ALL SELECT id FROM table WHERE id = 8;
Edit: Other people mentioned the find_in_set function which is documented here.
You get answers fast around here, don't you…
The reason I'm asking this is that it's the only way I can think of to avoid sorting a complex multidimensional array. I'm not saying it would be difficult to sort, but if there were a simpler way to do it with straight sql, then why not.
One Oracle solution is:
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY DECODE(id,7,1,2,2,5,3,9,4,8,5,6);
This assigns an order number to each ID. Works OK for a small set of values.
Best I can think of is adding a second Column orderColumn:
7 1
2 2
5 3
9 4
8 5
And then just do a ORDER BY orderColumn