Generate different Unique IDs using SELECT rows by ID - php

It's a php mysqli query where there is a table named result(uid,matchid,playerid,score)
I want to generate a unique id based on number of rows returned by the select statement with matchid
That would look something like this
if matchid is 0 then on first the rows returned would be zero then uid must be
matchid+rows returned
and then saving data with that uid and match id with other details
I hoped it must return something like this...
i.e. if id1(ID) is 0
then the unique id(UID) would be
ID+UID
that is 00,01,02...and so on where the UID would increment based on number of rows returned with the specific id1(ID).
another example, taking a different ID
i.e. if id1(ID) is 1
then according to the above explanation
it would be
ID+UID
i.e. 10,11,12...and so on based on the number of rows returned by that id1(ID)
I have tried to get the number of rows returned by the match id and found that the result given was 0(must indicates null),and scound time it also returns 0 and after the third time it starts incrementing values.
i.e 00,00,01,02 and so on
<?php
$ri=0;
$res=mysqli_query($c,"select rsid from result where mid='$m' ");
$rows=mysqli_num_rows($res);
$ri=$rows++;
echo ("<script> alert( '$ri' ); </script>");
//Unique Match Id for Player Result
$rsid = $m.$ri;
//Saving the data in the table
mysqli_query($c,"insert into result values('$rsid','$m','$u','$k','$w')");
?>
I expected the output to be 00,01,02...
but the output is 00,00,01,02...

