How can I add prefix to SQLite auto_increment field? - sql

Is there a way to add prefix to an auto increment field in SQLite so that when new records are added, the auto increment value contain the prefix?

Is there a way to add prefix to an auto increment field in sqlite so
that when new records are added, the auto increment value contain the
prefix?
No.
Such a column is a special column that is an alias of the rowid column. Such a column MUST be an integer value.
The intended use of such a column is really to be able to uniquely identify a row.
BUT.....
However, it would be possible to always return a value with a prefix e.g. by using
SELECT 'prefix'||mycolumn FROM mytable;
Working Example
:-
DROP TABLE IF EXISTS mytable;
CREATE TABLE IF NOT EXISTS mytable (mycolumn INTEGER PRIMARY KEY, myothercolumn TEXT);
INSERT INTO mytable (myothercolumn) VALUES('Fred'),('Mary'),('Jane'),('andsoon');
SELECT 'myprefix'||mycolumn, myothercolumn FROM mytable;
Note there is no need for the AUTOINCREMENT keyword that only imposes a constraint but is inefficient AUTOINCREMENT
Result
Mimicking what you want
It would be possible to replicate what you want e.g. :-
DROP TRIGGER IF EXISTS mytable2manager;
DROP TABLE IF EXISTS mytable2;
CREATE TABLE IF NOT EXISTS mytable2 (mycolumn TEXT, myothercolumn TEXT);
CREATE TRIGGER IF NOT EXISTS mytable2manager AFTER INSERT ON mytable2 BEGIN
UPDATE mytable2 SET mycolumn = 'myprefix'||(SELECT count() FROM mytable2) WHERE rowid = new.rowid;
END;
INSERT INTO mytable2 (myothercolumn) VALUES('Fred'),('Mary'),('Jane'),('andsoon');
SELECT * FROM mytable2;
This creates a table along with a TRIGGER and then inserts the same data as above.
The TRIGGER is actioned whenever a row is inserted and in this case it mimics what the SQLite auto-generation of the rowid value does prefixing the value
it could alternatively use SET mycolumn = 'myprefix'||rowid
Without the TRIGGER mycolumn would be null
Result
Additional
You could even have the TRIGGER in the mimicking method apply different prefixes. The following example uses a different prefix when the value in the myothercolumn starts with an M
:-
DROP TRIGGER IF EXISTS mytable2manager;
DROP TABLE IF EXISTS mytable2;
CREATE TABLE IF NOT EXISTS mytable2 (mycolumn TEXT, myothercolumn TEXT);
CREATE TRIGGER IF NOT EXISTS mytable2manager AFTER INSERT ON mytable2 BEGIN
UPDATE mytable2 SET mycolumn = CASE WHEN substr(new.myothercolumn,1,1) = 'M' THEN 'myprefix' ELSE 'myotherprefix' END||(SELECT count() FROM mytable2) WHERE rowid = new.rowid;
-- UPDATE mytable2 SET mycolumn = 'myprefix'||rowid WHERE rowid = new.rowid;
END;
INSERT INTO mytable2 (myothercolumn) VALUES('Fred'),('Mary'),('Jane'),('andsoon');
SELECT * FROM mytable2;
Result

The simplest method is probably to use a view:
create view v_t as
select ('prefix' || id) as new_id, t.*
from t;

Related

POSTGRESQL Trigger that updates a column after having altered the table

