Update primary key, sorted by another column, duplicate handling? - php

Trying to basically re-count the primary column, meaning updating from the first to the last, incrementing the value with each row. First I am selecting,
select
`someid`,
`time`
from
`table`
ORDER BY
`time` ASC
fetching the result,
$data = new SplFixedArray($query->num_rows);
$data = $query->fetch_all();
and updating the table.
for($i=1; $i<count($data); $i++){
print_r("UPDATING | New someid: " . $i . " WHERE old someid: " . $data[$i][0] . " AND time = " . $data[$i][1] . "<br />");
$query = $mysqli->query("UPDATE `table` SET `someid` = '" . $i . "' WHERE `time` = '" . $data[$i][1] . "' AND `someid` = '".$data[$i][0]."'");
}
What confuses me is, that the first few entries aren't sorted correctly. It's like
someid | time
--------------
0 | 0
2 | 1
1 | 2
7 | 3
11 | 4
6 | 5
It's because of duplicates. After the 15ish rows in "someid" there is a gap and it jumps to 60. So after that there are no more duplicate someids. But how can I handle the first few rows? If I want to update someid to 2 but 2 is already existent?
Any way to handle that without using too many querys?
Edit:
Maybe it's unclear to some. Basically I am trying to refresh the int-values from a primary column. So basically I am counting them and enter their appropriate number into someid, sorted by sometime. Sometime is datetime. So the earliest "sometime" would get "someid" = 1.
At the moment, someid has gaps. 1..2..3..4..5..6..7..15..16..17..18..30..31..32
Updating, like I am doing now, doesn't work until I reach a gap. Because I am trying to write a duplicate value. Updating the first someid to 1, which might be of value 11, does not work because someid 1 is already existent somewhere.
I would update someid 15 to 8, because it's the next number. This works because 8 to 14 are missing, and I am not duplicating a value.
One solution might be to create another loop and first update every someid to some made up number, maybe start counting from row->count, incrementing for every row. After that, I can update them as I'd do normally. But isn't there a better solution?
// UPDATE:
This:
SET #count = 0;
UPDATE `table` SET `table`.`someid` = #count:= #count+ 1;
"Resets" the IDs, but it doesnt sort them by time.
Trying this:
SET #count = 0;
UPDATE `table` SET `table`.`someid` = #count:= #count+ 1;
ORDER BY `time` DESC;
Gives me the following error:
Duplicate entry '1' for key 'PRIMARY'

Related

Getting the missing IDs from a table by storing the records into an array and compare the set of numbers with a for loop

