PHP mysql ordering rows - php

For example, I have a table which looks like this :
id | name
1 | Mike
2 | Adam
3 | John
4 | Sarah
...
Now, when I execute query select * from table order by id desc it will output something like this:
4 | Sarah
3 | John
2 | Adam
1 | Mike
Now what do I do if I want to move John's row up or down, or move Adam's row up or down ( with a MySQL query ( I need basic one, just to know from where to start )).
My solution :
First of all, I created another column named orderID which has the same value as id.
Here is an example which moves up a user:
$query = "
SELECT (
SELECT orderID
FROM test WHERE id = 'user id that i want to move up'
) AS user_order,
(
SELECT orderID
FROM test WHERE orderID > user_order
ORDER BY orderID
LIMIT 0,1
) AS nextUser_order
";
$result = mysql_query($query);
$data = mysql_fetch_assoc($result);
$query = "
UPDATE test SET orderID = IF(orderID='{$data[nextUser_order]}',
'{$data[user_order]}', '{$data[nextUser_order]}')
WHERE orderID IN ('{$data[nextUser_order]}', '{$data[user_order]}');
";
$result = mysql_query($query);
Is there a better way to do that?

You have to switch IDs, or to order it by another column. That's the only way.

Changing the id is not what you want to do. You never want to mess with your primary key especially because later down the road it would be easier (and take up much less space, one is an int the other a varchar) to reference your users using their id rather than their name from other tables, it is nice to have a field that you know will never change.
Make another field such as order as a floating point number.
When you move foo between bar and foobar, set foo's order to the average of bar and foobar's order.

You can put arbitrary values into an order by clause in a query, but none will work easily for a simple "move up/down a row" type things. You can force certain values to sort first or last, but not "put this value after that value, but let that value go into its natural place". You'd need to have an extra field to specify sorting order.

SQL tables aren't inherently ordered - they effectively behave like a "bag of rows". If you want the results in a specific order, you will need to sort them (using ORDER BY ...) when you pull them out of the bag -- otherwise, the SQL server will return them in whatever order it feels is easiest. (In this case, they're coming out in the reverse order you inserted them, but that's not guaranteed at all.)

You should def be using another column which holds the order of the display. id is just a unique identifier. On a relational database moving up and down rows might result in a lot of queries because of the updates on the related tables so I stick with the idea of defining a special row for this purpose.

Related

Most efficient method determining if a list of values completely satisfy a one to many relationship (MySQL)

