I am currently trying to manage revisions of a data set in a postgreSql database. The table I would like to use has the following structure:
CREATE TABLE dataset (
id BIGSERIAL PRIMARY KEY,
revision INTEGER NOT NULL,
object_id BIGINT NOT NULL
);
The id field is a unique auto-increment identifier. The object_id should be the identifier for a object, while revision keeps track of the revisions:
id | object_id | revision
-------------------------
1 | 1 | 1
2 | 2 | 1
3 | 1 | 2
4 | 1 | 3
5 | 3 | 1
6 | 4 | 1
What I now need is a function, that:
Sets a auto-increment object_id and sets revision to 1, if no object_id is provided.
Sets a auto-increment revision for this object_id, if an object_id is provided.
I already found this answer, but this does not really solve the problem of creating consecutive revisions for a object_id and it does not solve the problem of auto creating consecutive object_ids.
EDIT:
I would do something like the following, but this doesn't feel very comfortable:
CREATE OR REPLACE FUNCTION update_revision() RETURNS TRIGGER LANGUAGE plpgsql AS
$$
BEGIN
IF tg_op='INSERT' THEN
IF NEW.object_id != NULL THEN
NEW.object_id = SELECT nextval(object_id_seq_id);
NEW.revision = 1;
ELSE
NEW.revision = SELECT MAX(revision)+1 FROM dataset WHERE spot_id = NEW.spot_id;
END IF;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER update_revision BEFORE INSERT OR UPDATE ON dataset
FOR EACH ROW EXECUTE PROCEDURE update_revision();
Make (object_id, revision) unique. BTW why aren't they the primary key?
create table dataset (
id bigserial primary key,
object_id bigint not null,
revision integer not null,
unique (object_id, revision)
);
create or replace function include_revision (_object_id integer)
returns dataset as $$
with object_id as (
select coalesce(max(object_id), 0) + 1 as object_id
from dataset
), revision as (
select coalesce(max(revision), 0) + 1 as revision
from dataset
where object_id = _object_id
)
insert into dataset (object_id, revision)
select
coalesce(_object_id, (select object_id from object_id)),
(select revision from revision)
returning *
;
$$ language sql;
object_id is set to coalesce(_object_id, (select object_id from object_id)), that is, only if _object_id is null it will use the calculated max(object_id)
Testing:
select include_revision(null);
include_revision
------------------
(1,1,1)
select include_revision(1);
include_revision
------------------
(2,1,2)
select include_revision(null);
include_revision
------------------
(3,2,1)
Related
I am new to MYSQL and would like to create a table where a constant Letter depicting the department is added to an auto increment number. This way I would be able to identify the category of the worker upon viewing the ID.
Ex. Dept A and employee 135. The ID I am imaging should read A135 or something similar. I have created the table, the auto increment works fine, the constant letter has been declared and is featuring. However I would like to concatenate them in order to use the A135 as a primary key.
Any Help Please?
This quite tricky, and you would be probably better off doing manual concatenation in a select query.
But since you asked for it...
In normal usage you would have used a computed column for this, but they do not support using autoincremented columns in their declaration. So you would need to use triggers:
on insert, query information_schema.tables to retrieve the autoincremented id that is about to be assigned and use it to generate the custom id
on update, reset the custom id
Consider the following table structure:
create table workers (
id int auto_increment primary key,
name varchar(50) not null,
dept varchar(1) not null,
custom_id varchar(12)
);
Here is the trigger for insert:
delimiter //
create trigger trg_workers_insert before insert ON workers
for each row
begin
if new.custom_id is null then
select auto_increment into #nextid
from information_schema.tables
where table_name = 'workers' and table_schema = database();
set new.custom_id = CONCAT(new.dept, lpad(#nextid, 11, 0));
end if;
end
//
delimiter ;
And the trigger for update:
delimiter //
create trigger trg_workers_update before update ON workers
for each row
begin
if new.dept is not null then
set new.custom_id = CONCAT(new.dept, lpad(old.id, 11, 0));
end if;
end
//
delimiter ;
Let's run a couple of inserts for testing:
insert into workers (dept, name) values ('A', 'John');
insert into workers (dept, name) values ('B', 'Jim');
select * from workers;
| id | name | dept | custom_id |
| --- | ---- | ---- | ------------ |
| 1 | John | A | A00000000001 |
| 2 | Jim | B | B00000000002 |
And let's test the update trigger
update workers set dept = 'C' where name = 'Jim';
select * from workers;
| id | name | dept | custom_id |
| --- | ---- | ---- | ------------ |
| 1 | John | A | A00000000001 |
| 2 | Jim | C | C00000000002 |
Demo on DB Fiddle
Sorry, my answer does not fit in a comment.
I agree with #GMB.
This is a tricky situation and in some cases (selects mainly) will lead in a performance risk due you'll have to split PK in where statements, which is not recommended.
Having a column for department and another for auto_increment is more logical. And the only gap you have is to know the number of employees per department you'll have to make a count grouping by dept. Instead of a max() splitting your concatenated PK, which is is at high performance cost.
Let atomic and logic data remain in separate columns. I would suggest to create a third column with the concatenated value.
If, for some company reason, you need B1 and A1 values for employees of different departments, I'd suggest to have 3 columns
Col1 - letter(not null)
Col2 - ID(Not auto-increment, but calculated as #GMB's solution) (Not NULL)
Col3 - Concatenation of Col1 and Col2 (not null)
PK( Col1, col2)
I have datewise tables created with date as part of the table name.
ex. data_02272015, data_02282015 (name format is data_<mmddyyyy>). All the tables have the same schema.
Now, The tables have a datetime column TransactionDate. I need to get all the records by querying against this column. One table stores 24 hr data of the corresponding day. So, if I query with date 2015-02-28 xx:xx:xx, I can just query the table data_02282015. But, if I want to query with date 2015-02-27 xx:xx:xx, I have to consider both the tables data_02282015 and data_02272015.
I can get the union like this:
SELECT * FROM data_02272015
UNION
SELECT * FROM data_02282015;
But the problem is I also need to check whether either of the table exists. So if data_02282015 does not exists, the query fails. Is there a way with which query will return the records from the table(s) that exists.
So,
If both table exists, then it will return union of records of both the tables.
If either table does not exists, then it will return records for existing table only.
If both tables does not exists, empty resultset.
I tried things like:
SELECT IF( EXISTS(SELECT 1 FROM data_02282015), (SELECT * FROM data_02282015), 0)
...
But it didn't worked.
If I understand the question correctly, you need a FULL JOIN :
CREATE TABLE two
( val INTEGER NOT NULL PRIMARY KEY
, txt varchar
);
INSERT INTO two(val,txt) VALUES
(0,'zero'),(2,'two'),(4,'four'),(6,'six'),(8,'eight'),(10,'ten');
CREATE TABLE three
( val INTEGER NOT NULL PRIMARY KEY
, txt varchar
);
INSERT INTO three(val,txt) VALUES
(0,'zero'),(3,'three'),(6,'six'),(9,'nine');
SELECT *
FROM two t2
FULL JOIN three t3 ON t2.val = t3.val
ORDER BY COALESCE(t2.val , t3.val)
;
Result:
CREATE TABLE
INSERT 0 6
CREATE TABLE
INSERT 0 4
val | txt | val | txt
-----+-------+-----+-------
0 | zero | 0 | zero
2 | two | |
| | 3 | three
4 | four | |
6 | six | 6 | six
8 | eight | |
| | 9 | nine
10 | ten | |
(8 rows)
Try this script. As a complete solution, you could use the following embedded in a stored procedure, replacing id column with all your needed columns.
-- temp table that will collect results
declare #tempResults table (id int)
-- Your min and max dates to iterate between
declare #dateParamStart datetime
set #dateParamStart = '2015-02-25'
declare #dateParamEnd datetime
set #dateParamEnd = '2015-02-28'
-- table name using different dates
declare #currTblName nchar(13)
while #dateParamStart < #dateParamEnd
begin
-- set table name with current date
SELECT #currTblName = 'data_' + REPLACE(CONVERT(VARCHAR(10), #dateParamStart, 101), '/', '')
SELECT #currTblName -- show current table
-- if table exists, make query to insert into temp table
if OBJECT_ID (#currTblName, N'U') IS NOT NULL
begin
print ('table ' + #currTblName + 'exists')
execute ('insert into #tempResults select id from ' + #currTblName)
end
-- set next date
set #dateParamStart = dateadd(day, 1, #dateParamStart)
end
-- get your results.
-- Use distinct to act as a union if rows can be the same between tables.
select distinct * from #tempResults
how to set an unique primary key to all the table of database?
for example i don't wanted to repeat any primary key of different table.
table A:
----------
id | name
----------
1 | aaa
3 | bbb
5 | ccc
table B:
-------------
id | surname
-------------
7 | ddd
2 | eee
9 | fff
table C:
-------------
id | nickname
-------------
4 | ggg
6 | hhh
8 | iii
all id are primary key and auto_increment.
All the data is entered dynamically.I am using MYSQL in PHPMYADMIN.
You may add a new table to your schema called ID_Table that will have only one numeric column called current_id with default value of 0 ,when adding a new row to any other table of the schema you have to call a select on the ID_Table returning ID_Table.current_id + 1 as new id value.
Then updating ID_Table must be done
Update ID_Tableset ID_Table.current_id = ID_Table.current_id + 1
the GetNewId function could be implemented by
locking the ID_Table
Updating ID_Table
returning NewID
something like this (I have used Oracle syntax)
create table ID_Table(
current_id number
);
Insert into ID_Table values(0);
CREATE OR REPLACE Function GetNewId RETURN number is
new_id ID_Table.current_id%type;
row_count number;
begin
select nvl(ID_Table.current_id, 0) + 1
INTO new_id
FROM ID_Table
for update;
update ID_Table set ID_Table.Current_Id = new_id;
commit;
RETURN new_id;
end GetNewId;
You can get maximum ID from all three tables then add it in your insert query. But you have to remove the auto_increment attribute.
INSERT INTO TableA
SELECT MAX(ID)+1, 'jjj'
FROM
(SELECT MAX(ID) AS ID FROM TableA
UNION
SELECT MAX(ID) AS ID FROM TableB
UNION
SELECT MAX(ID) AS ID FROM TableC
) A;
See this SQLFiddle
Use the same sequence as the id generator for each inserted row, regardless of the table. Assuming you're using a DB that allows a sequence to be named as the id generator for the field.
This looks like it will do what you want in MySQL: http://devzone.zend.com/1786/mysql-sequence-generator/
Look at using Sequence. I'm not sure what DB you are using. Postgresql and Oracle have sequence that you can share between tables.
I have this mysql table built like this:
CREATE TABLE `posts` (
`post_id` INT(10) NOT NULL AUTO_INCREMENT,
`post_user_id` INT(10) NOT NULL DEFAULT '0',
`gen_id` INT(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`post_user_id`, `post_id`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
When I do:
insert into posts (post_user_id) values (1);
insert into posts (post_user_id) values (1);
insert into posts (post_user_id) values (2);
insert into posts (post_user_id) values (1);
select * from posts;
I get:
post_id | post_user_id | gen_id
1 1 0
2 1 0
1 2 0
3 1 0
A unique post_id is generated for each unique user.
I need the gen_id column to be 1 2 3 4 5 6 etc. How can I increment this column when I do an insert. I tried the one below, but it won't work. What's the right way to do this?
insert into posts (post_user_id,gen_id) values (1,select max(gen_id)+1 from posts);
//Select the highest gen_id and add 1 to it.
Try this:
INSERT INTO posts (post_user_id,gen_id)
SELECT 1, MAX(gen_id)+1 FROM posts;
Use a TRIGGER on your table. This sample code can get you started:
DELIMITER //
CREATE TRIGGER ai_trigger_name AFTER INSERT ON posts
FOR EACH ROW
BEGIN
UPDATE posts
SET gen_id = (SELECT MAX(gen_id) FROM posts) + 1
WHERE post_id = LAST_INSERT_ID()
LIMIT 1;
END;//
DELIMITER ;
For my case the first number to increment was null. I resolve with
IFNULL(MAX(number), 0) + 1
or better the query became
SELECT IFNULL(MAX(number), 0) + 1 FROM mytable;
Here is the table "Autos" and the data that it contains to begin with:
AutoID | Year | Make | Model | Color |Seq
1 | 2012 | Jeep |Liberty| Black | 1
2 | 2013 | BMW | 330XI | Blue | 2
The AutoID column is an auto incrementing column so it is not necessary to include it in the insert statement.
The rest of the columns are varchars except for the Seq column which is an integer column/field.
If you want to make it so that when you insert the next row into the table and the Seq column auto increments to the # 3 you need to write your query as follows:
INSERT INTO Autos
(
Seq,
Year,
Make,
Model,
Color,
)
Values
(
(SELECT MAX(Seq) FROM Autos) + 1, --this increments the Seq column
2013,'Mercedes','S550','Black');
The reason that I put the Seq column first is to ensure that it will work correctly... it does not matter where you put it, but better safe than sorry.
The Seq column should now have a value of 3 along with the added values for the rest of that row in the database.
The way that I intended that to be displayed did not happen...so I will start from the beginning: First I created a table.
create table Cars (
AutoID int identity (1,1) Primary Key,
Year int,
Make varchar (25),
Model varchar (25),
TrimLevel varchar (30),
Color varchar (30),
CreatedDate date,
Seq int
)
Secondly I inserted some dummy values
insert into Cars values (
2013,'Ford' ,'Explorer','XLT','Brown',GETDATE(),1),
(2011,'Hyundai' ,'Sante Fe','SE','White',GETDATE(),2),
(2009,'Jeep' ,'Liberty','Jet','Blue',GETDATE(),3),
(2005,'BMW' ,'325','','Green',GETDATE(),4),
(2008,'Chevy' ,'HHR','SS','Red',GETDATE(),5);
When the insertion is complete you should have 5 rows of data.
Since the Seq column is not an auto increment column and you want to ensure that the next Seq's row of data is automatically incremented to the # 6 and its subsequent rows are incremented as well you would need to write the following code:
INSERT INTO Cars
(
Seq,
Year,
color,
Make,
Model,
TrimLevel,
CreatedDate
)
Values
(
(SELECT MAX(Seq) FROM Cars) + 1,
2013,'Black','Mercedes','A550','AMG',GETDATE());
I have run this insert statement many times using different data just to make sure that it works correctly....hopefully this helps!
MySQL. Two columns, same table.
Column 1 has product_id
Column 2 has category_ids (sometimes 2 categories, so will look like 23,43)
How do i write a query to return a list of product_id, category_ids, with a seperate row if there is more than 1 category_id associated with a product_id.
i.e
TABLE:
product_id | category_ids
100 | 200,300
101 | 201
QUERY RESULT: Not trying to modify the table
100 | 200
100 | 300
101 | 201
EDIT: (note) I don't actually wish to manipulate the table at all. Just doing a query in PHP, so i can use the data as needed.
Your database table implementation seems bad designed, however in your case what you need would be a reverse function of GROUP_CONCAT, but unfortunately it doesn't exist in MySQL.
You have two viable solutions :
Change the way you store the data (allow duplicate on the product_id field and put multiple records with the same product_id for different category_id)
Manipulate the query result from within your application (you mentioned PHP in your question), in this case you have to split the category_ids column values and assemble a result set by your own
There is also a third solution that i have found that is like a trick (using a temporary table and a stored procedure), first of all you have to declare this stored procedure :
DELIMITER $$
CREATE PROCEDURE csv_Explode( sSepar VARCHAR(255), saVal TEXT )
body:
BEGIN
DROP TEMPORARY TABLE IF EXISTS csv_Explode;
CREATE TEMPORARY TABLE lib_Explode(
`pos` int unsigned NOT NULL auto_increment,
`val` VARCHAR(255) NOT NULL,
PRIMARY KEY (`pos`)
) ENGINE=Memory COMMENT='Explode() results.';
IF sSepar IS NULL OR saVal IS NULL THEN LEAVE body; END IF;
SET #saTail = saVal;
SET #iSeparLen = LENGTH( sSepar );
create_layers:
WHILE #saTail != '' DO
# Get the next value
SET #sHead = SUBSTRING_INDEX(#saTail, sSepar, 1);
SET #saTail = SUBSTRING( #saTail, LENGTH(#sHead) + 1 + #iSeparLen );
INSERT INTO lib_Explode SET val = #sHead;
END WHILE;
END; $$
DELIMITER ;
Then you have to call the procedure passing the array in the column you want to explode :
CALL csv_explode(',', (SELECT category_ids FROM products WHERE product_id = 100));
After this you can show results in the temporary table in this way :
SELECT * FROM csv_explode;
And the result set will be :
+-----+-----+
| pos | val |
+-----+-----+
| 1 | 200 |
| 2 | 300 |
+-----+-----+
It could be a starting point for you ...