Reorder rows in a MySQL table - mysql

I have a table:
+--------+-------------------+-----------+
| ID | Name | Order |
+--------+-------------------+-----------+
| 1 | John | 1 |
| 2 | Mike | 3 |
| 3 | Daniel | 4 |
| 4 | Lisa | 2 |
| 5 | Joe | 5 |
+--------+-------------------+-----------+
The order can be changed by admin hence the order column. On the admin side I have a form with a select box Insert After: to entries to the database. What query should I use to order+1 after the inserted column.
I want to do this in a such way that keeps server load to a minimum because this table has 1200 rows at present. Is this the correct way to save an order of the table or is there a better way?
Any help appreciated
EDIT:
Here's what I want to do, thanks to itsmatt:
want to reorder row number 1 to be after row 1100, you plan to leave 2-1100 the same and then modify 1 to be 1101 and increment 1101-1200

You need to do this in two steps:
UPDATE MyTable
SET `Order` = `Order` + 1
WHERE `Order` > (SELECT `Order`
FROM MyTable
WHERE ID = <insert-after-id>);
...which will shift the order number of every row further down the list than the person you're inserting after.
Then:
INSERT INTO MyTable (Name, `Order`)
VALUES (Name, (SELECT `Order` + 1 FROM MyTable WHERE ID = <insert-after-id>));
To insert the new row (assuming ID is auto increment), with an order number of one more than the person you're inserting after.

Just add the new row in any normal way and let a later SELECT use ORDER BY to sort. 1200 rows is infinitesimally small by MySQL standards. You really don't have to (and don't want to) keep the physical table sorted. Instead, use keys and indexes to access the table in a way that will give you what you want.

you can
insert into tablename (name, `order`)
values( 'name', select `order`+1 from tablename where name='name')
you can also you id=id_val in your inner select.
Hopefully this is what you're after, the question isn't altogether clear.

Related

get one record at a time from joint table

I want to get a record from a joint table at a time. But I don't hope the tables are joined as a whole.
The actual tables are as follow.
table contents -- stores content information.
+----+----------+----------+----------+-------------------+
| id | name |status |priority |last_registered_day|
+----+----------+----------+----------+-------------------+
| 1 | content_1|0 |1 |2020/10/10 11:20:20|
| 2 | content_2|2 |1 |2020/10/10 11:21:20|
| 3 | content_3|2 |2 |2020/10/10 11:22:20|
+----+----------+----------+----------+-------------------+
table clusters -- stores cluster information
+----+----------+
| id | name |
+----+----------+
| 1 | cluster_1|
| 2 | cluster_2|
+----+----------+
table content_cluster -- each record indicates that one content is on one cluster
+----------+----------+-------------------+
|content_id|cluster_id| last_update_date|
+----------+----------+-------------------+
| 1 | 1 |2020-10-01T11:30:00|
| 2 | 2 |2020-10-01T11:30:00|
| 3 | 1 |2020-10-01T10:30:00|
| 3 | 2 |2020-10-01T10:30:00|
+----------+----------+-------------------+
By specifying a cluster_id, I want to get one content name at a time where contents.status=2 and (contents name, cluster_id) pair is in content_cluster. The query in sql is something like follow.
SELECT contents.name
FROM contents
JOIN content_cluster
ON contents.content_id = content_cluster.content_id
where contents.status = 2
AND content_cluster.cluster_id = <cluster_id>
ORDER
BY contents.priority
, contents.last_registered_day
, contents.name
LIMIT 1;
However, I don't want the tables to be joined as a whole every time as I have to do it frequently and the tables are large. Is there any efficient way to do this? I can add some indices to the tables. What should I do?
I would try writing the query like this:
SELECT c.name
FROM contents c
WHERE EXISTS (SELECT 1
FROM content_cluster cc
WHERE cc.content_id = c.content_id AND
cc.cluster_id = <cluster_id>
) AND
c.status = 2
ORDER BY c.priority, c.last_registered_day, c.name
LIMIT 1;
Then create the following indexes:
content(status, priority, last_registered_day, name, content_id, name)
content_cluster(content_id, cluster_id).
The goal is for the execution plan to scan the index for context and for each row, look up to see if there is a match in content_cluster. The query stops at the first match.
I can't guarantee that this will generate that plan (avoiding the sort), but it is worth a try.
This query can easily be optimized by applying correct indexes. Apply the alter statements I am mentioning below. And let me know if the performance have considerably increased or not:
alter table contents
add index idx_1 (id),
add index idx_2(status);
alter table content_cluster
add index idx_1 (content_id),
add index idx_2(cluster_id);
If a content can be in multiple clusters and the number of clusters can change, I think that doing a join like this is the best solution.
You could try splitting your contents table into different tables each containing the contents of a specific cluster, but it would need to be updated frequently.

