I have an MySQL table that I would like sorted in a particular order when displayed on a Web page using PHP. In order to accomplish this I have a "displayorder" column with unique values. Rows with lower values in this column are displayed first.
The problem with this is that inserting new rows manually is difficult, because I have to adjust the "displayorder" for many many rows in order for the new row to fit. Is there a better way to do this that I don't know about?
The desired order may look like this:
Action
Adventure
MMO
Roleplaying
Simulation
Strategy
Software
Reference
One fairly simple way to do this is to create the sort column as decimal rather than an integer. This way, you start your table with values such as 1, 2, 3 etc', and when you want to insert a record between 1 and 2 you simply insert it as 1.5.
For a table that updates frequently, you want to choose a large number of decimal digits (the maximum, decimal(65,30) would probably be too big for this, but it's a possible choice).
You can create a procedure that will perform the insert and the shifting of order and use it instead of writting INSERT INTO ... in php
In example :
DROP TABLE IF EXISTS test;
CREATE TABLE test
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
orderPlace INT NOT NULL,
datas VARCHAR(255) NOT NULL DEFAULT 'foo'
);
INSERT INTO test (orderPlace) VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
DROP PROCEDURE IF EXISTS InsertNewRowAndShiftOrder;
DELIMITER $
CREATE PROCEDURE InsertNewRowAndShiftOrder(IN OrderValue INT,
IN SomeData VARCHAR(255))
BEGIN
INSERT INTO test VALUES (DEFAULT, OrderValue, SomeData);
SET #LastId = LAST_INSERT_ID();
UPDATE test SET orderPlace = orderPlace + 1
WHERE orderPlace >= OrderValue
AND id <> #LastId;
END$
DELIMITER ;
CALL InsertNewRowAndShiftOrder(5, 'bar');
SELECT * FROM test ORDER BY orderPlace;
This outputs :
id orderPlace datas
1 1 foo
2 2 foo
3 3 foo
4 4 foo
11 5 bar
5 6 foo
6 7 foo
7 8 foo
8 9 foo
9 10 foo
10 11 foo
Note : I can't provide a DB-fiddle link, because this doesn't work, for some reasons on that website. I tested on my localhost MySQL DB and it worked
Related
I have a table someTable with a column bin of type VARCHAR(4). Whenever I insert to this table, bin should be a unique combination of characters and numbers. Unique in this sense meaning has not appeared before in the table in another row.
bin is in the form of AA00, where A is a character A-F and 0 is a number 0-9.
Say I insert to this table once: it should come up with a bin value which doesn't appear before. Assuming the table was empty, the first bin could be AA11. On second insertion, it should be AA12, and then AA13, etc.
AA00, AA01, ... AA09, AA10, AA11, ... AA99, AB00, AB01, ... AF99, BA00, BA01, ... FF99
It doesn't matter this table can contain only 3,600 possible rows. How do I create this code, specifically finding a bin that doesn't already exist in someTable? It can be in order as I've described or a random bin, as long as it doesn't appear twice.
CREATE TABLE someTable (
bin VARCHAR(4),
someText VARCHAR(32),
PRIMARY KEY(bin)
);
INSERT INTO someTable
VALUES('?', 'a');
INSERT INTO someTable
VALUES('?', 'b');
INSERT INTO someTable
VALUES('?', 'c');
INSERT INTO someTable
VALUES('?', 'd');
Alternatively, I can use the below procedure to insert instead:
CREATE PROCEDURE insert_someTable(tsomeText VARCHAR(32))
BEGIN
DECLARE var (VARCHAR(4) DEFAULT (
-- some code to find unique bin
);
INSERT INTO someTable
VALUES(var, tsomeText);
END
A possible outcome is:
+------+----------+
| bin | someText |
+------+----------+
| AB31 | a |
| FC10 | b |
| BB22 | c |
| AF92 | d |
+------+----------+
As Gordon said, you will have to use a trigger because it is too complex to do as a simple formula in a default. Should be fairly simple, you just get the last value (order by descending, limit 1) and increment it. Writing the incrementor will be somewhat complicated because of the alpha characters. It would be much easier in an application language, but then you run into issues of table locking and the possibility of two users creating the same value.
A better method would be to use a normal auto-increment primary key and translate it to your binary value. Consider your bin value as two base 6 characters followed by two base 10 values. You then take the id generated by MySQL which is guaranteed to be unique and convert to your special number system. Calculate the bin and store it in the bin column.
To calculate the bin:
Step one would be to get the lower 100 value of the decimal number (mod 100) - that gives you the last two digits. Convert to varchar with a leading zero.
Subtract that from the id, and divide by 100 to get the value for the first two digits.
Get the mod 6 value to determine the 3rd (from the right) digit. Convert to A-F by index.
Subtract this from what's left of the ID, and divide by 6 to get the 4th (from the right) digit. Convert to A-F by index.
Concat the three results together to form the value for the bin.
You may need to edit the following to match your table name and column names, but it should so what you are asking. One possible improvement would be to have it cancel any inserts past the 3600 limit. If you insert the 3600th record, it will duplicate previous bin values. Also, it won't insert AA00 (id=1 = 'AA01'), so it's not perfect. Lastly, you could put a unique index on bin, and that would prevent duplicates.
DELIMITER $$
CREATE TRIGGER `fix_bin`
BEFORE INSERT ON `so_temp`
FOR EACH ROW
BEGIN
DECLARE next_id INT;
SET next_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='so_temp');
SET #id = next_id;
SET #Part1 = MOD(#id,100);
SET #Temp1 = FLOOR((#id - #Part1) / 100);
SET #Part2 = MOD(#Temp1,6);
SET #Temp2 = FLOOR((#Temp1 - #Part2) / 6);
SET #Part3 = MOD(#Temp2,6);
SET #DIGIT12 = RIGHT(CONCAT("00",#Part1),2);
SET #DIGIT3 = SUBSTR("ABCDEF",#Part2 + 1,1);
SET #DIGIT4 = SUBSTR("ABCDEF",#Part3 + 1,1);
SET NEW.`bin` = CONCAT(#DIGIT4,#DIGIT3,#DIGIT12);
END;
$$
DELIMITER ;
I have a database with a couple of tables. I need to add a column in one table after the insertion of a new row in another table.
Table A: id | Type | Category | ShortDesc | LongDesc | Active
Row 1 int(11), varchar, varchar,varchar,varchar,int
Row 2
Row 3
Table B: id | Row1-ShortDesc | Row2-ShortDesc | Row3-ShortDesc
Row 1 int(11), tiny(1), tiny(1), tiny(1) etc...
Row 2
Row 3
When I occasionally add a new row (item) to TableA, I want a new column in TableB. TableA is a long evolving collection. A Row in TableA can not be removed for obvious legacy reasons.
So when I insert a row to TableA I need to have another column inserted/appended into TableB.
Any help would be appreciated.
TIA.
Answer derived from training in SQL
I was finally able to derive and create my trigger solution utilizing a class in SQL Server at MAX TRAINING in CINCINNATI OHIO.
--SQL CODE
-- Create a table called TableA that just holds some data for the trigger
-- This table has a primary Key seeded with 1 and incremented by 1
CREATE TABLE TableA(
id int identity(1,1) PRIMARY KEY,
name varchar(60) NOT NULL,
shortDesc varchar(60) NOT NULL,
longDesc varchar(60) NOT NULL,
bigDesc TEXT NOT NULL
)
GO
-- Create a table TableB that only has a ID column. ID as a primary key seeded with 1, incremented by 1
CREATE TABLE TableB(
id int identity(1,1) PRIMARY KEY
)
GO
-- Just to see the two tables with nothing in it.
select * from TableA
select * from TableB
GO
-- The actual trigger in TableA based upon an insert
CREATE TRIGGER TR_myInserCol
ON TableA
AFTER INSERT
AS
BEGIN
-- Don't count the trigger events
SET NOCOUNT ON;
-- Because we are making strings we declare some variables
DECLARE #newcol as varchar(60);
DECLARE #lastRow as int;
DECLARE #sql as varchar(MAX);
-- Now fill the variables
-- make sure we are looking at the last, freshly inserted row
SET #lastRow = (SELECT COUNT(*) FROM TableA);
-- Make a SELECT statement for the last row
SET #newcol = (SELECT shortDesc FROM TableA WHERE id = #lastRow);
-- Adds a new column in TableB is inserted based on a
-- TableA.shortDesc as the name of the new column.
-- You can use any row data you want but spaces and
-- special characters will require quotes around the field.
SET #sql = ('ALTER TABLE TableB ADD ' + #newcol + ' char(99)');
-- And run the SQL statement as a combined string
exec(#sql);
END;
GO
--Insert a new rows into TableA
--The trigger will fire and add a column in TableB
INSERT INTO TableA
(name,shortDesc,longDesc,bigDesc)
VALUES ('attract','Attraction','Attractions','Places to go see and have
fun');
GO
INSERT INTO TableA
(name,shortDesc,longDesc,bigDesc)
VALUES ('camp','Camp','CAMP GROUND','Great place to sleep next to a creek');
GO
(name,shortDesc,longDesc,bigDesc)
VALUES ('fuel','GasStation','Fueling Depot','Get gas and go');
GO
INSERT INTO TableA
(name,shortDesc,longDesc,bigDesc)
VALUES ('petstore','PetStore','Pet Store','Get a friend');
GO
-- See the newly created rows in TableA and the new Columns created in TableB
select * from TableA
select * from TableB
GO
-- Do not execute unless you want to delete the newly created tables.
-- Use this to delete your tables
-- Clean up your work space so you can make changes and try again.
DROP TABLE TableA;
DROP TABLE TableB;
GO
Thanks again to those that tried to help me out. And yes, I still understand this may not be the best solution but for me this works as I will only insert rows in TableA maybe a couple of times a year and will more than likely max out with less than 300 rows over the next several years as the data I am working with doesn't change that frequently and have a single row to access with a single bit (T/F) allows me to now quickly assign TableB's to locations and people for their search criteria and to generate a nice SQL query string without multiple reads across potentially several pages. Thanks again!
And if someone wants to add or modify what I have done, I'm all ears. It's all about learning and sharing.
Michael
I'm trying to insert a data as a primary ID that has one alphanumerical value and two numerical value in MySQL database. This data will auto incrementally generate number, but the alphanumerical value will be fixed. Like, D1, D2....D54, D55, D56 etc. Here, 'D' is always the same, but the number will be automatically incremented. Is there any way to do this?
First of all it's unadvisable to do so, like others commented, you can have this id value generated on the fly.
But if nonetheless you want it your way there're at least two ways to do so:
More or less reliable way involves using a separate table for sequencing and a trigger
Schema:
CREATE TABLE Table1_seq
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE Table1
(
`id` VARCHAR(10) NOT NULL PRIMARY KEY DEFAULT '',
...
);
Trigger:
DELIMITER $$
CREATE TRIGGER tg_bi_table1
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
INSERT INTO table1_seq() VALUES();
SET NEW.id = CONCAT('D', LPAD(LAST_INSERT_ID(), 4,'0'));
END$$
DELIMITER ;
Then you just insert your rows to table1
INSERT INTO Table1 () VALUES (),(),();
And you'll get
| ID |
---------
| D0001 |
| D0002 |
| D0003 |
Here is SQLFiddle demo
Unreliable way is to generate your new id on the fly in INSERT statement itself
INSERT INTO Table1 (id, ...)
SELECT CONCAT('D', LPAD(COALESCE(SUBSTR(MAX(id), 2), 0) + 1, 4, '0')),
...
FROM table1
Here is SQLFiddle demo
The problems with this approach:
Under heavy load two concurrent sessions can grab the same MAX(id) value and therefore generate the same new id leading to the failure of insert.
You can't use multi-insert statements
We can't set auto-increment for alphanumeric. In your case if D is always same then no need to add it to your pk field. Keep your constant in a separate field and add it when you select.
I have an auto increment column ID, and for some situation I wanted the other column to be equal to the primary key + 1 value
ID | other
1 | 2
2 | 3
3 | 4
4 | 123 (some situation, it is not always plus 1)
How can I achieve this?
Here's what I have tried
INSERT INTO table (`ID`,`other`) VALUES ('',(SELECT MAX(ID)+1 FROM table))
But that returns an error
You can't specify target table 'table' for update in FROM clause
Try Below query:
ALTER TABLE dbo.table ADD
Column AS ([ID]+1)
GO
It will definitely work
Using a normal AUTO_INCREMENT column as id, I cannot think of a way to do this in MySQL. Triggers, which otherwise would have been an option, don't work well with AUTO_INCREMENT columns.
The only way I see is to do two commands for an INSERT;
INSERT INTO bop (value) VALUES ('These values should be 1 and 2');
UPDATE bop SET other = id+1 WHERE id = LAST_INSERT_ID();
An SQLfiddle to test with.
The closest I'm getting to what you're looking for is to generate sequences separately from AUTO_INCREMENT using a function, and use that instead to generate the table id;
DELIMITER //
CREATE TABLE bop (
id INT UNIQUE,
other INT,
value VARCHAR(64)
)//
CREATE TABLE bop_seq ( seq INT ) // -- Sequence table
INSERT INTO bop_seq VALUES (1) // -- Start value
CREATE FUNCTION bop_nextval() RETURNS int
BEGIN
SET #tmp = (SELECT seq FROM bop_seq FOR UPDATE);
UPDATE bop_seq SET seq = seq + 1;
RETURN #tmp;
END//
CREATE TRIGGER bop_auto BEFORE INSERT ON bop
FOR EACH ROW
SET NEW.id = bop_nextval(), NEW.other=NEW.id + 1;
//
That'd let you do inserts and have it autonumber like you want. The FOR UPDATE should keep the sequence transaction safe, but I've not load tested so you may want to do that.
Another SQLfiddle.
I solved this by updating 2 times the DB..
I wanted to do +1 from 19 till ..
UPDATE `table` SET `id`=`id`+101 WHERE id <= 19
UPDATE `table` SET `id`=`id`-100 WHERE id <= 119 AND id >= 101
How can I make a table that has rows with order to one another and can be rearranged:
Example:
Rows:idappearance name
Records
(1,"john"),(2,"mike")
Now, I want to insert "Avi" between them:
not having to worry about rearranging them
(1,"john"),(2,"Avi"),(3,"mike")
This table can have a foreign key in
another table (like departments..).
idappearance is the order of appearance I want
to set, doesn't need to be PK.
It needs to handle about 50K of names so O(n) isn't best solution.
Simple solution would be having reasonable numerical gaps between records. In other words;
(10000,"John"),(20000,"mike")
(10000,"John"),(15000,"Avi"),(20000,"mike")
(10000,"John"),(12500,"tom"),(15000,"avi"),(20000,"mike")
etc..
Gap between records should be determined based on your data domain
You could have a trigger on inserts. I don't use MySQL, but here's the code for sql-server...
Basically, on an insert, the trigger increments the appearanceId of all rows with appearanceId which are equal to or greater than the new appearance id.
CREATE Table OrderedTable
(
id int IDENTITY,
name varchar(50),
appearanceOrder int
)
GO
CREATE TRIGGER dbo.MyTrigger
ON dbo.OrderedTable
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE OrderedTable SET
AppearanceOrder = AppearanceOrder + 1
WHERE AppearanceOrder >= (
SELECT TOP 1 AppearanceOrder
FROM inserted )
AND id NOT IN (
SELECT id
FROM inserted )
END
GO
INSERT INTO OrderedTable VALUES ('Alice', 1)
INSERT INTO OrderedTable VALUES ('Bob', 1)
INSERT INTO OrderedTable VALUES ('Charlie', 1)
INSERT INTO OrderedTable VALUES ('David', 1)
This returns David, Charlie, Bob, Alice as expected.
SELECT *
FROM OrderedTable
ORDER BY AppearanceOrder
Note that I haven't fully tested this implementation. One issue is that it will leave holes in the AppearanceOrder if items are deleted, or the inserts deliberately insert outside the current range. If these matter, they are left as an exercise to the reader ;-)
If appearance order were a double-precision floating point number, you could insert any name between any two adjacent names with a single insert. If you start with a table like this:
create table test (
person_id integer primary key,
person_name varchar(10) not null,
appearance_order double precision not null unique
);
insert into test values (100, 'John', 11);
insert into test values (23, 'Mike', 12);
Insert Avi between them by simply
insert into test values (3, 'Avi', 11.5);
Sort by the column 'appearance_order'.
select * from test order by appearance_order
100 John 11
3 Avi 11.5
23 Mike 12
Insert Eva between John and Avi by
insert into test values (31, 'Eva', 11.25);
select * from test order by appearance_order
100 John 11
31 Eva 11.25
3 Avi 11.5
23 Mike 12
You do need to separate identification from sort order. That means using one column for the id number (and as the target for foreign key references) and another for the appearance order. But, depending on your application, you might not need a unique constraint on appearance_order.