I have a one-to-many relationship of rooms and their occupants:
Room | User
1 | 1
1 | 2
1 | 4
2 | 1
2 | 2
2 | 3
2 | 5
3 | 1
3 | 3
Given a list of users, e.g. 1, 3, what is the most efficient way to determining which room is completely/perfectly filled by them? So in this case, it should return room 3 because, although they are both in room 2, room 2 has other occupants as well, which is not a "perfect" fit.
I can think of several solutions to this, but am not sure about the efficiency. For example, I can do a group concatenate on the user (ordered ascending) grouping by room, which will give me comma separated strings such as "1,2,4", "1,2,3,5" and "1,3". I can then order my input list ascending and look for a perfect match to "1,3".
Or I can do a count of the total number of users in a room AND containing both users 1 and 3. I will then select the room which has the count of users equal to two.
Note I want to most efficient way, or at least a way that scales up to millions of users and rooms. Each room will have around 25 users. Another thing I want to consider is how to pass this list to the database. Should I construct a query by concatenating AND userid = 1 AND userid = 3 AND userid = 5 and so on? Or is there a way to pass the values as an array into a stored procedure?
Any help would be appreciated.
For example, I can do a group concatenate on the user (ordered ascending) grouping by room, which will give me comma separated strings such as "1,2,4", "1,2,3,5" and "1,3". I can then order my input list ascending and look for a perfect match to "1,3".
First, a word of advice, to improve your level of function as a developer. Stop thinking of the data, and of the solution, in terms of CSVs. It limits you to thinking in spreadsheet terms, and prevents you from thinking in Relational Data terms. You do not need to construct strings, and then match strings, when the data is in the database, you can match it there.
Solution
Now then, in Relational data terms, what exactly do you want ? You want the rooms where the count of users that match your argument user list is highest. Is that correct ? If so, the code is simple.
You haven't given the tables. I will assume room, user, room_user, with deadly ids on the first two, and a composite key on the third. I can give you the SQL solution, you will have to work out how to do it in the non-SQL.
Another thing I want to consider is how to pass this list to the database. Should I construct a query by concatenating AND userid = 1 AND userid = 3 AND userid = 5 and so on? Or is there a way to pass the values as an array into a stored procedure?
To pass the list to the stored proc, because it needs a single calling parm, the length of which is variable, you have to create a CSV list of users. Let's call that parm #user_list. (Note, that is not contemplating the data, that is passing a list to a proc in a single parm, because you can't pass an unknown number of identified users to a proc otherwise.)
Since you constructed the #user_list on the client, you may as well compute #user_count (the number of members in the list) while you are at it, on the client, and pass that to the proc.
Something like:
CREATE PROC room_user_match_sp (
#user_list CHAR(255),
#user_count INT
...
)
AS
-- validate parms, etc
...
SELECT room_id,
match_count,
match_count / #user_count * 100 AS match_pct
FROM (
SELECT room_id,
COUNT(user_id) AS match_count -- no of users matched
FROM room_user
WHERE user_id IN ( #user_list )
GROUP BY room_id -- get one row per room
) AS match_room -- has any matched users
WHERE match_count = MAX( match_count ) -- remove this while testing
It is not clear, if you want full matches only. In that case, use:
WHERE match_count = #user_count
Expectation
You have asked for a proc-based solution, so I have given that. Yes, it is the fastest. But keep in mind that for this kind of requirement and solution, you could construct the SQL string on the client, and execute it on the "server" in the usual manner, without using a proc. The proc is faster here only because the code is compiled and that step is removed, as opposed to that step being performed every time the client calls the "server" with the SQL string.
The point I am making here is, with the data in a reasonably Relational form, you can obtain the result you are seeking using a single SELECT statement, you don't have to mess around with work tables or temp tables or intermediate steps, which requires a proc. Here, the proc is not required, you are implementing a proc for performance reasons.
I make this point because it is clear from your question that your expectation of the solution is "gee, I can't get the result directly, I have work with the data first, I am ready and willing to do that". Such intermediate work steps are required only when the data is not Relational.
Maybe not the most efficient SQL, but something like:
SELECT x.room_id,
SUM(x.occupants) AS occupants,
SUM(x.selectees) AS selectees,
SUM(x.selectees) / SUM(x.occupants) as percentage
FROM ( SELECT room_id,
COUNT(user_id) AS occupants,
NULL AS selectees
FROM Rooms
GROUP BY room_id
UNION
SELECT room_id,
NULL AS occupants,
COUNT(user_id) AS selectees
FROM Rooms
WHERE user_id IN (1,3)
GROUP BY room_id
) x
GROUP BY x.room_id
ORDER BY percentage DESC
will give you a list of rooms ordered by the "best fit" percentage
ie. it works out a percentage of fulfilment based on the number of people in the room, and the number of people from your set who are in the room

Resetting column ID's [duplicate]

This question already has answers here:
How to handle fragmentation of auto_increment ID column in MySQL
(5 answers)
how to reindex mysql table
(7 answers)
Closed 9 years ago.
I currently have a database which looks a lot like the one below:
1 citrus
2 pear
4 apple
5 melon
8 mango
The numbers represent a column that hold the row numbers but because rows are often deleted they get messed up quite often. With what MySQL query in PHP could I recount these rows so they would make sense again?
You should never re-arrange ID's in a relational database. At least not if they are to be used as a foreign key. (Which I bet they would be, otherwise what's the sense of that ID?)
Your fruits table: 1=>citrus, 2=>pear, 4=>apple, 5=>melon, 8=>mango
Consider having another table, colors holding 1=>red, 2=>yellow, 3=>green.
And now consider having a table fruit_color holding 1=>2 , 2=>3, 4=>3, 5=>2, 8=>1.
Now what would happen if I were to just go rearrange your fruits table?... The relations would get messed up.
The ID associated with each row is a primary key, and is generally auto_increment'd on each insert. This id is used as the unique identification for each row, so that a query can be used to select it, and it alone.
As you delete data, the rows will remain lined up in order from lowest id to highest id, the highest id being the last row inserted.
It is normal for the database to have gaps, but you can also manually assign the id, granted you know that it does not already exist in the database.
If you want to keep your data in a specific order, you could assign an index to each object, representing its rank, and using an order by command in the query.
SELECT id, fruit, rank FROM fruits ORDER BY rank ASC;
What you are trying to do is going to be (A) complicated code, and (B) a nightmare to update, which means that (C) odds are good it is going to screw up your database. As nl-x suggests, your best bet is to leave your PRIMARY KEYs as is. I would recommend, however, if you want to have a set ID, to assign it when you pull it with PHP.
function get_fruits() {
//SQL query
$query = ...
//SQL result
$i = 0;
while($row = $query->fetch_assoc()) {
$rows[$i] = $row;
}
return $rows;
}
Now when you cycle through your rows you'll be able to treat each array key as the rank, and the end user will be none the wiser, all while keeping your database's integrity up to snuff.
** Pulling Data by Order**
If for whatever reason you needed to be able to pull by the record number rather than the ID, you can also get the 5th record from the following table with the following query.
ID | fruit_name
----------------
1 | Apple
2 | Banana
4 | Kiwi
7 | Coconut
9 | Strawberry
The Query in PHP:
$sql = "SELECT fruit_name FROM fruit_table LIMIT $i,1"
That will pull the $i+1 record. Meaning if you want to get the first record, $i=0. This is the way most for loops are executed, and MySQL auto increments by default will start with 1.

