mysql reorder rows with unique constraint - php

I'm having some trouble coming up with an efficient solution to this problem. Maybe I am making it more complicated than needs to be. I have a table like this:
thing_id | user_id | order
1 1 0
2 1 1
3 1 2
The user may mess around with their things and it may happen that they change thing 1 to thing 3, and thing 3 to thing 1. In my case, it is not that the user is explicitly changing the order. Rather, they are modifying their bank of things, and they may change the thing in slot 1 to be the thing in slot 3, and vice versa. So if the user performs this operation, the table should look like this:
thing_id | user_id | order
3 1 0
2 1 1
1 1 2
What complicates this is that (thing_id, user_id) has a unique constraint, so doing sequential updates does not quite work. If I try to UPDATE tbl SET thing_id=3 WHERE thing_id=1, the unique constraint is broken.
The order column is purely for show, in order to make an alphabetized list. So I suppose I could use PHP to check the order and figure things out like that, but this introduces code that really has nothing to do with the important stuff. I'd like to find a solution that is purely/mostly SQL.
Also, along the same lines, if I were to insert a new row into the table, I would want the order value to be 3. Is there an efficient way to do this in SQL, without first having to SELECT MAX(order) WHERE user_id=1?

My comment seems to have gotten some traction, so I'm posting it as an answer... To avoid your problem, add a new column, without constraints, and just use that for user desired updates.

Why aren't you updating the order instead of the thingid?
UPDATE tbl
SET order = 2
WHERE thing_id=1;
Each row represents a "thing-user" pair. The data is the ordering that you want to use. You don't want to change the entity ("thing-user"). You want to change the data.
By the way, you'll then have to do some additional work to keep unique values in orders.
If you switched this around and put the unique constraint on user_id, order, then it would make sense to update the thing_id.

Related

Two incrementing columns in the same table