I have a populated table that already has some specific columns. I'd like to alter the table structure by adding a new column which will represent a field that is the count of the rows of another table.
Is it possible to implement it through a trigger in order to do it automatically after the alter command?
I have come up with something like this but apparently doesn't update the column:
Function for the trigger:
CREATE FUNCTION update_column() RETURNS TRIGGER AS
$BODY$
DECLARE
num INTEGER;
BEGIN
SELECT COUNT(*) INTO num FROM mytable1, mytable2 WHERE mytable1.field = mytable2.field GROUP BY mytable1.field;
UPDATE mytable1 SET new_column = num;
END;
$BODY$
LANGUAGE PLPGSQL;
The trigger:
CREATE TRIGGER insert
AFTER UPDATE
ON mytable1
FOR EACH ROW
EXECUTE PROCEDURE update_column();
And the alter command:
ALTER TABLE mytable1 ADD new_column INT;
Is it possible to implement it through a trigger in order to do it automatically after the alter command?
Firstly, your trigger fires after each update on the table, not after the alter table. Second, there would not be much point for a trigger on such DDL event anyway.
If your table has data already, you might need to initialize the column after its creation, with something like:
update mytable1 t1
set new_column = (select count(*) from mytable2 t2 where t2.field = t1.field)
As for the logic you wanted in the trigger: you need to use new and old to refer to the row that was changed in the original table - and, typically, you would expect the update to affect either no rows (if field was not changed), or two rows (if it was):
update mytable1 t1
set new_column = (select count(*) from mytable2 t2 where t2.field = t1.field)
where t1.field in (new.field, old.field) and new.field <> old.field
That said, I would not recommend such set up. Maintaining such derived information is expensive and tedious (you have an update trigger, now you need an insert and a delete trigger too). Instead, you can compute the logic on the fly when needed, or create a view:
create view myview as
select t1.*,
(select count(*) from mytable2 t2 where t2.field = t1.field) as new_column
from mytable1 t1
You would not set this value automatically in a trigger based on when the column is added. You would use a trigger to maintain the value when there are changes on the second table.
The process is:
Add the column with a default value:
alter table mytable1 add new_column int default 0;
Initialize the value of the column to the current values:
update mytable1 t1
set new_column = (select count(*) from mytable2 t2 where t2.field = t1.field);
Add insert/update/delete triggers on mytable2. The key logic is:
update mytable1
set cnt = cnt + 1
where t1.field = new.field;
update mytable1
set cnt = cnt - 1
where t1.field = old.field;
If you don't have a foreign key relationship, you may also need to insert a row in mytable1 if the field value is not in the table. However, I strongly recommend an explicit foreign key constraint to prevent this from happening.
This increments/decrements the value when changes are made to the second table.

Bulk insert, update if on conflict (bulk upsert) on Postgres

I am writing a data-mining program, which bulk inserts user data.
The current SQL is just a plain bulk insert:
insert into USERS(
id, username, profile_picture)
select unnest(array['12345']),
unnest(array['Peter']),
unnest(array['someURL']),
on conflict (id) do nothing;
How do I do an update if on conflict? I tried:
...
unnest(array['Peter']) as a,
unnest(array['someURL']) as b,
on conflict (id) do
update set
username = a,
profile_picture = b;
But it throws There is a column named "a" in table "*SELECT*", but it cannot be referenced from this part of the query. error.
EDIT:
Table of USERS is very simple:
create table USERS (
id text not null primary key,
username text,
profile_picture text
);
Turns out a special table named excluded contains the row-to-be-inserted
(strange name though)
insert into USERS(
id, username, profile_picture)
select unnest(array['12345']),
unnest(array['Peter']),
unnest(array['someURL'])
on conflict (id) do
update set
username = excluded.username,
profile_picture = excluded.profile_picture;
http://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT
The SET and WHERE clauses in ON CONFLICT DO UPDATE have access to the existing row using the table's name (or an alias), and to rows proposed for insertion using the special excluded table...
For bulk insert from another table if they are identical you can do it like that :
INSERT INTO table_a (SELECT * FROM table_b)
ON CONFLICT ON CONSTRAINT "pk_guid"
DO UPDATE SET
column1 = excluded.column1,
column2 = excluded.column2,
column3 = excluded.column3,
...... ;

How to keep in a database the number of calls of each record?

