0

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
  • 1
    possible duplicate of [Add a constraint using T-SQL based on a condition](http://stackoverflow.com/questions/5116700/add-a-constraint-using-t-sql-based-on-a-condition) – Conrad Frix Apr 15 '13 at 20:51
  • This might have been an even better dupe [Constraint for only one record marked as default](http://stackoverflow.com/a/640722/119477) In particular [this answer](http://stackoverflow.com/a/15776793/119477) – Conrad Frix Apr 15 '13 at 20:53
  • Use a calculated column: http://dbaspot.com/sqlserver-programming/404703-how-construct-check-constraint-allow-only-one-true-flag-per-vendor.html – Tim Schmelter Apr 15 '13 at 20:55
  • possible duplicate of [Constraint for only one record marked as default](http://stackoverflow.com/questions/637894/constraint-for-only-one-record-marked-as-default) – Pondlife Apr 15 '13 at 22:03

4 Answers4

0

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.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
0

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.

Community
  • 1
  • 1
Conrad Frix
  • 51,984
  • 12
  • 96
  • 155
0

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
Lawson
  • 624
  • 1
  • 5
  • 19
0

There another approach: use a "reverse" FK:

ER Model

[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.

Community
  • 1
  • 1
Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167