Select random row per distinct field value?

I have a MySQL query that results in something like this:
person | some_info
==================
bob | pphsmbf24
bob | rz72nixdy
bob | rbqqarywk
john | kif9adxxn
john | 77tp431p4
john | hx4t0e76j
john | 4yiomqv4i
alex | n25pz8z83
alex | orq9w7c24
alex | beuz1p133
etc...
(This is just a simplified example. In reality there are about 5000 rows in my results).
What I need to do is go through each person in the list (bob, john, alex, etc...) and pull out a row from their set of results. The row I pull out is sort of random but sort of also based on a loose set of conditions. It's not really important to specify the conditions here so I'll just say it's a random row for the example.
Anyways, using PHP, this solution is pretty simple. I make my query and get 5000 rows back and iterate through them pulling out my random row for each person. Easy.
However, I'm wondering if it's possible to get what I would from only a MySQL query so that I don't have to use PHP to iterate through the results and pull out my random rows.
I have a feeling it might involve a BUNCH of subselects, like one for each person, in which case that solution would be more time, resource and bandwidth intensive than my current solution.
Is there a clever query that can accomplish this all in one command?
Here is an SQLFiddle that you can play with.
To get a random value for a distinct name use
SELECT r.name,
(SELECT r1.some_info FROM test AS r1 WHERE r.name=r1.name ORDER BY rand() LIMIT 1) AS 'some_info'
FROM test AS r
GROUP BY r.name ;
Put this query as it stands in your sqlfiddle and it will work
Im using r and r1 as table alias names. This will also use a subquery to select a random some_info for the name
SQL Fiddle is here
My first response would be to use php to generate a random number:
$randId = rand($min, $max);
Then run a SQL query that only gets the record where your index equals $randID.
Here is the solution:
select person, acting from personel where id in (
select lim from
(select count(person) c, min(id) i, cast(rand()*(count(person)-1) +min(id)
as unsigned) lim from personel group by person order by i) t1
)
The table used in the example is below:
create table personel (
id int(11) not null auto_increment,
person char(16),
acting char(19),
primary key(id)
);
insert into personel (person,acting) values
('john','abd'),('john','aabd'),('john','adbd'),('john','abfd'),
('alex','ab2d'),('alex','abd3'),('alex','ab4d'),('alex','a6bd'),
('max','ab2d'),('max','abd3'),('max','ab4d'),('max','a6bd'),
('jimmy','ab2d'),('jimmy','abd3'),('jimmy','ab4d'),('jimmy','a6bd');
You can limit the number of queries, and order by "rand()" to get your desired result.
Perhaps if you tried something like this:
SELECT name, some_info
FROM test
WHERE name = 'tara'
ORDER BY rand()
LIMIT 1

MySQL: How to get a sequential number with rows?

