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 :)
Related
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
I have 2 different tables as want to get records in a single query. Currently, I am using 2 queries then merging the array result and then displaying the record. Following is my current code:
$db = JFactory::getDbo();
$query1 = "SELECT a.id as cId, a.title, a.parent_id,a.level FROM `categories` AS a WHERE ( a.title LIKE '%keyword%' )";
$result1 = $db->setQuery($query1)->loadObjectlist(); //gives selected records
$query2 = "SELECT b.id as indId, b.indicator , b.cat_id, b.subcat_id, b.section_id FROM `indicator` as b WHERE ( b.indicator LIKE '%keyword%' )";
$result2 = $db->setQuery($query2)->loadObjectlist(); //gives selected records
$_items = array_merge($result1,$result2); //then using $_items in php code to display the data
It is in Joomla however I just want to know how we can merge these 2 queries into one. I tried the following but it gives the result of first query from categories table.
(SELECT id as cId, title, parent_id,level, NULL FROM `categories` WHERE ( title LIKE '%birth%' ))
UNION ALL
(SELECT id as indId, indicator , cat_id, subcat_id, section_id FROM `indicator` WHERE ( indicator LIKE '%birth%' ))
Desired output:
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| cId | title | parent_id | level | indId | indicator | cat_id | subcat_id | section_id
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| 2874 | births | 2703 | 2 | null | null | null | null | null |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| 13 | birth weight| 12 | 3 | null | null | null | null | null |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| null | null | null | null | 135 | resident births| 23 | 25 | 1 |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| null | null | null | null | 189 | births summary | 23 | 25 | 1 |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
This above output will help to get proper pagination records. I tried to use join but JOIN needs a common column in ON clause. Here, I want all the columns and their values. Basically I want to combine the 2 table records in one query. Any help would be appreciated
Here is an example,
There are a number of ways to do this, depending on what you really want. With no common columns, you need to decide whether you want to introduce a common column or get the product.
Let's say you have the two tables:
parts: custs:
+----+----------+ +-----+------+
| id | desc | | id | name |
+----+----------+ +-----+------+
| 1 | Sprocket | | 100 | Bob |
| 2 | Flange | | 101 | Paul |
+----+----------+ +-----+------+
Forget the actual columns since you'd most likely have a customer/order/part relationship in this case; I've just used those columns to illustrate the ways to do it.
A cartesian product will match every row in the first table with every row in the second:
> select * from parts, custs;
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
1 Sprocket 102 Paul
2 Flange 101 Bob
2 Flange 102 Paul
That's probably not what you want since 1000 parts and 100 customers would result in 100,000 rows with lots of duplicated information.
Alternatively, you can use a union to just output the data, though not side-by-side (you'll need to make sure column types are compatible between the two selects, either by making the table columns compatible or coercing them in the select):
> select id as pid, desc, '' as cid, '' as name from parts
union
select '' as pid, '' as desc, id as cid, name from custs;
pid desc cid name
--- ---- --- ----
101 Bob
102 Paul
1 Sprocket
2 Flange
In some databases, you can use a rowid/rownum column or pseudo-column to match records side-by-side, such as:
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
2 Flange 101 Bob
The code would be something like:
select a.id, a.desc, b.id, b.name
from parts a, custs b
where a.rownum = b.rownum;
It's still like a cartesian product but the where clause limits how the rows are combined to form the results (so not a cartesian product at all, really).
I haven't tested that SQL for this since it's one of the limitations of my DBMS of choice, and rightly so, I don't believe it's ever needed in a properly thought-out schema. Since SQL doesn't guarantee the order in which it produces data, the matching can change every time you do the query unless you have a specific relationship or order by clause.
I think the ideal thing to do would be to add a column to both tables specifying what the relationship is. If there's no real relationship, then you probably have no business in trying to put them side-by-side with SQL.
As #Sinto suggested the answer for union and dummy column names following is the whole correct query:
(SELECT id as cId, title, parent_id,level, NULL as indId, NULL as indicator , NULL as cat_id, NULL as subcat_id, NULL as section_id FROM `jm_categories` WHERE ( title LIKE '%births%' )) UNION ALL (SELECT NULL as cId, NULL as title, NULL as parent_id,NULL as level, id as indId, indicator , cat_id, subcat_id, section_id FROM `jm_indicator_setup` WHERE ( indicator LIKE '%births%' ))
We have to match the column names from both tables so that we get records as a combination.
I know it's not the cleanest code to date, but I can't figure out why I can't get this one to work.
I'm looking to populate the field m.customersTemp with Customer Numbers from field c.ClientNumber. But only when a LIKE match from c.EmailAddress is found m.Emails... m.Emails is a field with a list of e-mails. Code Below.
UPDATE market m, customer c
SET m.customersTemp = CONCAT(m.customersTemp, c.ClientNumber)
WHERE m.Emails LIKE CONCAT('%', TRIM(c.EMailAddress), '%')
AND TRIM(c.EMailAddress)<>''
The result in field m.customersTemp only displays one value (customer number)... and I know there are many matches.
TABLE CUSTOMER
ClientNumber | EMailAddress
1234 a#a.com
4567 b#b.com
2222
1111 d#d.com
-------------------------------------------------------------
| TABLE MARKET |
-------------------------------------------------------------
| ID | Emails | customersTemp|
-------------------------------------------------------------
|1 | a#a.com, b#b.com, c#c.com | |
|2 | a#a.com, b#b.com, g#g.com | |
|3 | e#e.com | |
|4 | f#f.com | |
-------------------------------------------------------------
Result in customersTemp at ID 1 and 2 is only 1 ClientNumber. 4567
Don't forget to read the Warning at the bottom as to why you should NEVER save your data like this.
You can test this on a backup copy. I wouldn't run it against your main tables. Akin to someone saying: "Here, try this delete command, I think it will work."
-- drop table customer;
create table customer
( ClientNumber int,
EMailAddress varchar(100)
);
insert customer (ClientNumber,EMailAddress) values
(1234,'john#john.com'),
(4567,'joe#joe.com'),
(2222,''),
(1111,'somone#someone.com'),
(5454,'john#john.com');
-- drop table market;
create table market
( Emails varchar(100),
customersTemp varchar(100)
);
insert market(Emails,customersTemp) values
('john#john.com',''),
('joe#joe.com',''),
('test#test.com',''),
('more#more.com','');
The Update statement:
UPDATE market
INNER JOIN
( SELECT c.EMailAddress as e,GROUP_CONCAT(c.ClientNumber ORDER BY c.ClientNumber) theList
FROM customer c
GROUP BY c.EMailAddress
) xDerived1
ON market.EMails = xDerived1.e
SET market.customersTemp = xDerived1.theList;
Results:
select * from market;
+---------------+---------------+
| Emails | customersTemp |
+---------------+---------------+
| john#john.com | 1234,5454 |
| joe#joe.com | 4567 |
| test#test.com | |
| more#more.com | |
+---------------+---------------+
Version2
drop table customer;
create table customer
( ClientNumber int,
EMailAddress varchar(100)
);
insert customer (ClientNumber,EMailAddress) values
(1234,'a#a.com'),
(4567,'b#b.com'),
(2222,''),
(1111,'d#d.com'),
(8484,'g#g.com');
-- select * from customer;
drop table market;
create table market
( id int auto_increment primary key,
Emails varchar(100),
customersTemp varchar(3000)
);
insert market(Emails,customersTemp) values
('a#a.com,b#b.com,c#c.com',''),
('a#a.com,b#b.com,g#g.com',''),
('e#e.com',''),
('f#f.com','');
-- select * from market;
drop table if exists marketHelper7;
create table marketHelper7
( -- btw this might be the kind of table
-- as an intersect/junction table that you
-- should have to begin with
-- and not have your CSV stuff
cid int not null,
mid int not null
);
insert marketHelper7 (cid,mid)
select c.ClientNumber,m.id as MarketId
from customer c
join market m
on find_in_set(c.EMailAddress,m.Emails)>0;
update market set customersTemp=''; -- do a reset
UPDATE market m
join
( SELECT mh.mid as i,GROUP_CONCAT(mh.cid ORDER BY mh.cid) theList
FROM marketHelper7 mh
GROUP BY mh.mid
) xDerived1
ON m.id = xDerived1.i
SET m.customersTemp = xDerived1.theList;
drop table marketHelper7;
.
select * from market;
+----+-------------------------+----------------+
| id | Emails | customersTemp |
+----+-------------------------+----------------+
| 1 | a#a.com,b#b.com,c#c.com | 1234,4567 |
| 2 | a#a.com,b#b.com,g#g.com | 1234,4567,8484 |
| 3 | e#e.com | |
| 4 | f#f.com | |
+----+-------------------------+----------------+
Version 2 above has the helper table.
Warning:
By the way, never save your data like this. It is insane, and the performance is awful. Please see my answer here on Junction Tables (many-to-many) (similar to association tables or "item has" tables a.k.a. One-to-Many). They are all the same concept that utilized Data Normalization best practices and fast indexes during queries. Plus you stay happier not fighting with your data constantly or wondering if you blow the buffer size with group_concat.
Note that group_concat() has flexibility for its separator choice, and the order by, baked inside the function call.
The maximum length for the output of group_concat is subject to the system variable group_concat_max_len which probably defaults to 1K but can be set to at least 4GB.
The Percona article on group_concat(), and the manual pages for group_concat() and find_in_set().
I'm working on a website, and I'm implementing a friend system. People can become friends and that will unlock some stuff which is not important. It is coded in PHP with MySQL as database.
Lets say this is my account table
id | name | picture
0 | Jorik | Cat.jpg
1 | Joost | Fish.jpg
2 | Henk | Ferret.png
This is the friend table(Is this a good way to do this?)
id | user id | friend id | invite date | accepted
0 | 0 | 1 | 123 | 0
1 | 2 | 0 | 456 | 1
2 | 1 | 2 | 123 | 1
The way I check if they are friends is as following (Could be an error in it, but you get the idea).
$fid = friend id, $uid = user id.
WHERE ( (`userid` = '{$uid}' && `friendid` = '{fid}' ) || (`friendid` = '{$uid}' && `userid` = '{$fid}' ))
First of all, is this an efficient way of doing this or is there a better way?
If I wanted to get the list of friends a user has I can run this query
WHERE (`userid` = '{$uid}' || `friendid` = '{$uid}')
now if I would run this query for the user 0, it would return
id | user id | friend id | invite date | accepted
0 | 0 | 1 | 123 | 0
1 | 2 | 0 | 456 | 1
I have worked with MySQL joins before but I can't figure out how to return something like the following result
id | friend id | invite date | accepted | Name | Picture
0 | 1 | 123 | 0 | Joost | Fish.jpg
1 | 2 | 456 | 1 | Jorik | Ferret.png
It would have to check if it should join the friend id or the user id with id from the account table. (The invited person is the friend id, the one who invited him will get the user id.)
I hope I gave enough information.
Can anyone help me with this?
Thanks in advance,
Jorik
My $0.02:
1) Normalize your data: get rid of the either 'userid' or 'id'. You only need one unique column for accounts.
2) Make a table for 'friends' like so:
( approximately )
create table Friends( UserId int unsigned, FriendUserId int unsigned, index( UserId, FriendUserId ));
Then add a row for each 'friend' where 'FriendUserId' is the account id (see #1) for each person.
3) Make a table for 'invites' like so:
(appx)
create table Invites ( UserId int unsigned, FriendId int unsigned, InviteDate datetime default timestamp, Accepted tinyint(1) default 0, primary key( UserId, FriendId ));
Add a row for each invite and update if/when its accepted.
Now to get a persons friends:
select * from Accounts a join Friends f on a.UserId = f.UserId where a.UserId = ;
to get Invites:
select * from Accounts a join Invites i on a.UserId = i.UserId
And so on...
.. but more than anything.. avoid duplicating data ..
If I understand your question correctly, something like this may work:
SELECT a.user_id as orig_user_id, f.*, ab.*
FROM account a
JOIN friends f
ON f.user_id=a.id
JOIN account ab
ON f.friend_id=ab.user_id
WHERE a.id={$uid}
I haven't tested this sql, but you get the picture. Basically, select the row for the user whose friends you are trying to locate, then join their friends rows, and then join the user account rows for their friends. You may need to alias some column names to make things clear in your code. You will have duplicates because you are double joining the account table.
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.