Get most unique combinations of 2 pictures

I have a mySQL table that holds n number of pictures.
+------------+--------------+
| picture_id | picture_name |
+------------+--------------+
| 1 | ben.jpg |
| 2 | nick.jpg |
| 3 | mark.jpg |
| 4 | james.jpg |
| .. | ... |
| n | abraham.jpg |
+------------+--------------+
For a web application, i need to display 2 pictures simultaneously where the user can vote for one picture or the other. After voting, the user gets a new set of two pictures.
(application use interface)
+---------------------+--------------------+
| Vorte for picture 1 | Vote for picture 2 |
+---------------------+--------------------+
I would like to avoid displaying the same combinations as much as possible. I can create a helper table that will hold all possible combinations.
+----------------+--------------+--------------+
| combination_id | picture_id_1 | Picture_id_2 |
+----------------+--------------+--------------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
| 4 | 1 | 5 |
| .. | .. | .. |
| (n^2-n)/2 | .. | .. |
+----------------+--------------+--------------+
but for 100 pictures, that would be (100^2 - 100)/2 = 4950 (edit) rows, and with every added picture the table would grow exponentially. (which is not a big issue in todays computing i suppose)
But how do i query this table in a way that the user always sees as little duplicates as possible.
Expected result:
run 1: picture_id's = 4,5 (any numbers between 1 and n)
run 2: picture_id's = 2,7
run 3: picture_id's = 5 and 20
...
DEMO:http://rextester.com/VNWIOA4679 (added 100 pic samples) 2 sec query for 1 user w/o any indexes.
I see no need for a helper table as the data can easily be constructed on the fly with proper indexes. at 1000 pictures you're looking at 499,500 combinations a user could vote upon. still easily managed within a database construct as we operate on a set level, not a record level.
Here's one way assuming my own table structures. I can't think of a more efficient way to store/process the data.
Using this approach as new pictures are added the query will generate a larger and larger combination set but always exclude those on which a user has already voted. no code changes for new pics, no regenerating sets just processing each time the ones a user hasn't made a selection upon.
Create table SO46205797_Pics(
PICID int);
Insert into SO46205797_Pics values (1);
Insert into SO46205797_Pics values (2);
Insert into SO46205797_Pics values (3);
Insert into SO46205797_Pics values (4);
Insert into SO46205797_Pics values (5);
Insert into SO46205797_Pics values (6);
Insert into SO46205797_Pics values (7);
Create table SO46205797_UserPicResults (
USERID int,
PICID int,
PICID2 int,
PICChoiceID int);
Insert into SO46205797_UserPicResults values (1,1,2,1);
Insert into SO46205797_UserPicResults values (1,1,3,1);
Insert into SO46205797_UserPicResults values (1,1,4,4);
magic happens here the above was just data setup.
SELECT A.PICID, B.PICID, C.PICChoiceID
FROM SO46205797_Pics A
INNER JOIN SO46205797_Pics B
on A.PICID < B.PICID
LEFT JOIN SO46205797_UserPicResults C
on A.PICID = C.PicID
and B.PICID = C.PICID2
and C.USERID = 1
WHERE C.userID is null;
Note that if we eliminate the C.userID is null part then we see all of the possible combinations (for user1) (note I treat ID 1, 2 the same as ID 2,1 which I think youw ant) for the 2 photos and which ones the user has selected. Since we don't want to display that choice again, we use the c.userID is null to exclude combinations the user already made a choice for.
Also when saving data to the userPicResults, you need to ensure PICID1 is always less than PICID2.
A different way to do this is using a not exists which may be slightly faster.
obviously indexes on USERID, PICID, PICID2 and in that order would be beneficial (i'd probably make it the a combined PK) for SO46205797_UserPicResults and an index on PICID for SO46205797_Pics as the PK.
SELECT A.PICID, B.PICID
FROM SO46205797_Pics A
INNER JOIN SO46205797_Pics B
on A.PICID < B.PICID
WHERE not exists (SELECT *
FROM SO46205797_UserPicResults C
WHERE A.PICID = C.PicID
and B.PICID = C.PICID2
and C.USERID = 1);
I considered maintaining a parent/child relationship for each image for each user; but this approach doesn't store the choices for all combinations.
The goal of this application is to let people vote for one picture against another, right? Then you need to have some kind of vote results table:
vote_results:
| vote_id | user_id | vote_up_picture_id | vote_down_picture_id | ...
Then, based on data from this table you can easily show to a user picture pairs, which he haven't seen yet:
select first.picture_id, second.picture_id
from pictures as first, pictures as second
where not exists(
select * from vote_results v
where (v.vote_up_picture_id = first.picture_id and v.vote_down_picture_id = second.picture_id)
or (v.vote_up_picture_id = second.picture_id and v.vote_down_picture_id = first.picture_id)
) and first.picture_id != second.picture_id
order by rand()
limit 1
PS. As you see, there is no need in helper table with combination_id

MySql: Copy sql rows and keep some fields the same while updating others

I have a table with three columns, I want copy/paste all three columns within the same table, however, of the three columns, I want to update two columns with new data specific for that day while keeping one column the same. For the following table:
ticket_number | book_id | log_id
------------- | ------- | ------
1 | 1 | 120
12 | 2 | 120
23 | 3 | 120
I want to:
1) Copy all columns and paste into the same table
2) change the ticket_number column with new data for that day (e.g. 2, 13, 25) as well as the log_id column with the id for the current day (e.g. 121), while keeping book_id column the same.
I have tried with no avail:
INSERT INTO ticket (ticket_number, book_id, log_id) SELECT (2,13,24), (book_id), (121) FROM ticket;
This the schema for reference
Your SELECT query needs to return the rows that you want to insert.
UPDATE: You can use a separate table, which might be easier. Something like this:
CREATE TABLE id_map (
old_ticket_number NUMBER,
new_ticket_number NUMBER
);
You could insert the values into this table.
You can then use this query:
INSERT INTO ticket (ticket_number, book_id, log_id)
SELECT
m.new_ticket_number,
t.book_id,
121
FROM ticket t
INNER JOIN id_map m ON t.ticket_number = m.old_ticket_number;
Does this so what you're looking for?

