Until now I had a column account_name as the primary key for my database. I'd now like to use a hash of account_name as the primary key instead.
So as an interim measure, I added an account_hash column and gave it the UNIQUE constraint, so that both account_name and account_hash exist together.
I populated account_hash for all database entries, and am now actually using account_hash as the key for the database, and am no longer actively using account_name for anything.
But of course because account_name is the "official" primary key, and must be NOT NULL, for any new entries I have been populating both account_name and account_hash with the same hash.
It's all working fine like this, but now I'd like to clean up the database, to get rid of account_name entirely, and to make account_hash the primary key instead.
What is the best way of doing this? It is a working database that is in use constantly, so any change needs to be at minimum disruption to the users.
Here is the \d+ information relating to the relevant columns:
Column | Type | Modifiers | Storage | Stats target | Description
-------------------------------+------------------------+-----------------------------+----------+--------------+-------------
account_name | character varying(255) | not null | extended | |
account_hash | character varying(256) | | extended | |
Indexes:
"users_pkey" PRIMARY KEY, btree (account_name)
"users_account_hash_256_key" UNIQUE CONSTRAINT, btree (account_hash)
Has OIDs: no
Thanks for any help!
You can drop the current primary key with
ALTER TABLE tablename DROP CONSTRAINT users_pkey;
Make the account_hash required with
ALTER TABLE tablename ALTER account_hash SET NOT NULL;
After that you can add a new primary key with
ALTER TABLE tablename ADD PRIMARY KEY USING INDEX indexname;
You may have to drop the users_account_hash_256_key constraint first to not have a duplicate and depending on how the unique index was created, you may have to create the index again for this.
If the account_name column is not used anywhere, it can then be dropped with
ALTER TABLE tablename DROP COLUMN account_name;
Note I would advise against this action. Hashes have collisions, so if you use them as primary keys, there may be a time when you cannot insert a value into the database because of that. Also performance is worse with varchar indexes than with integers (or a UUID, if a very large keyspace is needed), so if there is no specific reason for using hashes, I wouldn't do this.
Related
I have a database in which i have two tables:
CREATE TABLE Transactions (
ID BIGINT IDENTITY(1,1) NOT NULL,
AccountID BIGINT NOT NULL,
Amount BIGINT NOT NULL,
CONSTRAINT PK_Transactions PRIMARY KEY CLUSTERED (ID ASC,AccountID ASC),
CONSTRAINT FK_Transaction_Account FOREIGN KEY (AccountID) REFERENCES Accounts(ID)
);
CREATE TABLE Accounts (
ID BIGINT IDENTITY(1,11) NOT NULL,
Balance BIGINT NOT NULL,
CONSTRAINT PK_Accounts PRIMARY KEY (ID)
);
Transactions are inserted to their table by a stored procedure i wrote, so that two rows are generated when Account 1 transfers 25 "coins" to Account 21:
ID | AccountID | Amount
-------------------------
1 | 1 | -25
-------------------------
1 | 21 | 25
In the above schema, i want the first row to reference the bottom row based on ID and the AccountID being unequal to the AccountID of the bottom row.
And vica versa.
What i want to do would look something like this:
CONSTRAINT FK_Transaction_Counterpart FOREIGN KEY (ID) REFERENCES Transactions(ID) WHERE thisRow.AccountID != referencedRow.AccountID
I haven't found this possibility in the documentation on the table constraints.
So both out of curiosity and intent to use this i ask, is this possible? And if yes, how?
Edit:
Answers reflect that this is not possible, and i should adjust my design or intentions.
I think i will settle with assigning the two transaction rows to each other in the functional code.
A traditional foreign key can't be conditional (i.e. no WHERE clause attached). In your case, I'd probably just make sure that the inserts are atomic (in the same transaction) so that there'd be no possibility of only one of them inserting.
If the data model you are trying to implement is:
One transaction (ID) has two and only two entries in table Transactions
For the two rows of a given Transaction ID, the AccountIDs cannot be the same
Then one perhaps overly-complex way you could enforce this business rule within the database table structures would be as follows:
Table Accounts, as you have defined
Table Transactions, as you have defined
New table TransactionPair with:
Columns (all are NOT NULL)
ID
LowAccountID
HighAccountID
Constraints
Primary key on ID (only one entry per Transaction ID)
Foreign key on (ID, LowAccountID) into Transactions
Foreign key on (ID, HighAccountID) into Transactions
Check constraint on the row such that LowAccountID < HighAccountID
Process:
Add pair of rows to Transactions table
Add single row to TransactionPair referencing the rows just added
If that row cannot be added, something failed, roll everything back
Seems neat and tidy, but quite possibly overly complex. Your mileage may vary.
I am new in PostgreSQL. I'm trying to figure out the syntax for creating the following table.
I'm having difficulties in creating the sequence and the auto increment fields.
Column | Type | Modifiers
--------------+-----------------------+-----------------------------------------------------
id_numuser | integer | not null default nextval('id_numuser_seq'::regclass)
username | character varying(70) |
completename | character varying(70) |
id_cat | integer |
email | character varying(70) |
password | character varying(30) |
active | boolean |
Indexes:
"users_pkey" PRIMARY KEY, btree (id_numuser)
"taskuser_uniq" UNIQUE, btree (username)
Foreign-key constraints:
"users_id_cat_fkey" FOREIGN KEY (id_cat) REFERENCES usercategories(id_numcat)
Use a serial column. Details here:
Auto increment SQL function
The complete Script:
CREATE TABLE users (
id_numuser serial PRIMARY KEY
,username character varying(70) UNIQUE
,completename character varying(70)
,id_cat integer REFERENCES usercategories(id_numcat)
,email character varying(70)
,password character varying(30)
,active boolean
);
You can use pgAdmin to get complete reverse-engineered SQL scripts for all objects.
Aside: I'd suggest to use just text instead of varchar(n).
If you're ever in doubt about how to define something, pg_dump will help.
pg_dump -t 'users' --schema-only
will print a dump that shows the command(s) to create your users table.
It won't use shorthand like SERIAL, so it'll create the sequence then assign the sequence ownership and set the column default. So sometimes there's a shorter and simpler way than how pg_dump does it. The way pg_dump does it will always work, though.
In this case it produces (trimmed):
CREATE TABLE users (
id_numuser integer NOT NULL,
username character varying(70),
completename character varying(70),
id_cat integer,
email character varying(70),
password character varying(30),
active boolean
);
CREATE SEQUENCE users_id_numuser_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE users_id_numuser_seq OWNED BY users.id_numuser;
ALTER TABLE ONLY users ALTER COLUMN id_numuser
SET DEFAULT nextval('users_id_numuser_seq'::regclass);
ALTER TABLE ONLY users
ADD CONSTRAINT users_pkey PRIMARY KEY (id_numuser);
ALTER TABLE ONLY users
ADD CONSTRAINT users_username_key UNIQUE (username);
ALTER TABLE ONLY users
ADD CONSTRAINT users_id_cat_fkey FOREIGN KEY (id_cat) REFERENCES usercategories(id_numcat);
So it's defining the sequence and all the constraints after creating the base table, not as part of it, and it's specifying a bunch of stuff that would usually be set by defaults.
The effect is the same and you can do things this way if you want.
Stuff like SERIAL PRIMARY KEY is basically just convenient shorthand. All that is covered well in the documentation for CREATE TABLE, so once you know what you want you can generally figure out how to define it pretty easily. Most of the time, anything you can write in ALTER TABLE ... ADD ... can be written the same in CREATE TABLE (...), eg:
ALTER TABLE ONLY users
ADD CONSTRAINT users_id_cat_fkey FOREIGN KEY (id_cat) REFERENCES usercategories(id_numcat);
can be done at create time with:
CREATE TABLE users (
....,
CONSTRAINT users_id_cat_fkey FOREIGN KEY (id_cat) REFERENCES usercategories(id_numcat)
);
Additionally, for any column-specific CONSTRAINT there's usually a way to tack it on to the end of the column definition. In this case, you omit the CONSTRAINT constraint_name (it's generated) and the FOREIGN KEY (id_cat) (because the column is implied, you don't need to specify it), and write:
CREATE TABLE users (
....
id_cat integer REFERENCES usercategories(id_numcat),
....
);
Once you know what to look for in the CREATE TABLE docs it's usually easy to find how to write what you want.
I created a set of tables in a database.A assigned relations with foreign key.
i am not getting cascading effect of data in mysql.
i created a table named ME and with MY_ID column name
I also created table named MY_Friends with MY_ID column name and foreing key with references to ME(MY_ID).
I am able to notice cascading effect in mysql
Me
My_ID | int(11) | NO | PRI | 0
My_Friends
My_ID | int(11) | YES | MUL | NULL | |
there are two columns description in two tables
FOREIGN KEY and CASCADE will have no effect if the tables are MyISAM. Check if the tables are defined using the InnoDB engine. Give us the output from SHOW CREATE TABLE Me and SHOW CREATE TABLE My_Friends so we can verify if that's the problem.
I use PostgreSQL but am looking for SQL answer as standard as possible.
I have the following table "docs" --
Column | Type | Modifiers
------------+------------------------+--------------------
id | character varying(32) | not null
version | integer | not null default 1
link_id | character varying(32) |
content | character varying(128) |
Indexes:
"docs_pkey" PRIMARY KEY, btree (id, version)
id and link_id are for documents that have linkage relationship between each other, so link_id self references id.
The problem comes with version. Now id is no longer the primary key (won't be unique either) and can't be referenced by by link_id as foreign key --
my_db=# ALTER TABLE docs ADD FOREIGN KEY(link_id) REFERENCES docs (id) ;
ERROR: there is no unique constraint matching given keys for referenced table "docs"
I tried to search for check constraint on something like "if exists" but didn't find anything.
Any tip will be much appreciated.
I usually do like this:
table document (id, common, columns, current_revision)
table revision (id, doc_id, content, version)
which means that document has a one-to-many relation with it's revisions, AND a one-to-one to the current revision.
That way, you can always select a complete document for the current revision with a simple join, and you will only have one unique row in your documents table which you can link parent/child relations in, but still have versioning.
Sticking as close to your model as possible, you can split your table into two, one which has 1 row per 'doc' and one with 1 row per 'version':
You have the following table "versions" --
Column | Type | Modifiers
------------+------------------------+--------------------
id | character varying(32) | not null
version | integer | not null default 1
content | character varying(128) |
Indexes:
"versions_pkey" PRIMARY KEY, btree (id, version)
And the following table "docs" --
Column | Type | Modifiers
------------+------------------------+--------------------
id | character varying(32) | not null
link_id | character varying(32) |
Indexes:
"docs_pkey" PRIMARY KEY, btree (id)
now
my_db=# ALTER TABLE docs ADD FOREIGN KEY(link_id) REFERENCES docs (id) ;
is allowed, and you also want:
my_db=# ALTER TABLE versions ADD FOREIGN KEY(id) REFERENCES docs;
of course there is nothing stoping you getting a 'combined' view similar to your original table:
CREATE VIEW v_docs AS
SELECT id, version, link_id, content from docs join versions using(id);
Depending on if it's what you want, you can simply create a FOREIGN KEY that includes the version field. That's the only way to point to a unique row...
If that doesn't work, you can write a TRIGGER (for all UPDATEs and INSERTs on the table) that makes the check. Note that you will also need a trigger on the docs table, that restricts modifications on that table that would break the key (such as a DELETE or UPDATE on the key value itself).
You cannot do this with a CHECK constraint, because a CHECK constraint cannot access data in another table.
I've run into a situation in our database where I need to shift the timestamps of a number of records by a day, however I have a unique constraint that requires the an id field and a timestamp field to be unique.
Here is the table description.
Table "public.eedata"
Column | Type | Modifiers
-------------+--------------------------------+------------------------------------------------------------
eedata_id | bigint | not null default nextval('eedata_eedata_id_seq'::regclass)
user_id | integer |
eeupload_id | bigint |
eetimestamp | timestamp(0) without time zone |
Indexes:
"pk_eedata" PRIMARY KEY, btree (eedata_id)
"eedata_user_id_key" UNIQUE, btree (user_id, eetimestamp)
"fki_eeuploadid" btree (eeupload_id)
Foreign-key constraints:
"fk_eeupload_id" FOREIGN KEY (eeupload_id) REFERENCES eeupload(eeupload_id) ON UPDATE CASCADE ON DELETE CASCADE
"fk_user_id" FOREIGN KEY (user_id) REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE
The problem in this case is caused by the eedata_user_id_key constraint. I can successfully subtract a day using,
update eedata set eetimestamp = eetimestamp - interval '1 day' where eeupload_id = xxx;
because the order that it applies the update in prevents any collisions, however when I try
update eedata set eetimestamp = eetimestamp + interval '1 day' where eeupload_id = xxx;
I get ERROR: duplicate key violates unique constraint "eedata_user_id_key"
What I need to be able to do is either specify the order in which the update is applied (effectively an order by for an update statement) or the ability to suspend the constraint for a single update statement.
I'm using Postgres 8.1.11 if that matters.
Could you just remove the constraint for the duration of your update, and then add it back in when you're done?
I recently had a similar issue - I have test data that ends September 2008, and I needed it to be more recent. I moved it up six months, but had some duplicates. What I needed to do is detect the duplicates before they were created, and do an UPDATE of the existing rows instead of an INSERT of new rows.