Handling numerical lists in php and MySQL - php

I'm writing a small list system which is ordered via a numerical field in the database that is dynamic
The database holds the following:
Id | position | person
======================
1 | 3 | John
2 | 1 | Jane
3 | 4 | David
4 | 2 | Emily
Now when I select that list I sort it via the position field.
What I want to do is manage this order via a backend and whilst I have the main principle down, one thing that I am struggling with is the situation where a someone updates say Emily to 3 without changing the others in that ordered list.
So the page that updates the order just has the persons name and a text box with the current order in it.
I want to try to work out a way to handle this but can't seem to grasp any possible way to do this as all will be saved at once so there is no way to determine which item has changed, and therefore should be the correct one in case of duplicate.
Any ideas?

I'd just sort like this:
...order by position, person
That way, people with the same 'position' will still be ordered reliably (by name). Leave the "fixing" up to the user.
Ofcourse, the best way to handle this would be to not bother the user at all with a 'position' field but use a draggable sortable list of some kind.

If you want to move Emily DOWN the list her position 2 become 3.
So you need to move 3 back up to 2 (which was Emilys position)
The same goes for moving UP the list. You are basically swapping positions of the next/previous record. BUT you need to check that there is a record to swap with.
PSEUDO CODE MOVE DOWN:
get Emily
update record below Emily (Emily's position+1)
IF updated okay - update Emily position

One logic behind reordering a list manually would be to reorder the entire relevant list items whenever one list position is changed. Changing 10 to 2 would require everything from 2 to 9 to be pushed +1 position forward, so the logic would be
for i in new pos+1..old pos
pos[i]++

here is an exemple that i use
first i change the position of the row, lets use calendar_event_id = 7 as exemple , and then i call reposition, that runs:
SET #position = 0;
UPDATE calendar_events SET position = #position := #position + 1
WHERE position >= 0
ORDER BY position, IF(calendar_event_id = 7, 0, 1), name
and if you deleted a row, just skip the IF(calendar_event_id = 7, 0, 1), part

Related

MySQL select based on MySQL JSON array values

Hello people of the coding world.
I'm working on a small web development project and have an idea but am unsure whether or not it is applicable in pure MySQL or whether I will need to utilise some PHP code to make it work the way I need it to work. I'm not against using PHP, but feel that there is a better solution to splitting things.
I have three database tables. I'll call them A, B, and C. Tables B and C contain items that can have an item_parent. Any item in C is a child of at least one item in B, and any item in B is a child of at least one item in A. Each item, in all three tables, will have an item_id and item_level assigned to it. The ID is just a unique identifier, and serves no other purpose. The level is used to determine visibility to the viewer, as the viewer must have a level equal to or above the required level in order to view an item.
My query is that I want to be able to get the access level required in one query. This query would need to check all parents of items in table C and B for their access levels. If an item has multiple parents, it must check both and return the lowest of the two. If you can see at least one parent, you can see the child provided its level is not too high, but if you cannot see any parent then you cannot see the child regardless of child's level.
In order to get the multi-parenting system functional I've used the JSON data type for item_parent. It allows me to set a variable number of values without much change to the code used to assign or read those same values. item_id and item_level are of types INT and SMALLINT respectively.
Some sample data:
table_a
item_id | item_level
1 | 1
2 | 4
3 | 6
table_b
item_id | item_level | item_parent
1 | 2 | [1,2]
2 | 3 | [2]
3 | 7 | [1,3]
table_c
item_id | item_level | item_parent
1 | 0 | [3]
2 | 5 | [1]
3 | 8 | [1,2]
The query I want would need to run checks on the parents and return the lowest of those, then the highest of the returned data sets. Each item_parent refers to an item_id in the table directly above it.
The way I imagine this running (on table_c.item_id=1) is as follows:
table_c.item_id=1 parents are checked. The lowest level of all parents of an item is returned. Assuming we are checking the first item, the number returned would be 7, as we are checking table_b.item_id=3 (the only defined parent of table_c.item_id=1).
table_b.item_id=3 parents are checked, and again lowest value returned. We are only checking table_b.item_id=3 as it is a parent of table_c.item_id=1 and we are following the tree up. This item has two parents, so both are checked and the lowest level between them is returned. This would return a 1.
Now that we've done the level checking, we return the highest level of all returned results including the level of table_c.item_id=1. The numbers we are working with are 0, 1, and 7. The returned level would be 7, as it is the highest level of all those returned.
In the above situation, if your viewing level was 4 then you would be unable to see table_c.item_id=1 due to the level of table_b.item_id=3. However, if your level was 7 or above, you would be able to see it.
I've a few questions on how to make this work.
Can this be done by pure MySQL queries, or will I need to break things into multiple queries that are run separately with PHP processing the item_parent JSON data to prepare the next query? If so, what would the query be?
Is my use of JSON as the item_parent data type appropriate, or is there a data type that better for storing this information?
Apologies if I have not worded this very well. In my head, the idea is solid, but when it comes to explaining it I'm not sure what words to use or if I've conveyed my thoughts properly. Please let me know if edits are needed or if things are unclear.
Thank you in advance to those to take the time to read and work on a potential solution.
Following some reading of some comments and resources I was linked to, I determined that it would be more appropriate to rethink part of my database design as there is no pure MySQL way to do what I was hoping for with the existing structure.
Since I've rethought part of the database, this question becomes moot.

How to analyze item's path through system

I'm looking for a little bit of direction for how to analyze a problem. I work for a small manufacturing company. We paint about 150 items per day. Those items then go to Quality Control. About 70% pass QC. The remaining 30% have to be repaired in some way.
We have 5 different repair categories:Repaint, Reclear, Remake, Reglaze, Fix
Every time an order gets QC'd my system inputs some data in a "Repairs" mysql table. If it passes QC, it's given a category of Great. It's structure is like this:
id | Repair | Date
5 | repaint| 2013-01-01
6 | reclear| 2013-01-01
5 | great | 2013-01-02 ...etc
I need to be able to perform analysis on what actions are happening. I'd like to know what 'paths' items are going down.
For example. What percentage of items have these categories Reclear->Repaint->Great. What percentage have Repaint->Repaint->Remake->Great (every item should eventually end with 'Great)
I'm kind of stuck on where to start in figuring out how to analyze this.
Should I be keeping track of the repair number in the table? If I did that then maybe I could use a self join to select orders where repairnum=1 AND repair=Repaint joined with repairnum=2 AND repair='Great' This would tell me which orders went down the path Repaint->Great I'm a little hesitant to go this route because 1) I don't want to have to do a query and get the repairnumber before I insert a new row into the table and 2) It seems like I'd have to have some pretty nasty querys to analyze items that have 5 or 6 (or more) repairs.
Perhaps someone can point me in the right direction?
My app is in php and mysql.
You don't need a separate "repair number", because you have the date when each repair was made, so can order by that (assuming you store time as well if more than one repair can be made in a day).
The "path" for an item is the list of its repairs, in order of date. If you just say SELECT repair FROM repairs WHERE id=5 ORDER BY date ASC you'll get them as rows.
The trick is to turn these into a single value representing the whole path, using GROUP_CONCAT - SELECT GROUP_CONCAT(repair ORDER BY date ASC SEPARATOR '->') FROM repairs WHERE id=5
Once you have that, you can run that for all products in the DB using a GROUP BY, and then look for patterns in it with HAVING:
SELECT
id,
GROUP_CONCAT(repair ORDER BY date ASC SEPARATOR '->') as path
FROM
repairs
GROUP BY
id
HAVING
path = 'Repaint->Repaint->Remake->Great'
Note that I don't have a copy of MySQL to try this out with, so I may have made a mistake, but the manual suggests that the above should work.

PHP coders suggestions on alternative to WHERE NOT IN query

I am trying to build a simple randomised voting system for a site, currently, and I believe wrongly, I have the following setup
as the user goes to the random voting section of the site, he is presented with a votable item that firstly, is an item that was last voted on the longest time agp, Secondly, isn't something the user voted for before and thirdly must be relevant to them based upon their list of relevant subjects. As a side note, if the user skips the vote, it can't show them the same thing again later in the list and all the single votes must be recorded to produce statistics.
Currently, the way I am doing this is by holding a serialized array against their account in the database containing a list of vote item Id numbers that they have previously voted for.
I would like to say at this point that I don't condone this, and inserting a serialized array into the database was silly, and I regret my actions ;) .
Nevertheless, I couldn't figure out another way to do this. at this point, the array is built upon by users voting which adds to the list. but the query would become massive if I was to continue and someone had up to 100 things they could vote for. At this point I am using this query to get the next item in the list:
//$finarr is the received unserialized list of values from the database
$sql = "SELECT * FROM vote_item_headers WHERE Id NOT IN (".$finarr.") ORDER BY LastVoted DESC LIMIT 1"
//execute query and display results
I should also note that at this point, I haven't even bothered adding in the third requirement above because frankly, I didn't even know where to begin or anything when I had to make a query that needed to encompass all of the requirements above.
A bit more information you might find relevant:
I have some other tables which are explained below.
core_users = list of users and their interests
core_interests = a list of all interests
core_language = a list of the different possible languages that an item could fall into
vote_item_headers = a list of all the votable items with a reference in the interests and lang tables to define extra properties of the votable item.
core_votes = the master list of people's votes
I am really sorry if this is too vague for you guys, all I really want is guidance in this instance when dealing with large amounts of information that needs to be combined to get a result.
Any suggestions welcome. I am happy to restructure the entire thing just to get it right.
voted
user_id | Id
----------+------
1 | 12
1 | 14
1 | 187
2 | 23
SELECT * FROM vote_item_headers
WHERE Id NOT IN (
SELECT Id FROM voted WHERE user_id=1234
)
ORDER BY LastVoted DESC LIMIT 1
but you also want relevant posts
relevant_posts
user_id | Id
----------+------
1 | 342
1 | 253
1 | 32
2 | 53
SELECT vote_item_headers.* FROM vote_item_headers
# cut down the amount returned with relevant_posts table
INNER JOIN relevant_posts ON (
vote_item_headers.Id=relevant_posts.Id
AND user_id=1234
)
WHERE vote_item_headers.Id NOT IN (
SELECT Id FROM voted WHERE user_id=1234
)
ORDER BY LastVoted DESC LIMIT 1

Concurrent mutiuser access php+mysql

Im going to develop Stock maintaining system using php+mysql. which will runs on server machine, so many users can update stock data. (in/out)
Im currently working on this system. I have following problems.
User A opens record “A”. ex- val=10
User B opens record “A”. ex - val=10
User A saves changes to record “A”. ex - val=10+2=12 (add 3 items, then stock should be 12)
User B saves changes to record “A”. ex - here i need to get record "A" value AS = 12, then B update val=12+3=15. (then add 3 items final stock will be 15)
In this example, User A’s changes are lost – replaced by User B’s changes.
I know mysql Innodb facilitate row level locking. My question is ,
is innodb engine do concurrent control ; and is this enough to (Innodb) to avoid "lost update" problem. or need to do extra coding to avoid this problem.
Is this enough please tell me how innodb works with my previous example. (lost update)
(sorry for my bad english)
thanks
InnoDB allows concurrent access, so User A and User B could definitely be handling the same data. User A will update the row based on his/her data, then User B can do the same -- ultimately resulting in User A's loss of data.
You should consider an alternative, if every update is vital to keep. For example, if both users are updating a blog article, you could make a new table that holds all these edits. Both user's edits would be preserved, despite when they retrieved the article content. When the article is retrieved, you can check when the most recent edit occurred and retrieve that instead.
Look, there's something called "versioning".
The idea is simple:
When a user opens a record, he also gets the version number.
When he saves changes to that record, at the sql level, the update is conditional, meaning that the update will happen ONLY if the current version is the same. This update also increases the version by one.
This way ensures you're not writing to a "stale" copy of your record.
Hope it's clear.
You could also implement some polling to the server, keep a record of the last update of the row and if it changes where if user B updates the record before A then you can notify user A that the record has been updated and that his changes wont take effect or you could update the values dynamically.
You can use two tables for this purpose. First - StockItems with item name, id, and count. Second - StockActivities with item id and operation amount.
To add or remove items from stock you need to insert records to the second table StockActivities, with item id and quantity that is added / removed.
item id:1, qnt: +10
item id:1, qnt: +1
item id:10, qnt: -2
Field count of StockItems table should be "read only" for users and should be calculated based on StockActivities table.
For example, you can create after insert trigger for StockActivities table that will update count field of added / removed stock item.
Judging by comments left, I think it prudent to respond with some pointers I have come across, in case someone needs to.
If you only want to update a value by an offset, you can do this quite easily and atomically. Assume the following data:
+----+--------+-------+
| id | name | price |
+----+--------+-------+
| 1 | Foo | 49 |
| 2 | Bar | 532 |
| 3 | Foobar | 24 |
+----+--------+-------+
We can now run the following queries to add one to the price:
select id, price from prices where name like "Foo";
// Later in the application
update prices set price=50 where id=1;
This is the non-concurrent/non-atomic way to do this, assuming that there is no changes or fetches in between the two queries. A more atomic way to do this, is the following.
select id, price from prices where name like "Foo";
// Later in the application
update prices set price=price+1 where id=1;
Here, this query allows us to increment the price in one query, eliminating the ability for others to come and update between two queries.
Additionally, there are methods of updating data safely, where the nature of the update is not a simple addition or subtraction. Let's say, here, that we have the following data:
+----+----------+---------------------+
| id | job_name | last_run |
+----+----------+---------------------+
| 1 | foo_job | 2016-07-13 00:00:00 |
| 2 | bar_job | 2016-07-14 00:00:00 |
+----+----------+---------------------+
In this case, we have multiple different clients, where all clients can do any job. We then need a way to dispatch work to one client, and only one client.
We can either use a transaction, where we will error out if the record has been updated or we can use a technique called CAS, or Compare and Swap.
Here's how we do this in MySQL:
update jobs set last_run=NOW() where id=1 and last_run='2016-07-13 00:00:00'
Then, in the data returned from mysql, we can tell the number of rows affected. If we have affected a row, then we have successfully updated it, and the job is ours. If there were no rows updated, then another machine has updated it, claiming the job there.
This works because any update from our application will cause the column to change, and since the column's value is a condition for completing the updated, it will avoid concurrent changes, allowing the application to decide what occurs next.

Select records with values up to and over (but only one over) another value?

I have data such as...
ID | Amount
-----------------
1 | 50.00
2 | 40.00
3 | 15.35
4 | 70.50
etc. And I have a value I'm working up to, in this case let's say 100.00. I want to get all records up to 100.00 in order of the ID. And I want to grab one more than that, because I want to fill it up all the way to the value I'm aiming for.
That is to say, I want to get, in this example, records 1, 2, and 3. The first two total up to 90.00, and 3 pushes the total over 100.00. So I want a query to do that for me. Does such a thing exist in MySQL, or am I going to have to resort to PHP array looping?
Edit:
To put it in English terms: Let's say they have $100 in their account. I want to know which of their requests can be paid, either in toto or partially. So I can pay off the $50 and the $40, and part of the $15.35. I don't care, at this point in the program, about the partialness; I only want to find out which quality in any way.
Yes, is possible
set #total:=0;
select * from
(
select *, if(#total>100, 0, 1) as included, #total:=#total+Amount
from your_table
order by id
) as alls
where included=1
order by id;
Refering to the last sentence: doesn't mysql sum cut it?

Categories