Delete all rows except first N from a table having single column

I need a single query. Delete all rows from the table except the top N rows. The table has only one column. Like,
|friends_name|
==============
| Arunji |
| Roshit |
| Misbahu |
| etc... |
This column may contain repeated names as well.
Contains repeated names
Only one column.
If you can order your records by friends_name, and if there are no duplicates, you could use this:
DELETE FROM names
WHERE
friends_name NOT IN (
SELECT * FROM (
SELECT friends_name
FROM names
ORDER BY friends_name
LIMIT 10) s
)
Please see fiddle here.
Or you can use this:
DELETE FROM names ORDER BY friends_name DESC
LIMIT total_records-10
where total_records is (SELECT COUNT(*) FROM names), but you have to do this by code, you can't put a count in the LIMIT clause of your query.
If you don't have an id field, i suppose you use an alphabetic order.
MYSQL
DELETE FROM friends
WHERE friends_name
NOT IN (
SELECT * FROM (
SELECT friends_name
FROM friends
ORDER BY friends_name ASC
LIMIT 10) r
)
You delete all rows exept the 10 firsts (alphabetic order)
I just wanted to follow up on this relatively old question because the existing answers don't capture the requirement and/or are incorrect. The question states the names can be repeated, but only the top N must be preserved. Other answers will delete incorrect rows and/or incorrect number of them.
For example, if we have this table:
|friends_name|
==============
| Arunji |
| Roshit |
| Misbahu |
| Misbahu |
| Roshit |
| Misbahu |
| Rohan |
And we want to delete all but top 3 rows (N = 3), the expected result would be:
|friends_name|
==============
| Arunji |
| Roshit |
| Misbahu |
The DELETE statement from the currently selected answer will result in:
|friends_name|
==============
| Arunji |
| Misbahu |
| Misbahu |
| Misbahu |
See this sqlfiddle. The reason for this is that it first sorts names alphabetically, then takes top 3, then deletes all that don't equal that. But since they are sorted by name they may not be the top 3 we want, and there's no guarantee that we'll end up with only 3.
In the absence of unique indexes and other fields to determine what "top N" means, we go by the order returned by the database. We could be tempted to do something like this (substitute 99999 with however high number):
DELETE FROM names LIMIT 99999 OFFSET 3
But according to MySQL docs, while the DELETE supports the LIMIT clause, it does not support OFFSET. So, doing this in a single query, as requested, does not seem to be possible; we must perform the steps manually.
Solution 1 - temporary table to hold top 3
CREATE TEMPORARY TABLE temp_names LIKE names;
INSERT INTO temp_names SELECT * FROM names LIMIT 3;
DELETE FROM names;
INSERT INTO names SELECT * FROM temp_names;
Here's the sqlfiddle for reference.
Solution 2 - new table with rename
CREATE TABLE new_names LIKE names;
INSERT INTO new_names SELECT * FROM names LIMIT 3;
RENAME TABLE names TO old_names, new_names TO names;
DROP TABLE old_names;
Here's the sqlfiddle for this one.
In either case, we end up with top 3 rows in our original table:
|friends_name|
==============
| Arunji |
| Roshit |
| Misbahu |