For example, I want to know at any moment most popular records that users searched in the database.
I expect that for each record I need to introduce a new number field. Thus, the record will be like this:
key - value - counter
How I can to increase the value of counter inside a database?
I think it's something like calling a stored procedure while a query, but I'm not sure. Perhaps the question is quite simple, I'm just a beginner and I apologize in that case.
You should use a trigger for this. Triggers are commands that execute on events, everytime an INSERT, UPDATE or DELETE statement is executed, even if their calls do not modify any records. Due tot this, you can't directly create a trigger for updating the count field of a record when you SELECT (read) it.
But, you can try a workaround in which you also have a date field in your table, and update it everytime a record is called. Use your application to send this datetime value to the database, which will trigger an UPDATE.
By making an UPDATE statement, your trigger is called and this way you can add your code to modify the count column.
CREATE TRIGGER tafter AFTER INSERT OR DELETE ON tbl1 FOR EACH ROW UPDATE SET counter = counter + 1 where key = 'keyval';
Firstly, this sounds like an awful performance problem. Every time you select a record you have to update it if you're tracking the selects with a single number, which just stores total selects, otherwise you have to insert timestamped values into another table to be able to analyse when the rows were read.
Anyway, you can do this with a common table expression in which you update a counter in the table and return the results to the main query: http://sqlfiddle.com/#!1/1aa41/6
Code something like:
create table my_table(col1 varchar(30), col2 numeric, select_count numeric);
insert into my_table values ('A',1,0);
insert into my_table values ('B',2,0);
insert into my_table values ('C',3,0);
insert into my_table values ('D',4,0);
insert into my_table values ('E',5,0);
with upd as (
update my_table
set select_count = select_count+1
where col1 = 'A'
returning *)
select *
from upd;
with upd as (
update my_table
set select_count = select_count+1
where col1 = 'B'
returning *)
select *
from upd;
with upd as (
update my_table
set select_count = select_count+1
where col1 = 'A'
returning *)
select *
from upd;
with upd as (
update my_table
set select_count = select_count+1
returning *)
select count(*)
from upd;
with upd as (
update my_table
set select_count = select_count+1
returning *)
select sum(col2)
from upd;
with upd as (
update my_table
set select_count = select_count+1
returning *)
select *
from upd;

SQL trigger, check if certain value was entered

INSERT INTO table_name (column1, column2, column3,...)
VALUES (value1, value2, value3,...)
Now what to check:
If inserted value1 = null, change it to 0
How to do it via trigger? I googled for examples and I have never ever done a trigger, so it is rather confusing.
So far got only this:
CREATE TRIGGER testTrigger
ON myTable
AFTER INSERT, UPDATE, DELETE
You can add default value . This is how it's done for a new column. For existing one you should add constraint. Check Update 2
ALTER TABLE table_name
ADD column1 int NOT NULL DEFAULT(0)
Add a column with a default value to an existing table in SQL Server
UPDATE:
To set default value, you should update NULL values at first.
UPDATE table_name
SET column1 = 0
WHERE column1 IS NULL
UPDATE 2:
Try adding constraint
ALTER TABLE table_name
ADD CONSTRAINT DF_column1 DEFAULT 0 FOR column1
You could write this in the trigger:
UPDATE T SET value1 =0
FROM table_name T
JOIN INSERTED I
ON T.<id>=I.<id>
WHERE I.value1 is null
INSERTED table which is accessible only within trigger will store the values that have inserted..
use ISNULL on the INSERTED value
SELECT ISNULL(INSERTED,0) FROM INSERTED

Can you access the auto increment value in MySQL within one statement?

