I have a table, something like
FieldsOnForms(
FieldID int (FK_Fields)
FormID int (FK_Forms)
isDeleted bit
)
The pair (FieldID,FormID) should be unique, BUT only if the row is not deleted (isDeleted=0).
Is it possible to define such a constraint in SQLServer 2008? (without using triggers)
P.S. Setting (FieldID, FormID, isDeleted) to be unique adds the possibility to mark one row as deleted, but i would like to have the chance to set n rows (per FieldID,FormID) to isDeleted = 1, and to have only one with isDeleted = 0
You can have a unique index, using the SQL Server 2008 filtered indexes feature, or you can apply a UNIQUE index against a view (poor man's filtered index, works against earlier versions), but you cannot have a UNIQUE constraint such as you've described.
An example of the filtered index:
CREATE UNIQUE NONCLUSTERED INDEX IX_FieldsOnForms_NonDeletedUnique ON FieldsOnForms (FieldID,FormID) WHERE isDeleted=0
You could change your IsDeleted column to a DeletedDate and make it a DATETIME with the exact time when the row was logically deleted. Alternatively, you could add a DeletedDate column and then create an IsDeleted computed column against that so that you still have that column available if it's being used in code. You would then of course put a unique index over the DeletedDate (in addition to the FieldID and FormId) instead of the IsDeleted column. That would allow exactly one NULL column.
Albin posted a solution similar to this, but then deleted it. I'm not sure why, but if he re-posts it then his was here before mine. :)
No, unique means really unique. You'll either have to move your deleted records to another table or change IsDeleted to something that can be unique across all deleted records (say a time stamp). Either solution will require additional work either in your application, in a stored procedure, or in a trigger.
Related
Will Laravel 4.1 manage the creation of a unique index(where deleted_at = null) by itself when softDeletes?
Is the approach below correct? Or is it going to mix in already deleted records?
Schema::create('example', function(Blueprint $table) {
$table->increments('id');
$table->integer('example')->unsigned()->unique(); //?????
$table->softDeletes();
});
The database is mysql, but if there's certain solutions for other DB:s, you can provide them as well. However, it should be done within the laravel framework! A uniform solution that works with all dbs that laravel officially supports is appreciated.
Update
It seems like this approach does not work, since it just ignores the softDeletes() option.
So proposed solution
Schema::create('exampe', function(Blueprint $table) {
$table->increments('id');
$table->integer('example')->unsigned();
$table->softDeletes();
$table->unique('example', 'deleted_at');
});
Problem is that there can potentially be two exactly similar timestamps at the deleted_at column.
What I actually need is a where-condition.
$table->unique('example', array('where', 'deleted_at', '=', null));
or
$table->integer('example')->unsigned()->unique()->where('deleted_at', '=', null)
I would recommend making a two-column UNIQUE constraint over the column you want to be unique (example) and a dummy column for instance called is_live. This column is always '1' when the row is not soft-deleted. When you soft-delete a row, set is_live=NULL.
The reason is related to the way "uniqueness" is defined. Unique constraints allow any number of rows that have a NULL value. This is because NULL is not equal to NULL in SQL, so therefore two NULLs count as "not the same".
For multi-column unique keys, if any column is NULL, the whole set of columns in the unique key behaves as if it's not the same as any other row. Therefore you can have any number of rows that have one column of the unique key the same, as long as the other column in the unique key is NULL.
create table example (
id serial primary key,
example int unsigned not null,
is_live tinyint default 1,
unique key (example, is_live)
);
Demo: http://sqlfiddle.com/#!9/8d1e4d/1
PS: The direct answer to your question, about implementing a condition for indexes in MySQL, is that MySQL doesn't support this. Databases that do support partial indexes include:
PostgreSQL (https://www.postgresql.org/docs/current/static/indexes-partial.html)
Microsoft SQL Server (https://msdn.microsoft.com/en-us/library/cc280372.aspx)
Using $table->softDeletes() doesn't change how the Schema sets unique indexes. In your example only the example column will be unique.
If you want to have multiple columns in your unique index just run $table->unique(['column1', 'column2']).
To set unique index you either use it on a chain, like $table->integer('example')->unique() or have it on a new row, like I wrote above.
I have same problem. Using 2 columns as unique, after soft delete I can't create another row with same unique data.
What I want is using Blueprint table object achieve (not RAW query) that SQL:
CREATE UNIQUE INDEX test ON test_table USING btree (test_id, user_id) WHERE deleted_at IS NULL;
But Blueprint and Fluent object dont have any where method.
I have an MSSQL Server 2008 table that associates multiple photos to houses, as follows:
HouseID - with foreign key to House table
PhotoID - with foreign key to Photo table
It's all working great, with an unique constraint on PhotoID so that a photo cannot be associated with multiple houses.
I would like to specify a default photo for the house records. The table is updated as such
HouseID
PhotoID
isDefault
The issue is that there can only be a single isDefault = 1 for a set of photos for a house.
In MSSQL Server 2008, how do I ensure that there is only a single isDefault = 1 for a given House ID, and the other records are isDefault = 0? Is it better to use a trigger, or is there a better way? If a trigger, any suggestions on the syntax to ensure optimization?
Lastly, I need this to work on the Insert and on the Update events.
Update:
The following worked like a charm. Comments?
CREATE VIEW HousePhoto_isDefault AS
SELECT yourSchema.HousePhoto.houseID, yourSchema.HousePhoto.isDefault
FROM yourSchema.HousePhoto WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX idx_HousePhoto_isDefault
ON HousePhoto_isDefault (houseID)
GO
As you describe it, you would need to use triggers.
However, if you make a small change to the data structure, you can do it with regular constraints, I think. Instead of storing isDefault at the photo-level, store DefaultPhotoId at the house-level. That way, you can never have more than one default photo, no matter what you do.
If you want to ensure that there is a default, then set it up as NOT NULL.
In MSSQL Server 2008, how do I ensure that there is only a single isDefault = 1 for a given House ID, and the other records are isDefault = 0?
Why yes as Yves Samèr pointed out in this answer you can use use a filtered index
CREATE UNIQUE INDEX photo_isDefault
ON Photos(HouseID) WHERE isDefault = 1
DEMO
The error that is produced is
Cannot insert duplicate key row in object 'dbo.Photos' with unique
index 'photo_isDefault'.: INSERT INTO Photos (houseID, isDefault)
VALUES (1,1)
You could also opt to use a INDEXED VIEW or as you noted a trigger will do as well.
I actually think that triggers might be overkill. Just use CASE statements. Here's an example of the UPDATE statement (replacing the variables with whatever scripting language you're using):
UPDATE HousePhoto
SET isDefault =
(
CASE
WHEN
(PhotoID = #PhotoID)
THEN
1
ELSE
0
END
)
WHERE HouseID = #HouseId
Of course, you could always just use two queries.
UPDATE HousePhoto SET isDefault = 0 WHERE HouseID = #HouseID
UPDATE HousePhoto SET isDefault = 1 WHERE HouseID = #HouseID AND PhotoID = #PhotoID
There another approach: use a "reverse" FK:
[SQL Fiddle]
It is crucial to note the usage of the identifying relationship and the resulting composite PK in Photo: {HouseId, PhotoNo}. This serves two purposes:
Ensures that if a photo is the default of some house, it must belong to the same house.
Makes the FK in House composite. Since one of the FK fields is also in the PK (HouseId), it cannot be NULL, so it is crucial to have another field that can be NULL (DefaultPictureNo). If any of the FK fields is NULL, the FK is not enforced which allows us to break the chicken-and-egg problem when inserting new data1 in the presence of such circular FKs.
Compared to using isDefault flag and the associated filtered index, this approach makes the following tradeoffs:
PRO: Avoids the overhead of the extra field. This is potentially important if the total number of pictures is very high compared to the number of default pictures.
CON: Makes it impractical to use auto-increment for Photo PK.
CON: MS SQL Server will complain if you try to use declarative referential actions (such as ON DELETE CASCADE) in the presence of such circular FKs. You'll need to implement referential actions using triggers. This is an MS SQL Server quirk not generally applicable to other DBMSes.
TIE: Interferes less with clustering in Picture, at the price of interfering more with clustering in House.2
PRO: It's applicable to DBMSes that don't support filtered indexes (not really important here, but worth a mention).
1 I.e. enables us to insert a house without setting the default picture right away, which would be impossible since this is a new house and has no pictures yet.
2 Secondary indexes can be expensive in clustered tables, and the FK in House will need a supporting index.
I have created a table named as ABC. It has three columns which are as follows:-
The column number_pk (int) is the primary key of my table in which I have made the auto increment feature on for that column.
Now I have deleted two rows from that table say Number_pk= 5 and Number_pk =6.
The table which I get now is like this:-
Now if I again enter two new rows in this table with the same value I get the two new Number_pk starting from 7 and 8 i.e,
My question is that what is the logic behind this since I have deleted the two rows from the table. I know that a simple answer is because I have set the auto increment on for the primary key of my table. But I want to know is there any way that I can insert the two new entries starting from the last Number_pk without changing the design of my table?
And how the SQL Server manage this record since I have deleted the rows from the database??
The logic is guaranteeing that the generated numbers are unique. An ID field does not neccessarily have to have a meaning, but rather is most often used to identify a unique record, thus making it easier to perform operations on it.
If your database is designed properly, the deleted ID numbers would not have been possible to delete if they were referenced by any other tables in a foreign key relationship, thus preventing records from being orphaned in that way.
If you absolutely want to have entries sequences, you could consider issuing a RESEED, but as suggested, it would not really give you much advantages.
The identity record is "managed" because SQL Server will keep track of which numbers have been issued, regardless of whether they are still present or not.
Should you ever want to delete all records from a table, there are two ways to do so (provided no foreign key relatsons exist):
DELETE FROM Table
DELETE just removes the records, but the next INSERTED value will continue where the ID numbering left of.
TRUNCATE TABLE
TRUNCATE will actually RESEED the table, thus guaranteeing it starts again at the value you originally specified (most likely 1).
Although you should not do this until their is a specific requirement.
1.) Get the max id:
Declare #id int
Select #id = Max(Number_pk) From ABC
SET #id = #id + 1;
2.) And reset the Identity Column:
DBCC CHECKIDENT('ABC', RESEED, #id)
DBCC CHECKIDENT (Transact-SQL)
I have noticed that if I have a unique compound keys for two columns, column_a and column_b, then my sql ignores this constraint if one column is null.
E.g.
if column_a=1 and column_b = null I can insert column_a=1 and column_b=null as much as I like
if column_a=1 and column_b = 2 I can only insert this value once.
Is there a way to apply this constraint, other than maybe changing the columns to Not Null and setting default values?
http://dev.mysql.com/doc/refman/5.0/en/create-index.html
"A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. This constraint does not apply to NULL values except for the BDB storage engine. For other engines, a UNIQUE index allows multiple NULL values for columns that can contain NULL."
So, no, you can't get MySQL to treat NULL as a unique value. I guess you have a couple of choices: you could do what you suggested in your question and store a "special value" instead of null, or you could use the BDB engine for the table. I don't think this minor difference in behaviour warrants making an unusual choice of storage engine, though.
I worked around this issue by creating a virtual (stored) column on the same table that was COALESCE(column_b, 0). I then made by unique composite index based upon that column (and the second column) instead. Works very well.
Of course this was probably not possible back in 2010 :)
What's the best way to store a linked list in a MySQL database so that inserts are simple (i.e. you don't have to re-index a bunch of stuff every time) and such that the list can easily be pulled out in order?
Using Adrian's solution, but instead of incrementing by 1, increment by 10 or even 100. Then insertions can be calculated at half of the difference of what you're inserting between without having to update everything below the insertion. Pick a number large enough to handle your average number of insertions - if its too small then you'll have to fall back to updating all rows with a higher position during an insertion.
create a table with two self referencing columns PreviousID and NextID. If the item is the first thing in the list PreviousID will be null, if it is the last, NextID will be null. The SQL will look something like this:
create table tblDummy
{
PKColumn int not null,
PreviousID int null,
DataColumn1 varchar(50) not null,
DataColumn2 varchar(50) not null,
DataColumn3 varchar(50) not null,
DataColumn4 varchar(50) not null,
DataColumn5 varchar(50) not null,
DataColumn6 varchar(50) not null,
DataColumn7 varchar(50) not null,
NextID int null
}
Store an integer column in your table called 'position'. Record a 0 for the first item in your list, a 1 for the second item, etc. Index that column in your database, and when you want to pull your values out, sort by that column.
alter table linked_list add column position integer not null default 0;
alter table linked_list add index position_index (position);
select * from linked_list order by position;
To insert a value at index 3, modify the positions of rows 3 and above, and then insert:
update linked_list set position = position + 1 where position >= 3;
insert into linked_list (my_value, position) values ("new value", 3);
A linked list can be stored using recursive pointers in the table. This is very much the same hierarchies are stored in Sql and this is using the recursive association pattern.
You can learn more about it here (Wayback Machine link).
I hope this helps.
The simplest option would be creating a table with a row per list item, a column for the item position, and columns for other data in the item. Then you can use ORDER BY on the position column to retrieve in the desired order.
create table linked_list
( list_id integer not null
, position integer not null
, data varchar(100) not null
);
alter table linked_list add primary key ( list_id, position );
To manipulate the list just update the position and then insert/delete records as needed. So to insert an item into list 1 at index 3:
begin transaction;
update linked_list set position = position + 1 where position >= 3 and list_id = 1;
insert into linked_list (list_id, position, data)
values (1, 3, "some data");
commit;
Since operations on the list can require multiple commands (eg an insert will require an INSERT and an UPDATE), ensure you always perform the commands within a transaction.
A variation of this simple option is to have position incrementing by some factor for each item, say 100, so that when you perform an INSERT you don't always need to renumber the position of the following elements. However, this requires a little more effort to work out when to increment the following elements, so you lose simplicity but gain performance if you will have many inserts.
Depending on your requirements other options might appeal, such as:
If you want to perform lots of manipulations on the list and not many retrievals you may prefer to have an ID column pointing to the next item in the list, instead of using a position column. Then you need to iterative logic in the retrieval of the list in order to get the items in order. This can be relatively easily implemented in a stored proc.
If you have many lists, a quick way to serialise and deserialise your list to text/binary, and you only ever want to store and retrieve the entire list, then store the entire list as a single value in a single column. Probably not what you're asking for here though.
This is something I've been trying to figure out for a while myself. The best way I've found so far is to create a single table for the linked list using the following format (this is pseudo code):
LinkedList(
key1,
information,
key2
)
key1 is the starting point. Key2 is a foreign key linking to itself in the next column. So your columns will link something link something like this
col1
key1 = 0,
information= 'hello'
key2 = 1
Key1 is primary key of col1. key2 is a foreign key leading to the key1 of col2
col2
key1 = 1,
information= 'wassup'
key2 = null
key2 from col2 is set to null because it doesn't point to anything
When you first enter a column in for the table, you'll need to make sure key2 is set to null or you'll get an error. After you enter the second column, you can go back and set key2 of the first column to the primary key of the second column.
This makes the best method to enter many entries at a time, then go back and set the foreign keys accordingly (or build a GUI that just does that for you)
Here's some actual code I've prepared (all actual code worked on MSSQL. You may want to do some research for the version of SQL you are using!):
createtable.sql
create table linkedlist00 (
key1 int primary key not null identity(1,1),
info varchar(10),
key2 int
)
register_foreign_key.sql
alter table dbo.linkedlist00
add foreign key (key2) references dbo.linkedlist00(key1)
*I put them into two seperate files, because it has to be done in two steps. MSSQL won't let you do it in one step, because the table doesn't exist yet for the foreign key to reference.
Linked List is especially powerful in one-to-many relationships. So if you've ever wanted to make an array of foreign keys? Well this is one way to do it! You can make a primary table that points to the first column in the linked-list table, and then instead of the "information" field, you can use a foreign key to the desired information table.
Example:
Let's say you have a Bureaucracy that keeps forms.
Let's say they have a table called file cabinet
FileCabinet(
Cabinet ID (pk)
Files ID (fk)
)
each column contains a primary key for the cabinet and a foreign key for the files. These files could be tax forms, health insurance papers, field trip permissions slips etc
Files(
Files ID (pk)
File ID (fk)
Next File ID (fk)
)
this serves as a container for the Files
File(
File ID (pk)
Information on the file
)
this is the specific file
There may be better ways to do this and there are, depending on your specific needs. The example just illustrates possible usage.
There are a few approaches I can think of right off, each with differing levels of complexity and flexibility. I'm assuming your goal is to preserve an order in retrieval, rather than requiring storage as an actual linked list.
The simplest method would be to assign an ordinal value to each record in the table (e.g. 1, 2, 3, ...). Then, when you retrieve the records, specify an order-by on the ordinal column to get them back in order.
This approach also allows you to retrieve the records without regard to membership in a list, but allows for membership in only one list, and may require an additional "list id" column to indicate to which list the record belongs.
An slightly more elaborate, but also more flexible approach would be to store information about membership in a list or lists in a separate table. The table would need 3 columns: The list id, the ordinal value, and a foreign key pointer to the data record. Under this approach, the underlying data knows nothing about its membership in lists, and can easily be included in multiple lists.
This post is old but still going to give my .02$. Updating every record in a table or record set sounds crazy to solve ordering. the amount of indexing also crazy, but it sounds like most have accepted it.
Crazy solution i came up with to reduce updates and indexing is to create two tables (and in most use cases you don's sort all records in just one table anyway). Table A to hold the records of the list being sorted and table B to group and hold a record of the order as a string. the order string represents an array that can be used to order the selected records either on the web server or browser layer of a webpage application.
Create Table A{
Id int primary key identity(1,1),
Data varchar(10) not null
B_Id int
}
Create Table B{
Id int primary key Identity(1,1),
GroupName varchat(10) not null,
Order varchar(max) null
}
The format of the order sting should be id, position and some separator to split() your string by. in the case of jQuery UI the .sortable('serialize') function outputs an order string for you that is POST friendly that includes the id and position of each record in the list.
The real magic is the way you choose to reorder the selected list using the saved ordering string. this will depend on the application you are building. here is an example again from jQuery to reorder the list of items: http://ovisdevelopment.com/oramincite/?p=155
https://dba.stackexchange.com/questions/46238/linked-list-in-sql-and-trees suggests a trick of using floating-point position column for fast inserts and ordering.
It also mentions specialized SQL Server 2014 hierarchyid feature.
I think its much simpler adding a created column of Datetime type and a position column of int, so now you can have duplicate positions, at the select statement use the order by position, created desc option and your list will be fetched in order.
Increment the SERIAL 'index' by 100, but manually add intermediate values with an 'index' equal to Prev+Next / 2. If you ever saturate the 100 rows, reorder the index back to 100s.
This should maintain sequence with primary index.
A list can be stored by having a column contain the offset (list index position) -- an insert in the middle is then incrementing all above the new parent and then doing an insert.
You could implement it like a double ended queue (deque) to support fast push/pop/delete(if oridnal is known) and retrieval you would have two data structures. One with the actual data and another with the number of elements added over the history of the key. Tradeoff: This method would be slower for any insert into the middle of the linked list O(n).
create table queue (
primary_key,
queue_key
ordinal,
data
)
You would have an index on queue_key+ordinal
You would also have another table which stores the number of rows EVER added to the queue...
create table queue_addcount (
primary_key,
add_count
)
When pushing a new item to either end of the queue (left or right) you would always increment the add_count.
If you push to the back you could set the ordinal...
ordinal = add_count + 1
If you push to the front you could set the ordinal...
ordinal = -(add_count + 1)
update
add_count = add_count + 1
This way you can delete anywhere in the queue/list and it would still return in order and you could also continue to push new items maintaining the order.
You could optionally rewrite the ordinal to avoid overflow if a lot of deletes have occurred.
You could also have an index on the ordinal to support fast ordered retrieval of the list.
If you want to support inserts into the middle you would need to find the ordinal which it needs to be insert at then insert with that ordinal. Then increment every ordinal by one following that insertion point. Also, increment the add_count as usual. If the ordinal is negative you could decrement all of the earlier ordinals to do fewer updates. This would be O(n)