How can I number my results where the lowest ID is #1 and the highest ID is the #numberOfResults
Example: If I have a table with only 3 rows in it. whose ID's are 24, 87, 112 it would pull like this:
ID 24 87 112
Num 1 2 3
The reason why I want this, is my manager wants items to be numbered like item1, item2, etc. I initially made it so it used the ID but he saw them like item24, item87, item112. He didn't like that at all and wants them to be like item1, item2, item3. I personally think this is going to lead to problems because if you are deleting and adding items, then item2 will not always refer to the same thing and may cause confusion for the users. So if anyone has a better idea I would like to hear it.
Thanks.
I agree with the comments about not using a numbering scheme like this if the numbers are going to be used for anything other than a simple ordered display of items with numbers. If the numbers are actually going to be tied to something, then this is a really bad idea!
Use a variable, and increment it in the SELECT statement:
SELECT
id,
(#row:=#row+1) AS row
FROM table,
(SELECT #row:=0) AS row_count;
Example:
CREATE TABLE `table1` (
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
INSERT INTO table1 VALUES (24), (87), (112);
SELECT
id,
(#row:=#row+1) AS row
FROM table1,
(SELECT #row:=0) AS row_count;
+-----+------+
| id | row |
+-----+------+
| 24 | 1 |
| 87 | 2 |
| 112 | 3 |
+-----+------+
How it works
#row is a user defined variable. It is necessary to set it to zero before the main SELECT statement runs. This can be done like this:
SELECT #row:=0;
or like this:
SET #row:=0
But it is handy to tie the two statements together. This can be done by creating a derived table, which is what happens here:
FROM table,
(SELECT #row:=0) AS row_count;
The the second SELECT actually gets run first. Once that's done, it's just a case of incrementing the value of #row for every row retrieved:
#row:=#row+1
The #row value is incremented every time a row is retrieved. It will always generate a sequential list of numbers, no matter what order the rows are accessed. So it's handy for some things, and dangerous for other things...
Sounds like it would be better just making that number in your code instead of trying to come up with some sort of convoluted way of doing it using SQL. When looping through your elements, just maintain the sequentiality there.
What is the ID being used for?
If it's only for quick and easy reference then that's fine, but if it's to be used for deleting or managing in any way as you mentioned then your only option would be to assign a new ID column that is unique for each row in the table. Doing this is pointless though because that duplicates the purpose of your initial ID column.
My company had a similar challenge on a CMS system that used an order field to sort the articles on the front page of the site. The users wanted a "promote, demote" icon that they could click that would move an article up or down.
Again, not ideal, but the strategy we used was to build a promote function and accompanying demote function that identified the current sort value via query, added or subtracted one from the previous or next value, respectively, then set the value of the initially promoted/demoted item. It was also vital to engineer the record insert to accurately set the initial value of newly added records so inserts wouldn't cause a duplicate value to be added. This was also enforced at the DB level for safety's sake. The user was never allowed to directly key in the value of the sort, only promote or demote via icons. To be honest, it worked quite well for the user.
If you have to go this route.....it's not impossible. But there is brain damage involved....

MySQL - auto decrementing value

Let's say that I've got a table, like that (id is auto-increment):
id | col1 | col2
1 | 'msg'| 'msg'
2 | 'lol'| 'lol2'
3 | 'xxx'| 'x'
Now, I want to delete row number 2 and I get something like this
id | col1 | col2
1 | 'msg'| 'msg'
3 | 'xxx'| 'x'
The thing is, what I want to get is that:
id | col1 | col2
1 | 'msg'| 'msg'
2 | 'xxx'| 'x'
How can I do that in the EASIEST way (my knowledge about MySQL is very poor)?
You shouldn't do that.
Do not take an auto-incremented unique identifier as an ordinal number.
The word "unique" means that the identifier should be stuck to its row forever.
There is no connection between these numbers and enumerating.
Imagine you want to select records in alphabetical order. Where would your precious numbers go?
A database is not like an ordered list, as you probably think. It is not a flat file with rows stored in a predefined order. It has totally different ideology. Rows in the database do not have any order. And will be ordered only at select time, if it was explicitly set by ORDER BY clause.
Also, a database is supposed to do a search for you. So you can tell that with filtered rows or different ordering this auto-increment number will have absolutely nothing to do with the real rows positions.
If you want to enumerate the output - it's a presentation layer's job. Just add a counter on the PHP side.
And again: these numbers supposed to identify a certain record. If you change this number, you'd never find your record again.
Take this very site for example. Stack Overflow identifies its questions with such a number:
stackoverflow.com/questions/3132439/mysql-auto-decrementing-value
So, imagine you saved this page address to a bookmark. Now Jeff comes along and renumbers the whole database. You press your bookmark and land on the different question. Whole site would become a terrible mess.
Remember: Renumbering unique identifiers is evil!
I think there is no way to this directly. Maybe you can do "update" operation. But you must do it for all record after your deleted record. It is very bad solution for this.
Why using an auto-increment if you want to change it manually?
It is not good practice to change the value of an auto_increment column. However, if you are sure you want to, the following should help.
If you are only deleting a single record at a time, you could use a transaction:
START TRANSACTION;
DELETE FROM table1 WHERE id = 2;
UPDATE table1 SET id = id - 1 WHERE id > 2;
COMMIT;
However if you delete multiple records, you will have to drop the column and re-add it. It is probably not guaranteed to put the rows in the same order as previously.
ALTER TABLE table1 DROP id;
ALTER TABLE table1 ADD id INTEGER NOT NULL AUTO_INCREMENT;
Also, if you have data that relies on these IDs, you will need to make sure it is updated.
You can renumber the whole table like this:
SET #r := 0;
UPDATE mytable
SET id = (#r := #r + 1)
ORDER BY
id;

Categories