I'm currently working on a program that gets the missing IDs of a table and the idea that I come up with is that by storing the IDs into an array and use a for loop to check if a number exists in the array and if it's existing then it is classified as a missing ID. I also used the php function - in_array() to check if a number exists in the array.
This is the code that I came up with, but I ended up with just displaying the numbers from the for loop.
<?php
include 'dbconnect.inc'; //just to the the dbconnect for connecting into the database.
$numbers = array(1, 2, 4, 6, 7, 9);
$arrlength = count($numbers);
$query = "SELECT id FROM existing";
$result = mysqli_query($conn, $query);
$existing = array();
while ($row = mysqli_fetch_assoc($result)) {
$existing[] = $row;
}
for ($i=0; $i<7358; $i++) {
if (in_array($i, $existing)) {
echo $i . " is a missing ID <br>";
} elseif(!in_array($i, $existing)) {
echo $i . " exists in the table <br>";
}
}
?>
I prefer this solution than using the temporary tables in an SQL because it takes more than to load the query and it would not be good for a webpage.
Hope that you could help me. Thanks!
From this answer:
To get missing ranges:
SELECT a.id+1 AS 'Missing From', MIN(b.id)-1 AS 'Through'
FROM existing AS a
JOIN existing AS b ON a.id < b.id
GROUP BY a.id
HAVING a.id+1 < MIN(b.id)
fiddle
User variables are only evaluated when sent, so using a HAVING NOT (gap_from=0 AND gap_to=0) clause isn't possible as an optimization (see user variables manual). A such we use the "sending" to be sending to the temporary table to save a larger time full of data that is about to be discarded.
The temporary table uses the primary key ensure there will only be one (0,0) entry that occurs when the there is no gap. Inserting subsequent existing entries (0,0) gets ignored resulting in a minimal table of gaps.
The remainder of the table is the gaps in the sequence:
create table existing (id int unsigned not null)
insert into existing values (3),(5),(6),(7),(8),(19),(20),(21),(30)
set #last=0
CREATE TEMPORARY TABLE v (gap_from int unsigned, gap_to int unsigned, next int unsigned, PRIMARY KEY(gap_from, gap_to))
IGNORE SELECT IF(#last=id, 0, #last) as gap_from,
IF(#last=id, 0, id-1) as gap_to,
#last:=id+1 as next
FROM existing ORDER BY id
select gap_from,gap_to from v where NOT (gap_from=0 AND gap_to=0)
gap_from | gap_to
-------: | -----:
0 | 2
4 | 4
9 | 18
22 | 29
If you don't want the first gap, the one between 0 and the first entry in the table:
select gap_from,gap_to from v where gap_from!=0
db<>fiddle here

Update data in mysql column field without removing previous value

I am trying to update "new" column value with new value but problem is my query remove previous data while inserting new value
What is want: here is example table structure,
Table name = agg_lvl primary key set = uid
uid | new
--------|--------
1 | 100
2 | 300
You can see "new" has 100 points, for example I send 100 new points to user 1, so new column value should be 100 + 100 = 200, right now with this code
$query4 = mysql_query("INSERT INTO agg_lvl (uid, new) VALUES ('$uid','$new')
ON DUPLICATE KEY UPDATE uid='$uid',new='$new'");
Not sure what
new = '$new'
I have tried both ways but no success = >
new = 'new + $new' or new = new + '$new'
You should make changes in your query
Make num = nun+$num to add new value to old one
Remove quotes arount $new because it is a number but not a string
Remove uid from set list because insert already point to that record
And your query should look so:
$query4 = mysql_query("INSERT INTO agg_lvl (uid, new) VALUES ('$uid','$new')
ON DUPLICATE KEY UPDATE new=new+$new");
Okay first i will answer with the proper way to do the same, In this case i am assuming that UID is unique, so you make a new table scorecard with UID as foreign key. Now rather than update, you just insert stuff to table like if UID 1 gains 10 and 20 points, there are two entries. onw with 10 and one with 20. Now to get his current points, you add all points where UID=1 .
Now in your implementation the correct query would be
UPDATE userData SET points = points + x WHERE UID = $uid
where x is the new points gained and points is the name of column
$query4 = mysql_query("INSERT INTO agg_lvl (uid, new) VALUES ('$uid','$new')
ON DUPLICATE KEY UPDATE uid='$uid',new=new+$new");
worked for me with help of #splash58

To append comma separated value no duplicates in mysql update Query

I am storing table data as comma separated values like this
id name fk_id
1 abc 2,4,6
2 def 2,7,8
Now, I want to use CASE WHEN or some function in Update Query of MySQL which updates the field if no duplicate value:
For example, same value for fk_id then ignores else update like if 10 for id 1 then update but if 7 for id 2 then ignore
id name fk_id
1 abc 2,4,6,10
2 def 2,7,8
I tried the following code:
if(
find_in_set($fk_id,fk_id),
fk_id,
CONCAT_WS(',', fk_id, $fk_id)
)
But not working.
Just update your code like as
set fk_id = if(find_in_set($fk_id,fk_id),
fk_id,
CONCAT(fk_id, ',', $fk_id)
)
Try this :
$sql = "
UPDATE `test`
SET `fk_id` = CONCAT(fk_id, ',','" . $fk_id . "')
WHERE `fk_id` REGEXP ',?(" . $fk_id . "),?'
";
NOTE : REGEXP is not the best option when it comes to speed.

Using Joins to Eliminate Multiple SELECTs in Loop

I'm trying to teach myself PHP/mysql by building a joe's goals clone, if you will.
Basically each user has multiple goals, and each day they record how many times a certain event occurred. For example, say my goal is to drink only 1 cup of coffee per day. If I had 3 today (oops!), I'd record 3 "checks" for today. I use a table called 'checks' to hold the check count for each day.
I have the following tables, and sample inserts:
CREATE TABLE `users` (
`user_id` int(5) NOT NULL AUTO_INCREMENT,
`user_email` varchar(50) NOT NULL,
`user_name` varchar(25) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
-- Dumping data for table `users`
INSERT INTO `users` VALUES (1, 'xxx#xxx.com', 'xxx');
INSERT INTO `users` VALUES (2, 'some#guy.com', 'SomeGuy');
CREATE TABLE `goal_settings` (
`goal_id` int(5) NOT NULL AUTO_INCREMENT,
`user_id` int(5) NOT NULL,
`goal_description` varchar(100) NOT NULL,
PRIMARY KEY (`goal_id`),
KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
-- Dumping data for table `goal_settings`
INSERT INTO `goal_settings` VALUES (1, 1, 'Run 1k');
INSERT INTO `goal_settings` VALUES (2, 1, 'Read 20 pages');
INSERT INTO `goal_settings` VALUES (3, 2, 'Cups of Coffee');
CREATE TABLE `checks` (
`check_id` int(40) NOT NULL AUTO_INCREMENT,
`goal_id` int(5) NOT NULL,
`check_date` date NOT NULL,
`check_count` int(3) NOT NULL,
PRIMARY KEY (`check_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
-- Dumping data for table `checks`
INSERT INTO `checks` VALUES (6, 1, '2012-03-02', 3);
INSERT INTO `checks` VALUES (2, 1, '2012-03-01', 2);
INSERT INTO `checks` VALUES (3, 2, '2012-03-01', 1);
INSERT INTO `checks` VALUES (5, 1, '2012-02-29', 1);
The output I'd like has goal_ids as rows and a range of dates as columns (like a week view calendar).
goal_id | 2012-03-01 | 2012-03-02 | 2012-03-03 | ... 2012-03-08 |
--------------------------------------------------------------------
1 2 3 0 ... 0
2 1 0 0 ... 0
Please note that when no checks exist for a given goal on a given day, 0 is returned instead of NULL.
I was able to get it working, poorly, using PHP. Truncated code, but I hope it shows basically what I tried: [$goal_ids is an array holding all goals associated with a user. $num_days is the number of days (i.e. columns) to be displayed, and $goal_days is an array used to hold the days we're looking to get info for].
$mysqli = new mysqli('xxx','xxx','xxx','goals');
$stmt = $mysqli->stmt_init();
$stmt = $mysqli->prepare("SELECT checks.check_count AS check_count
FROM `checks` WHERE goal_id = ? AND check_date = ?");
for($i=0; $i<=$goal_count - 1; $i++){
echo '<tr id="'.$goalid.'">';
for($j=0; $j <=$num_days; $j++){
$checkdate = $goal_days[$j];
$goalid = (integer) $goal_ids[$i];
if (!$stmt->bind_param("ii", $goalid, $checkdate)) {
echo "Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error;
}
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
$stmt->bind_result($check_count);
if($stmt->fetch()){
echo "<td>".$check_count."</td>";
}
else{
echo '<td>0</td>';
}
}
echo "</tr>";
}
echo "</table>";
$stmt->close();
This is obviously inefficient because for m goals and n days, it makes m x n select statements.
From reading, it seems like I'm basically trying to make a pivot table, but I've read that they are inefficient also, and I'm guessing what I'm doing is better handled by PHP than by doing a pivot table?
That leaves me with joins, which is what I think I'm asking for help with. I have considered creating a new column for every day, but I think it's not ideal. I'm open to totally changing the schema if necessary.
I hope I've been clear enough. Any help or pointers in the right direction would be greatly appreciated.
Thanks!
If I understand correctly your problem then I would suggest you should do a regular select in which each combination of goal_id and check_date will get a record in the result set, and then at the client side you will make a column for each check_date say by having an array of arrays and insert the checkcount in it.
This should at least be faster than having m x n select statements.
For more efficiency your sql can sort it by the goal_id and check_date, this will cause the records to be grouped together.
Here is an example of the sql statement:
SELECT check_date, goal_id, check_count FROM checks ORDER BY goal_id, check_date
Here is PHP sample code, assuming you have an array of arrays "$array_of_arrays" (initialized to zero to avoid the null problem) with the outer key being the goal_id and the inner key being the check_date:
while ($row = mysqli_fetch_result($result)){
$row_goal_id = $row["goal_id"];
$row_check_date = $row["check_date"];
$array_of_arrays[$row_goal_id][$row_check_date] = $row["check_count"];
}
And then you can use the array of arrays to do what you like, say for if you wish to output as an HTML table example then join the inner array with </td><td> and the outer array with </td></tr><tr><td>.
An example of how to create and initialize the "$array_of_arrays" array would be as follows (assuming you have an array $goals containing all the goals and an array $dates containing all the dates, if you don't know then in advance you can fetch them from the checks table by doing a SELECT DISTINCT)
$array_of_arrays = array();
foreach ($goals as $key=>$value){
$array_of_arrays[$value] = array();
foreach ($checks as $key1=>$value1){
$array_of_arrays[$value][$value1] = 0;
}
}
A similar approach can be used to generate the final HTML table as follows:
$final_array = array();
foreach ($array_of_arrays as $key=>$value){
$final_array[$key] = implode("</td><td>", $value);
}
$final_str = implode("</td></tr><tr><td>", $final_array);
$table_str = "<table><tr><td>" . $final_str . "</td></tr></table>";
Consider adding a table of days (or creating temporary one at runtime) holding just consecutive dates or dates you need. You could then get a nice list of check counts using a single query:
SELECT g.goal_id, d.day, COALESCE(c.check_count,0) as check_count
FROM
goal_settings g
JOIN
days d
LEFT JOIN
checks c
ON c.goal_id = g.goal_id AND c.check_date = d.day
WHERE
g.user_id = 1
AND d.day BETWEEN '2012-03-01' AND '2012-03-03'
ORDER BY g.goal_id, d.day
resulting in a rowset like:
goal_id | day | check_count
1 | 2012-03-01 | 2
1 | 2012-03-02 | 3
1 | 2012-03-03 | 0
2 | 2012-03-01 | 1
2 | 2012-03-02 | 0
2 | 2012-03-03 | 0
And then fetch those rows in a loop with php to build a nice html table - if goal_id changed then print new row and so on.

Counter in mysql doesn't work

I'am trying to implement a "most popular searches" function on a web site using a simple counter. The idea is that the first time a search is made, the query is stored in a special table. After that the count number increments by one every time that same search is made. I think I do exactly as I should but the code doesn't seem to work. The count value stays on NULL in the table. Here's my code. "Searchny" is the table where I store the searches. Any pointers much appreciated!
$search_result = mysql_query("SELECT * FROM searchny WHERE sok='$sokt'");
if(mysql_num_rows($search_result) == 0 ) {
$sql = "INSERT INTO searchny (sok, dt) VALUES ('" . $sokt . "', NOW());";
mysql_query($sql);
}
else {
mysql_query("UPDATE searchny SET count = count+1 WHERE sok='$sokt'");
}
Probably your count column is NULL by default and every count+1 doesn't make it any less NULL. Try either setting default value of one or explicitly set it to one in INSERT.
Your code has no mistake, so the problem isn't the code.
Is your value NULL or 0 ?
If it is NULL, change
INSERT INTO searchny (sok, dt) VALUES ('" . $sokt . "', NOW());
to
INSERT INTO searchny (sok, dt, count) VALUES ('" . $sokt . "', NOW(), 0);
Your problem is most likely that count is NULL. Anything added to NULL is NULL.
Try this query:
SELECT NULL + 1
The result will be:
NULL
You could set your default value for the count field to 0 instead.
However, here's a simpler way to implement this. Note that I've excluded the date field because I don't know what the requirements for the date are:
Table searchny:
---------
sok
count
---------
primary_key (sok)
Then do it all in one statement:
INSERT INTO searchny (sok, count) VALUES ($sokt, 1)
ON DUPLICATE KEY UPDATE count = count + 1
If it's a new sok value, then a new row is inserted and the count is set to 1. If the sok value already exists, then the record's count is incremented by 1.

Categories