I have a table that contains invoices for several companies, each company needs to have their own incrementing invoice number system.
id | invoiceId | companyId
--------------------------
1 | 1 | 1
2 | 2 | 1
3 | 1 | 2
4 | 1 | 3
I was hoping to achieve this with a unique compound key similar to this approach for MyISAM outlined here, but it seems it is not possible with InnoDB.
I need to return the new ID immediately after insertion and have concerns about creating a race condition if I try and achieve this with PHP.
Is my best option to create a trigger and if yes what would that look like? I have no experience with triggers and my research into using an after insert trigger has me worried with this quote from the MariaDB documentation:
RESTRICTIONS
You can not create an AFTER trigger on a view. You can not update the
NEW values. You can not update the OLD values.
Thanks for any advice
You need to add a unique index besides getting your next value. The next value is best gotten by querying the table with a trigger or by some procedure within a transaction. The remark of trigger on a view is not relevant in that case.
The unique index is on companyId,invoiceId is required to prevent two insert processes running on the same company adding an invoice, which then can end up both with the same invoiceId. Even better is when you switch to InnoDB so you can use transactions: Then 2 processes started at virtually the same time can benefit from transaction isolation with as result that they will be serialized and you get 2 unique incrementing invoice ids returned without having to handle the unique index exception in your code.
As far as I know, mysql's last_id is connection based, and not global and shared between processes.
using this simple script I've validated my concerns
(note this is codeigniter syntax, but it's relatively easy to understand)
I accessed the following function twice, within 10 seconds of each other(in different browser windows)
function test(){
$arr['var'] = rand(0,100000000);
$this->db->insert('test_table',$arr);
sleep(30);
$id = $this->db->insert_id();
var_dump($id);
}
Interesting to note, instead of getting "2" as a response in both of them, I've gotten 1 and two respectfully. This makes even more sense when you look at the underlying function
function insert_id()
{
return #mysqli_insert_id($this->conn_id);
}
This solves the returned ID, Your race condition is the product of the underlying query, which is basically "Select MAX(invoiceId ) WHERE companyID = X" and add +1 to that, and insert it.
This should be possible with a table lock before insert, however this depends on how many times per second you expect this table to get updated.
note, on persistent connection the last_insert_id might work differently, I haven't tested it.

Getting Random Output Using MySQL

I have a database containing more than 100,000 values. The structure looks something like as follows:
id | countryid | webid | categoryid | title | dateaddedon
If I use basic RAND() considering there are so many ids it won't be able to return a random result. I end up seeing titles of same webid next to each other. I would rather want titles from different webids being displayed. Therefore I figured since there are only 4-5 different values of webid it might be a better option to randomize the output based on this. I am unable to figure out how to define which specific column values should be randomized when using mysql SELECT command.
I am current using following
SELECT * FROM table WHERE countryid='1' ORDER BY dateaddedon DESC, RAND(3)
I am currently using 3 as seed value. I am not sure what kind of impact does seed value have on RAND. I would highly appreciate if someone could explain that too.
If seed value is specified it produces a repeatable sequence of column values. Unless you require a repeatable value leave it out. Also if you should have the RAND() as the first clause in ORDER.
SELECT * FROM table WHERE countryid='1' ORDER BY RAND(),dateaddedon DESC

Restructured database, using SQL in phpmyadmin to move data around

I've recently been working on normalizing and restructuring my database to make it more effective in the long run. Currently I have around 500 records, and obviously I don't want to lose the users data.
I assume SQL through phpmyadmin is the easiest way to do this?
So let me give you guys an example
In my old table I would have something like this
records //this table has misc fields, but they are unimportant right now
id | unit |
1 | g |
With my new one, I have it split apart 3 different tables.
records
id
1
units
id | unit
1 | g
record_units
id | record_id | unit_id
1 | 1 | 1
Just to be clear, I am not adding anything into the units table. The table is there as a reference for which id to store in the record_units table
As you can see it's pretty simple. What moved in the second table is that I started using an index table to hold my units, since they would be repeated quite often. I then store that unit id, and the pairing record id in the record_units table, so I can later retrieve the fields.
I am not incredibly experienced with SQL, though i'd say I know average. I know this operation would be quite simple to do with my cakephp setup, because all my associations are already setup, but I can't do that.
If I understand correctly you want to copy related records from your old table to the new tables, in which case you can use something like this
UPDATE units u
INNER JOIN records r
ON u.id=r.id
SET u.unit = r.unit
This will copy the unit type from your old table to the matching id in the new units table and then you can do something similare on your 3rd table

List Update - row conflict

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)

How to prevent duplicate values from inserting mySQL considering two columns?

Here is my table:
Table Name: UserLinks
Link_ID User_1 User_2
1 234325 100982
2 116727 299011
3 399082 197983
4 664323 272351
Basically, in this table a duplicate value is:
Link_ID User_1 User_2
1 232 109
2 109 232
I have looked around and found that I should use INSERT IGNORE to prevent duplicate entries, but I am not sure how to write a query that considers that the relationship between User_1 and User_2 is the same as between User_2 and User_1.
Any advice/help is really appreciated.
Thats a bit nasty, a commutative relationship between the 2 fields, but a unique index will not help given the values can be either way around.
If you could alter the code / data to ensure that the lower value of the ids was always placed in the user_1 field, that would at least then let the unique index work - but its a bit nasty.
Alternatively if the insertion is set based (e.g. not a row at a time but a set of rows) you could join to the existing data and anti-join based on both ways round e.g. :
(existing.user_1 = new.user_1 and existing.user_2 = new user_2)
OR (existing.user_1 = new.user_2 and existing.user_2 = new user_1)
and in the where clause check to ensure no match was made (the anti part of the join)
where existing.link_id is null
That wouldn't be efficient for row at a time insertion though.
How accurate do you need it. You could just create a unique index (or primary key) that is the hash of the 2 values xor'd together.
Something like primary key (md5(user_1) xor md5(user_2)).
Because "md5(232) xor md5(109)" will always equal to "md5(109) xor md5(232)". It does no matter on the order.
This will have a small chance of collision if you have a lot of records (like millions or billions) but otherwise, it should work.
You might need to check on the sql for this, as I did not test if SQL allows primary key to be generated like this.
This way, you do not need to add any additional check when inserting or updating as the unique constrant will do the checking for you.

Categories