To produce your desired results, you can use the INSERT ... SELECT syntax with a COUNT subquery on the result table, in order to retrieve your row count or 0, along with a CONCAT() of the mid to produce the desired rsid value.
To help prevent SQL injection attacks, it is strongly encouraged to use prepared statements,
Assuming rsid is your primary key you can use the following.
Example db-fiddle
//enable mysqli exception handling
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$c = mysqli_connect($host, $user, $pass, $dbname);
$query = <<<'EOL'
INSERT INTO `result`
SELECT
CONCAT(?, COALESCE((SELECT COUNT(r.rsid)
FROM `result` AS r
WHERE r.mid = ?
GROUP BY r.mid), 0)),
?, ?, ?, ?
EOL;
try {
/* example data
* $m = 0;
* $u = 1;
* $w = 1;
* $k = 1;
*/
$stmt = mysqli_prepare($c, $query)
mysqli_stmt_bind_param($stmt, 'ssssss', $m, $m, $m, $u, $k, $w);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
/*
* retrieve the theoretical last record inserted
*/
$stmt = mysqli_prepare($c, 'SELECT MAX(rsid) FROM result WHERE mid = ?');
mysqli_stmt_bind_param($stmt, 's', $m);
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $rsid);
if (mysqli_stmt_fetch($stmt)) {
echo $rsid;
}
mysqli_stmt_close($stmt);
} catch(mysqli_sql_exception $e) {
//handle the exception
//echo $e->getMessage();
}
Results per execution
| execution | $rsid |
| 1 | 00 |
| 2 | 01 |
| 3 | 02 |
...
Disclaimer, your current approach of using the total number of rows to generate the rsid is subject to serious data integrity complications. When a
record is deleted from the result table, the retrieved COUNT or mysqli_num_rows will result in a conflicting rsid. Additionally if the mid value is ever changed (a typo is corrected), the rsid value will become invalid.
There are also additional complications to consider with race conditions/hazards. See sql - Do database transactions prevent race conditions for details on preventative measures
For example, if you have a record of 00 in the database already, SELECT CONCAT(mid, COUNT(rsid)) WHERE mid = 0; will return 01. After inserting 01 and then removing 00, your next insert will be a duplicate rsid of 01;
INSERT INTO results(rsid)
VALUES(01);
DELETE FROM results WHERE rsid = '00';
INSERT INTO results(rsid)
VALUES(01); /* error duplicate key */
I recommend using SELECT MAX(rsid) + 1 instead of COUNT(). This will ensure your rsid is not repeated after deletion, but does not resolve the UPDATE mid issue. However you will need to have a minimum mid of 1 for MAX() + 1 to work.
Example db-fiddle
INSERT INTO `result`
SELECT
COALESCE((SELECT MAX(r.rsid) + 1
FROM `result` AS r
WHERE r.mid = ?
GROUP BY r.mid), CONCAT(?, 0)),
?, ?, ?, ?
If you absolutely need to use row count, to avoid the complications, you would need to ensure to only DELETE the highest rsid record within the mid grouping, and never UPDATE the mid column values. Otherwise you will need to rebuild all rsid values, on any change to the table. If you decide to rebuild the rsid values, I suggest using before update and after delete triggers to handle both of these instances and use a DATETIME NULL DEFAULT CURRENT_TIMESTAMP column to determine the ordering of the records.
Alternatively you could generate an rsid only when viewed, by using an AUTO_INCREMENT primary key and an incremental user variable. Then you no longer need to worry about the application controlled rsid, and only be required to insert the mid.
Example db-fiddle
SET #rsid: = NULL;
SET #mid = NULL;
SELECT
result.*,
CONCAT(mid, CASE WHEN #mid != result.mid OR #rsid IS NULL THEN #rsid := 0 ELSE #rsid := #rsid + 1 END) AS rsid,
#mid:=result.mid
FROM result
ORDER BY mid ASC, id ASC;
Result
| id | mid | ... | rsid |
| 1 | 0 | ... | 00 |
| 29 | 0 | ... | 01 |
| 311 | 0 | ... | 02 |
| 20 | 1 | ... | 10 |
| 40 | 1 | ... | 11 |
...
As a last alternative, you can use a segmented AUTO_INCREMENT by specifying a composite primary key of mid, id with the MyISAM engine, which is not capable of transactions or foreign key references. However, there are additional complications with this approach. [sic]
AUTO_INCREMENT values are reused if you delete the row with the biggest AUTO_INCREMENT value in any group.
Example db-fiddle
CREATE TABLE `result` (
`mid` INT(11) UNSIGNED NOT NULL,
`id` INT(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`mid`, `id`)
)
ENGINE=MyISAM
;
INSERT INTO result(mid)
VALUES(0),(1),(0),(2),(0),(1);
SELECT
result.*,
CONCAT(mid, id) AS rsid
FROM result;
Results:
| mid | id | rsid |
| --- | --- | ---- |
| 0 | 1 | 01 |
| 0 | 2 | 02 |
| 0 | 3 | 03 |
| 1 | 1 | 11 |
| 1 | 2 | 12 |
| 2 | 1 | 21 |
To update your current result table you can use
/* remove default value */
ALTER TABLE `result` ALTER `mid` DROP DEFAULT;
/* change engine, add id column, add composite primary key */
ALTER TABLE `result`
ENGINE=MyISAM,
CHANGE COLUMN `mid` `mid` INT(11) NOT NULL FIRST,
ADD COLUMN `id` INT(11) NOT NULL AUTO_INCREMENT AFTER `mid`,
ADD PRIMARY KEY (`mid`, `id`);

Related

Rewrite of counter by partition

I use mysql and php with phpmyadmin. I have major problem with a partition based counter that I wan't to improve but my knowledge on sql prevents me from doing that. Im struggling very much with this.
I want the duplicated data in my table to have a counter that adds a number after a value if this value gets a duplicated value and then restarts from 1 until a new value is met and so on. Here is what the final result should look like
---------------------------
1 | Josh-1
---------------------------
2 | Josh-2
--------------------------
3 | Josh-3
--------------------------
4 | Josh-4
--------------------------
5 | Fred-1
--------------------------
6 | Fred-2
--------------------------
7 | Fred-3
-------------------------
I had gotten help with this counter here before but it's not working as I wan't it to. Also when I have pressed the insert button in my form the table looks like this in phpmyadmin after I reload it
---------------------------
1 | Josh-1-1-1
---------------------------
2 | Josh-2
--------------------------
3 | Josh-3
--------------------------
4 | Josh-4
--------------------------
5 | Fred-1
--------------------------
6 | Fred-2
--------------------------
7 | Fred
-------------------------
Whats going on here? The code that I seek help with rewriting is this
UPDATE usermeta u1,
(SELECT
u1.`id`, CONCAT(u1.`name`,'-',ROW_NUMBER() OVER(PARTITION BY u1.`name` ORDER BY u1.`id`)) newname
FROM
usermeta u1 JOIN (SELECT `name` , COUNT(*) FROM usermeta GROUP BY `name` HAVING COUNT(*) > 1) u2
ON u1.`name` = u2.`name` ) u3
SET u1.`name` = u3.`newname`
WHERE u1.`id` = u3.`id`
Could this code be rewritten so it creates a table of numbered names and duplicates that looks like the first table example and work like it should in phpmyadmin ? All help is very much appreciated. Keep in mind that I am a struggling moderate sql user.
Possible solution - BEFORE INSERT trigger and additional MyISAM table with secondary autoincrement:
Working table
CREATE TABLE user (id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(127));
Additional table
CREATE TABLE user_index (id INT AUTO_INCREMENT,
name VARCHAR(127),
PRIMARY KEY (name, id)) ENGINE=MyISAM;
Trigger
CREATE TRIGGER insert_user_index
BEFORE INSERT ON user
FOR EACH ROW
BEGIN
DECLARE new_index INT;
INSERT INTO user_index (name) VALUES (NEW.name);
SET new_index = LAST_INSERT_ID();
DELETE FROM user_index WHERE name = NEW.name AND id < new_index;
SET NEW.name = CONCAT_WS('-', NEW.name, new_index);
END
Insert rows - the AI index is added to the name. Check the result.
INSERT INTO user (name) VALUES
('Josh'),
('Josh'),
('Fred'),
('Josh'),
('Fred'),
('Fred'),
('Josh');
SELECT * FROM user;
id | name
-: | :-----
1 | Josh-1
2 | Josh-2
3 | Fred-1
4 | Josh-3
5 | Fred-2
6 | Fred-3
7 | Josh-4
Look what is stored in additional table now.
SELECT * FROM user_index;
id | name
-: | :---
3 | Fred
4 | Josh
db<>fiddle here
If your working table user exists already, and it contains some data, then you'd create additional table and fill it with data using, for example,
CREATE TABLE user_index (id INT AUTO_INCREMENT,
name VARCHAR(127),
PRIMARY KEY (name, id)) ENGINE=MyISAM
SELECT MAX(SUBSTRING_INDEX(name, '-', -1) + 0) id,
SUBSTRING_INDEX(name, '-', 1) name
FROM user
GROUP BY 2;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=38f028cfe1c9e85188ab0454463dcd78

Know last X field value

I have a database with this table
+------+---------------------+-------------+
| id | key | value |
+------+---------------------+-------------+
| 152 | incidencia_0_fecha | 20150306 |
| 158 | incidencia_1_fecha | 20150307 |
| 234 | incidencia_2_fecha | 20150309 |
| . | ...... | ........ |
| n | incidencia_N_fecha | date_value |
+------+---------------------+-------------+
And I want to know what is the last key (N its dinamic and i don't know his last value). In this table the last must be incidencia_2_fecha.
How can i do it?
Thanks
You can easily get the number in the string using two REPLACES.
SELECT MAX(
REPLACE(REPLACE(`key`, 'incidencia_', ''), '_fecha', '')
)
FROM mytable
If the values in the id column are strictly increasing, you can do this:
SELECT key FROM your_table WHERE id = (SELECT MAX(id) FROM your_table);
EDIT 1:
If the table is quite large, you should make sure that there's an index on the id column. Otherwise the query could take a long time to run.
EDIT 2:
Another option, if the value column contains the date at the time the record was inserted (and is indexed), would be to do the above query, but replace id with value, i.e.
SELECT key FROM your_table WHERE value = (SELECT MAX(value) FROM your_table);
First fetch record in desc
SELECT key from tbl_name ORDER BY id DESC LIMIT 0,1

Assigning New id with PHP end() function

I have Database table of Questions. I use the integer variable Id to sort all the elements of the table.
What do I want?
Whenever, a new question is added. It is assigned a new id, which is 1 greated than the ID of the last question in my database.
Here is what I do:
include('dbconnect.php');
$ids = $connection->prepare('SELECT * FROM question ORDER BY id ASC');
$ids->execute(array());
$result = $ids->fetchAll(PDO::FETCH_ASSOC);
$new_id = end($result['id']); //Error in this line.
$new_id = $new_id + 1;
But, I always get the error
Warning: end() expects parameter 1 to be array, null given in /Applications/MAMP/htdocs/question/submit.php on line 20
I am using the FetchAll statement so I feel, that an array should be returned. Can anyone figure out where is the error.
Each New Question, which is added to the database gets an ID of 1.
As documented under Using AUTO_INCREMENT:
The AUTO_INCREMENT attribute can be used to generate a unique identity for new rows:
CREATE TABLE animals (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO animals (name) VALUES
('dog'),('cat'),('penguin'),
('lax'),('whale'),('ostrich');
SELECT * FROM animals;
Which returns:
+----+---------+
| id | name |
+----+---------+
| 1 | dog |
| 2 | cat |
| 3 | penguin |
| 4 | lax |
| 5 | whale |
| 6 | ostrich |
+----+---------+
No value was specified for the AUTO_INCREMENT column, so MySQL assigned sequence numbers automatically. You can also explicitly assign NULL or 0 to the column to generate sequence numbers.

JOIN 2 Tables with different columns

I Have 2 Tables, One For New Pictures and One For New Users, i want to create like a wall that mixes the latest actions so it'll show new users & pictures ordered by date.
What i want is a single query and how to know inside the loop that the current entry is a photo or user.
TABLE: users
Columns: id,username,fullname,country,date
TABLE: photos
Columns: id,picurl,author,date
Desired Output:
Daniel from California Has just registred 5mins ago
New Picture By David ( click to view ) 15mins ago
And so on...
I'm begging you to not just give me the query syntax, i'm not pro and can't figure out how to deal with that inside the loop ( i only know how to fetch regular sql queries )
Thanks
You could use an union:
SELECT concat(username, " from ", country, " has just registered") txt, date FROM users
UNION
SELECT concat("New picture By ", username, " (click to view)") txt, date FROM photos INNER JOIN users ON author=users.id
ORDER BY date DESC
LIMIT 10
This assumes that author column in photos corresponds to the users table id. If author actually is a string containing the user name (which is a bad design), you'll have to do this instead:
SELECT concat(username, " from ", country, " has just registered") txt, date FROM users
UNION
SELECT concat("New picture By ", author, " (click to view)") txt, date FROM photos
ORDER BY date DESC
LIMIT 10
Make sure you have an index on date in both tables, or this will be very inefficient.
I've put together this little example for you to look at - you might find it helpful.
Full script can be found here : http://pastie.org/1279954
So it starts with 3 simple tables countries, users and user_photos.
Tables
Note: i've only included the minimum number of columns for this demo to work !
drop table if exists countries;
create table countries
(
country_id tinyint unsigned not null auto_increment primary key,
iso_code varchar(3) unique not null,
name varchar(255) unique not null
)
engine=innodb;
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
country_id tinyint unsigned not null,
username varbinary(32) unique not null
-- all other detail omitted
)
engine=innodb;
drop table if exists user_photos;
create table user_photos
(
photo_id int unsigned not null auto_increment primary key,
user_id int unsigned not null,
-- all other detail omitted
key (user_id)
)
engine=innodb;
The important thing to note is that the primary keys of users and photos are unsigned integers and auto_increment (1,2,3..n) so I can find the latest 10 users and 10 photos by ordering by their primary keys (PK) descending and add a limit clause to restrict the number of rows returned.
-- change limit to increase rows returned
select * from users order by user_id desc limit 2;
select * from user_photos order by photo_id desc limit 2;
Test Data
insert into countries (iso_code, name) values ('GB','Great Britain'),('US','United States'),('DE','Germany');
insert into users (username, country_id) values ('f00',1),('bar',2),('stack',1),('overflow',3);
insert into user_photos (user_id) values (1),(1),(2),(3),(1),(4),(2),(1),(4),(2),(1);
So now we need a convenient way (single call) of selecting the latest 10 users and photos. The two tables are completely different so a union isnt going to be the best approach so what we'll do instead is write a stored procedure that returns two resultsets and handle generating the wall (merge resultsets) in our php script.
Stored procedure
Just a wrapper around some SQL code - think of it like SQL's version of a function call
drop procedure if exists list_latest_users_and_photos;
delimiter #
create procedure list_latest_users_and_photos()
begin
-- last 10 users
select
'U' as type_id, -- integer might be better
u.user_id,
u.country_id,
u.username,
-- other user columns...
c.name as country_name
from
users u
inner join countries c on u.country_id = c.country_id
order by
u.user_id desc limit 10;
-- last 10 photos
select
'P' as type_id,
up.photo_id,
up.user_id,
-- other photo columns...
u.username
-- other user columns...
from
user_photos up
inner join users u on up.user_id = u.user_id
order by
up.photo_id desc limit 10;
end #
delimiter ;
Testing
To test our stored procedure all we need to do is call it and look at the results.
mysql> call list_latest_users_and_photos();
+---------+---------+------------+----------+---------------+
| type_id | user_id | country_id | username | country_name |
+---------+---------+------------+----------+---------------+
| U | 4 | 3 | overflow | Germany |
| U | 3 | 1 | stack | Great Britain |
| U | 2 | 2 | bar | United States |
| U | 1 | 1 | f00 | Great Britain |
+---------+---------+------------+----------+---------------+
4 rows in set (0.00 sec)
+---------+----------+---------+----------+
| type_id | photo_id | user_id | username |
+---------+----------+---------+----------+
| P | 11 | 1 | f00 |
| P | 10 | 2 | bar |
| P | 9 | 4 | overflow |
| P | 8 | 1 | f00 |
| P | 7 | 2 | bar |
| P | 6 | 4 | overflow |
| P | 5 | 1 | f00 |
| P | 4 | 3 | stack |
| P | 3 | 2 | bar |
| P | 2 | 1 | f00 |
+---------+----------+---------+----------+
10 rows in set (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Now we know that works we can call it from php and generate the wall.
PHP Script
<?php
$conn = new Mysqli("localhost", "foo_dbo", "pass", "foo_db");
$result = $conn->query("call list_latest_users_and_photos()");
$users = array();
while($row = $result->fetch_assoc()) $users[] = $row;
$conn->next_result();
$result = $conn->use_result();
$photos = array();
while($row = $result->fetch_assoc()) $photos[] = $row;
$result->close();
$conn->close();
$wall = array_merge($users, $photos);
echo "<pre>", print_r($wall), "</pre>";
?>
Hope you find some of this helpful :)

How to determine order for new item?

I have a members table in MySQL
CREATE TABLE `members` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(65) collate utf8_unicode_ci NOT NULL,
`order` tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
And I would like to let users order the members how they like.
I'm storing the order in order column.
I'm wondering how to insert new user to be added to the bottom of the list.
This is what I have today:
$db->query('insert into members VALUES (0, "new member", 0)');
$lastId = $db->lastInsertId();
$maxOrder = $db->fetchAll('select MAX(`order`) max_order FROM members');
$db->query('update members
SET
`order` = ?
WHERE
id = ?',
array(
$maxOrder[0]['max_order'] + 1,
$lastId
));
But that's not really precise while when there are several users adding new members at the same time, it might happen the MAX(order) will return the same values.
How do you handle such cases?
You can do the SELECT as part of the INSERT, such as:
INSERT INTO members SELECT 0, "new member", max(`order`)+1 FROM members;
Keep in mind that you are going to want to have an index on the order column to make the SELECT part optimized.
In addition, you might want to reconsider the tinyint for order, unless you only expect to only have 255 orders ever.
Also order is a reserved word and you will always need to write it as `order`, so you might consider renaming that column as well.
Since you already automatically increment the id for each new member, you can order by id.
I am not sure I understand. If each user wants a different order how will you store individual user preferences in one single field in the "members" table?
Usually you just let users to order based on the natural order of the fields. What is the purpose of the order field?
Usually I make all my select statements order by "order, name"; Then I always insert the same value for Order (either 0 or 9999999 depending on if I want them first or last). Then the user can reorder however they like.
InnoDB supports transactions. Before the insert do a 'begin' statement and when your finished do a commit. See this article for an explanation of transactions in mySql.
What you could do is create a table with keys (member_id,position) that maps to another member_id. Then you can store the ordering in that table separate from the member list itself. (Each member retains their own list ordering, which is what I assume you want...?)
Supposing that you have a member table like this:
+-----------+--------------+
| member_id | name |
+-----------+--------------+
| 1 | John Smith |
| 2 | John Doe |
| 3 | John Johnson |
| 4 | Sue Someone |
+-----------+--------------+
Then, you could have an ordering table like this:
+---------------+----------+-----------------+
| member_id_key | position | member_id_value |
+---------------+----------+-----------------+
| 1 | 1 | 4 |
| 1 | 2 | 1 |
| 1 | 3 | 3 |
| 1 | 4 | 2 |
| 2 | 2 | 1 |
| 2 | 3 | 2 |
+---------------+----------+-----------------+
You can select the member list given the stored order by using an inner join. For example:
SELECT name
FROM members inner join orderings
ON members.member_id = orderings.member_id_value
WHERE orderings.member_id_key = <ID for member you want to lookup>
ORDER BY position;
As an example, the result of running this query for John Smith's list (ie, WHERE member_id_key = 1) would be:
+--------------+
| name |
+--------------+
| Sue Someone |
| John Smith |
| John Johnson |
| John Doe |
+--------------+
You can calculate position for adding to the bottom of the list by adding one to the max position value for a given id.

Categories