Need to insert CSV values in MySQL column to another table

I have a CSV file containing user information:
'Arlington', '1,3,5,7,9'
'StackExchange', '2,3'
And I will need the above information imported like this:
"User" table:
id | name
1 | 'Arlington'
2 | 'StackExchange'
"User groups" table:
id | user_id | group_id
1 | 1 | 1
2 | 1 | 3
3 | 1 | 5
4 | 1 | 7
5 | 1 | 9
6 | 2 | 2
7 | 2 | 3
What's the easiest way to do this? I have imported the data with a temp column holding the CSV values:
id | name | tmp_group_ids
1 | 'Arlington' | '1,3,5,7,9'
2 | 'StackExchange' | '2,3'
I am thinking if I import it this way, I will know exactly what id gets assigned for the user (the id column in the users table is auto_increment), and so I can use that id as user_id for the "user groups" table.
But now how do I get values from tmp_group_ids into the "User groups" table?
Would appreciate any help! Thanks!
the easy way would be a php or perl script.
You can use the MySQL SUBSTRING() function to split the string and insert the different values into the table. You can do this by writing a function or using a stored procedure.
I had recently a similar problem, I used the function SUBSTRING_INDEX(str,delim,count), using "," as delimiter
http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_substring-index
INSERT INTO tableUserGroup (userid, groupid)
SELECT
t1.id
, substring_index(t1.tmp_group_ids,',',2)
, substring_index(t1.tmp_group_ids,',',3)
FROM table1 t1
First, insert the names into the User table - with id autonumber, this will work:
INSERT INTO User
(name)
SELECT DISTINCT
name
FROM TempTable
Then:
--- Create a "numbers" table:
CREATE TABLE num
( i INT PRIMARY KEY
) ;
--- Populate it with numbers:
INSERT INTO num
(i)
VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
Then, you can use FIND_IN_SET() function which is handy for this situation (splitting comma-separated fields), like this:
INSERT INTO User_Groups
(user_id, group_id)
SELECT
u.id AS user_id
, num.i AS group_id
FROM User AS u
JOIN TempTable AS t
ON t.name = u.name
JOIN num
ON FIND_IN_SET(num.i, t.tmp_group_ids) > 0