Imagine I have a table in an Oracle database with 3 columns, ID (PK), NAME and ACTIVE. If I wanted, for example, to have NAME and ACTIVE be unique together, I could easily do that. However, what I want is for NAME to be unique only when ACTIVE in that row is set to true (1). When ACTIVE is false (0), I want to be able to have an arbitrary number of rows with the same NAME (but different ID, of course). Is this possible to do?
Yes, in Oracle you can create a unique index with a CASE statement, in your case something like;
CREATE UNIQUE INDEX ix_uq ON test(
name,
CASE WHEN active = 0 THEN id ELSE 0 END
)
Since id is unique, we can have multiple rows with the same name as long as active=0 (since the uniqueness will be based on the (name,id)), while an active entry will check uniqueness on (name,0) which allows only a single active row per name.
An SQLfiddle to test with. Try to add a duplicate active entry, and it will not insert.
Here another possibility.
CREATE UNIQUE INDEX ix_uq ON test(decode(active,1,name,null))
With that, your index is the smallest possible because you don't need to index inactive values. It couls be usefull if your table began to be very big with juste small active values
Related
I am trying to create a new table in SQL Developer that has a four columns. In one column there is a numerical value called ORG_ID, this ORG_ID can be the same across multiple entries in the table. Another column is called DEFAULT_FLAG, this column only contains a Y or N character denoting if it is the default entry for the table for that ORG_ID.
I am trying to create a CHECK in the DEFAULT_FLAG column that makes sure there is only one entry with a Y for all entries with the same ORG_ID. Here is an example of what it would look like:
xxxx|xxxx|ORG_ID|DEFAULT_FLAG
xxxx|xxxx|123456| Y
xxxx|xxxx|123456| N
xxxx|xxxx|987654| Y
xxxx|xxxx|567495| Y
In the above table, the second entry for ORG_ID 123456 would need to be rejected if Y was inserted as the DEFAULT_FLAG.
I'm a little new to SQL, so I've done my research of needing to use a constraint and check on the column. I tried writing my own but it did not work, the code is below.
default_flag varchar(1)
constraint one_default Check(ORG_ID AND DEFAULT_FLAG != "Y"),
This is too long for a comment.
You are trying to use a check constraint for something it is not designed for. You have an org_id. You should have an organizations table that uses this id as its primary key.
Then, then flag you want to store should be in the organizations table. Voila! The flag is only stored once. You don't need to worry about keeping it in synch between different rows.
Create a unique index for all ORG_ID entries with a 'Y', so each ORG_ID can only have one row with a 'Y':
create unique index idx on mytable(case when default_flag = 'Y' then org_id end)
I think a technically-better solution than the one from Thorsten Kettner, but using the same idea, is
CREATE UNIQUE INDEX ON mytable(org_id)
WHERE default_flag = 'Y';
But let me also suggest a table organization_defaults with two columns, one the ID for an organization and the other the ID for mytable is a better approach, as suggested in comments to the OP.
I have a item tax table that records the different tax rates for different counties in our state. Each row has an ID number (1-130). Our front end software always orders the tax options by this number when we want it alphabetical. Most of our rows were added that way but I want to be able to insert rows.
Thus I need to add 1 to every entry after a certain number (e.g. 37-130 need to all increase by one). Unfortunately, this is the primary key. Is it possible to increase this value on all of them easily? Or in a loop? I'll have to do this repeatedly as we're moving about a dozen entries if possible.
UPDATE ItemTax
SET ID = ID + 1
WHERE ID = Last ID number
Treating your question as academic, and not endorsing this as an actual solution, you can do this:
UPDATE ItemTax
SET ID = ID + 1
WHERE ID > 37
Depending upon how you use this id, it might be better to leave original ID column unchanged. E.g.
alter table TaxItem add NewID int null
GO
update TaxItem set NewID =
case
when ID between 37 and 130 then ID + 1
else ID
end
Now you don't have to update foreign key relationships, etc.
You see, as ID usually represents a surrogate key, and should never have its value changed in a good design. So your desire to change it value leads to suspicion that you do not understand your design as well as you should. -- We all start from ignorance, I have bad some very poor decisions in the past.
If this is the only change there will ever be for NewID, you don't even need a physical column, a computed column would serve well. But if this is the first mod of many a physical column is likely a better choice.
You also mention inserting rows. Build in some room to insert rows and change values as needed because you have room to rearrange rows by tweaking values without having to renumber entire blocks of rows just to insert a single row, e.g.
update TaxItem set NewID = ID * 100
I have a table with a SERIAL ID as primary key.
As you know the serial id increments itself automatically, and I need this feature in my table.
ID | info
---------
1 | xxx
2 | xxx
3 | xxx
For ordering matters, I want to insert a row between 1 and 2. Thus give to the new row an ID equal to 2, and want the other ID's to automatically increment to 3,4. If I execute such a query I get a duplicate key error.
Is there a way to make it possible, maybe changing the SERIAL ID to some other type?
What you are describing is not what most people would consider an ID, which should be a permanent and arbitrary identifier, for which an auto-increment column is just a convenient way of creating unique values. You couldn't use a value that kept changing as a foreign key, for example, so might well want both columns.
However, the task you've described is easily achieved with just an ordinary Integer column, let's call it "position", since that seems a more logical label for this behaviour.
The algorithm is simple:
Make a space for the new value by shifting all existing elements up one place.
Insert your new element.
In SQL, that would look something like this, to insert at position 42:
UPDATE items SET position=position + 1 WHERE position >= 42;
INSERT INTO items ( position, name ) VALUES ( 42, 'Answer' );
You could wrap this up in an SQL function on the server, and wrap it in a transaction to prevent concurrent inserts messing each other up.
Note that by default, a PRIMARY KEY or UNIQUE constraint on the position column may be invalidated during the update, as changes to each row are validated separately. To get around this, you can use a "deferrable constraint"; even in "immediate" mode, this will only be checked at the end of the statement, so the update will not violate it.
CONSTRAINT uq_position UNIQUE (position) DEFERRABLE INITIALLY IMMEDIATE
Note also that a Serial column doesn't have to be unique, so you could still have the default value be an auto-increment. However, it won't notice you inserting extra values, so you need to reset the sequence after a manual insert:
SELECT setval(
pg_get_serial_sequence('items', 'position'),
( SELECT max(position) FROM items )
);
Here is a live demo putting it all together. (SQLFiddle seems to have a bug which isn't dropping/resetting the sequence, making the id values look rather odd.)
I have a table which contains different versions of objects, e.g. Object A version 1, A version 2, B version 24... etc. One column stores the foreign key to the object, another stores the version number. It it obvious that these in combination should be unique and that is easy to implement with a unique index on both.
However, I want to be able to keep track of which version is the current one with an IsCurrent Yes/No column. The current version is not necessarily the one with the highest number. The problem here is that there is no way to define an index which is unique for yes values but allows many nos.
I find a lot of results when searching for this problem but none of them appear to work in Access. I have tried a "hack" in which I create a calculated column to use in a unique index which is -1 if current is true and the PK otherwise, but Access does not allow you to index calculated columns.
Is there any way to do this?
There is a trick, but you must allow only "yes/null" values for the isCurrent column - "yes" means "this row is current", and "null" otherwise.
This can be done using a validation check [isCurrent]="yes" Or [isCurrent] Is Null
Then create a composite + unique + ignore nulls index on id+isCurrent fields, and allow nulls.
Just click on the "index" button and define it in this way:
This prevents from inserting two rows with the same id + "yes" in the 'isCurrent' column, but allows many rows with the same id + null in the 'isCurrent' column.
I want to add something to a table (trigger?) so that, for exactly, exactly 1 row per ID has a specific value for a specific column. So that if a statement was run that makes this not the case, an exception would be thrown and the insert would be rolled back.
Let's take this schema.
ID Current Value
1 Y 0
1 N 0
1 N 2
2 Y 2
And the constraint I want is that for each ID, exactly one row has a current of 'Y'.
Therefore, these statements would not be executed and return an appropriate error:
insert into table values (1,'Y',1);
insert into table values (3,'N',2);
update table set current = 'N' where ID = 1;
I have two questions:
Is it a good idea to code this kind of constraint logic into your table, or is that best saved for the applications that manipulate the data? Why?
How can it be done? What kind of tool does oracle provide to create a constraint like this?
It's best if you can specify it in a declarative fashion (rather than procedurally, e.g. using triggers). Especially because triggers, without some kind of locking algorithm, will NOT work anyway due to concurrent sessions trying to insert/update the table at the same time.
In this instance, the simplest solution is a unique, function-based index, e.g.:
CREATE UNIQUE INDEX only_one_current ON thetable
(CASE WHEN Current = 'Y' THEN ID END);
The expression is NULL if Current = 'N', and all-NULL rows in an index are not stored, which means that the uniqueness constraint will only apply to rows where Current = 'Y'.
I think what you are looking for is just a unique constraint.
You can add it using below statement so that only unique combination of ID , Current can exist in table.
ALTER TABLE table_name add CONSTRAINT constraint_name UNIQUE (ID,Current);