I have a MySQL database which contains a table of users. The primary key of the table is 'userid', which is set to be an auto increment field.
What I'd like to do is when I insert a new user into the table is to use the same value that the auto increment is creating in the 'userid' field in a different field, 'default_assignment'.
e.g.
I'd like a statement like this:
INSERT INTO users ('username','default_assignment') VALUES ('barry', value_of_auto_increment_field())
so I create user 'Barry', the 'userid' is generated as being 16 (for example), but I also want the 'default_assignment' to have the same value of 16.
Is there any way to achieve this please?
Thanks!
Update:
Thanks for the replies. The default_assignment field isn't redundant. The default_assigment can reference any user within the users table. When creating a user I already have a form that allows a selection of another user as the default_assignment, however there are cases where it needs to be set to the same user, hence my question.
Update:
Ok, I've tried out the update triggers suggestion but still can't get this to work. Here's the trigger I've created:
CREATE TRIGGER default_assignment_self BEFORE INSERT ON `users`
FOR EACH ROW BEGIN
SET NEW.default_assignment = NEW.userid;
END;
When inserting a new user however the default_assignment is always set to 0.
If I manually set the userid then the default_assignment does get set to the userid.
Therefore the auto assignment generation process clearly happens after the trigger takes effect.
there's no need to create another table, and max() will have problems acording to the auto_increment value of the table, do this:
CREATE TRIGGER trigger_name BEFORE INSERT ON tbl FOR EACH ROW
BEGIN
DECLARE next_id;
SET next_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='tbl');
SET NEW.field = next_id;
END
I declare the next_id variable because usually it will be used in some other way(*), but you could do straight new.field=(select ...)
CREATE TRIGGER trigger_name BEFORE INSERT ON tbl FOR EACH ROW
BEGIN
SET NEW.field=(SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='tbl');
END
Also in cases of (SELECT string field) you can use CAST value;
CREATE TRIGGER trigger_name BEFORE INSERT ON tbl FOR EACH ROW
BEGIN
SET NEW.field=CAST((SELECT aStringField FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='tbl') AS UNSIGNED);
END
(*) To auto-name an image:
SET NEW.field = CONCAT('image_', next_id, '.gif');
(*) To create a hash:
SET NEW.field = CONCAT( MD5( next_id ) , MD5( FLOOR( RAND( ) *10000000 ) ) );
try this
INSERT INTO users (default_assignment) VALUES (LAST_INSERT_ID()+1);
seeing that last_insert_id() wouldn't work in this case, yes, the trigger would be the only way to accomplish that.
I do ask myself though: What do you need this functionality for? Why do you store the users id twice? Personally, I don't like storing redundant data like this and I'd probably solve this in application code by making that ominous default_assignment column NULL and using the user id in my application code if default_assignment was NULL.
Actually I just tried to do the same thing as was suggested above. But it seems Mysql doesent generate the inserted ID before the row actually gets commited. So NEW.userid will always return 0 in a Before insert trigger.
The above also wont work unless it is a BEFORE INSERT trigger, since you cant update values in a AFTER INSERT query.
From a Mysql Forum Post It seems the only way to handle this is using an additional table as a sequence. So that your trigger can pull in the values from an external source.
CREATE TABLE `lritstsequence` (
`idsequence` int(11) NOT NULL auto_increment,
PRIMARY KEY (`idsequence`)
) ENGINE=InnoDB;
CREATE TABLE `lritst` (
`id` int(10) unsigned NOT NULL auto_increment,
`bp_nr` decimal(10,0) default '0',
`descr` varchar(128) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `dir1` (`bp_nr`)
) ENGINE=InnoDB;
DELIMITER $$
DROP TRIGGER /*!50032 IF EXISTS */ `lritst_bi_set_bp_nr`$$
CREATE TRIGGER `lritst_bi_set_bp_nr` BEFORE INSERT ON `lritst`
FOR EACH ROW
BEGIN
DECLARE secuencia INT;
INSERT INTO lritstsequence (idsequence) VALUES (NULL);
SET secuencia = LAST_INSERT_ID();
SET NEW.id = secuencia;
SET NEW.bp_nr = secuencia;
END;$$
DELIMITER ;
INSERT INTO lritst (descr) VALUES ('test1');
INSERT INTO lritst (descr) VALUES ('test2');
INSERT INTO lritst (descr) VALUES ('test3');
SELECT * FROM lritst;
Result:
id bp_nr descr
------ ------ ------
1 1 test1
2 2 test2
3 3 test3
This was copied from forums.mysql.com/read.php?99,186171,186241#msg-186241 but Im not allowed to post links yet.
The only I found that would solve this problem without an extra table would be to calculate self the next number and put that in the fields required.
CREATE TABLE `Temp` (
`id` int(11) NOT NULL auto_increment,
`value` varchar(255) ,
PRIMARY KEY (`idsequence`)
) ENGINE=InnoDB;
CREATE TRIGGER temp_before_insert BEFORE INSERT ON `Temp`
FOR EACH ROW
BEGIN
DECLARE m INT;
SELECT IFNULL(MAX(id), 0) + 1 INTO m FROM Temp;
SET NEW.value = m;
-- NOT NEEDED but to be safe that no other record can be inserted in the meanwhile
SET NEW.id = m;
END;
basically, the solution is like Resegue said.
But if you want it in one statement, you will use one of the below ways:
1. One long statement:
INSERT INTO `t_name`(field) VALUES((SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='t_name'))
or for text with number:
INSERT INTO `t_name`(field) VALUES(CONCAT('Item No. ',CONVERT((SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='t_name') USING utf8)))
it looks more clearly in PHP:
$pre_name='Item No. ';
$auto_inc_id_qry = "(SELECT AUTO_INCREMENT FROM information_schema.TABLES
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$table')";
$new_name_qry = "CONCAT('$pre_name',CONVERT($auto_inc_id_qry USING utf8))";
mysql_query("INSERT INTO `$table`(title) VALUES($new_name_qry)");
2. Using function: (not tested yet)
CREATE FUNCTION next_auto_inc(table TINYTEXT) RETURNS INT
BEGIN
DECLARE next_id INT;
SELECT AUTO_INCREMENT FROM information_schema.TABLES
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=table INTO next_id;
RETURN next_id;
END
INSERT INTO users ('username','default_assignment')
VALUES ('barry', next_auto_inc('users'))
$ret = $mysqli->query("SELECT Auto_increment FROM information_schema.tables WHERE table_schema = DATABASE() ");l
while ($row = mysqli_fetch_array($ret)) {
$user_id=$row['Auto_increment'];
}
You can do this reliably using a simple subquery:
INSERT INTO users ('username','default_assignment')
SELECT 'barry', Auto_increment FROM information_schema.tables WHERE TABLE_NAME='users'
I tested the above trigger idea with 10 concurrent threads doing inserts and I got over 1000 cases of 2 or 3 duplicates after ~25k inserted.
DROP TABLE IF EXISTS test_table CASCADE;
CREATE TABLE `test_table` (
`id` INT NOT NULL AUTO_INCREMENT,
`update_me` VARCHAR(36),
`otherdata` VARCHAR(36) NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8
COMMENT 'test table for trigger testing';
delimiter $$
DROP TRIGGER IF EXISTS setnum_test_table;
$$
CREATE TRIGGER setnum_test_table
BEFORE INSERT ON test_table FOR EACH ROW
-- SET OLD.update_me = CONCAT(NEW.id, 'xyz');
BEGIN
DECLARE next_id INT;
SET next_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='test_table' LOCK IN SHARE MODE );
-- SET NEW.update_me = CONCAT(next_id, 'qrst');
SET NEW.update_me = next_id;
END
$$
delimiter ;
-- SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='test_table'
INSERT INTO test_table (otherdata) VALUES ('hi mom2');
SELECT count(*) FROM test_table;
SELECT * FROM test_table;
-- select count(*) from (
select * from (
SELECT count(*) as cnt ,update_me FROM test_table group by update_me) q1
where cnt > 1
order by cnt desc
I used 10 of:
while true ; do echo "INSERT INTO test_table (otherdata) VALUES ('hi mom2');" | mysql --user xyz testdb ; done &
And ran the last query to watch for duplicates
example output:
'3', '4217'
'3', '13491'
'2', '10037'
'2', '14658'
'2', '5080'
'2', '14201'
...
Note 'LOCK IN SHARE MODE' didn't change anything. With and without gave duplicates at about the same rate. It seems that MySQL AUTO_INCREMENT doesn't work like Postgres' next_val() and is NOT concurrency safe.
I know this post is from 2010, but I couldn't find a good solution.
I've solved this by creating a separate table that holds the counters. When I need to generate an unique identifier for a column I just call a Stored proc:
CREATE DEFINER=`root`#`localhost` PROCEDURE `IncrementCounter`(in id varchar(255))
BEGIN
declare x int;
-- begin;
start transaction;
-- Get the last counter (=teller) and mark record for update.
select Counter+1 from tabel.Counter where CounterId=id into x for update;
-- check if given counter exists and increment value, otherwise create it.
if x is null then
set x = 1;
insert into tabel.Counters(CounterId, Counter) values(id, x);
else
update tabel.Counters set Counter = x where CounterId = id;
end if;
-- select the new value and commit the transaction
select x;
commit;
END
The 'for update' statement locks the row in the counters table. This avoids duplicates being made by multiple threads.