Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 SQL Server Development (2000)
 Good or Bad Design

Author  Topic 

nic
Posting Yak Master

209 Posts

Posted - 2003-01-17 : 12:37:59
Hi,
I'm build an program/database that will track "user applications". These applications will have different statuses. (complete,incomplete,finaled,etc). These values are stored in the database and I need to references these statuses throughout the program. Here is the dilema. I can see a scenario where a "user" might not like the name of a status and would like to change it. That being said, I can't reference a value in code that could be changed. The primary key for a status is an int value and in code, it makes it difficult if I reference status '1' which correlates to 'Incomplete'. Sooooo I made a the status table to be like:

Create table tblStatus(
st_id int
,st_systemName varchar(20)
,st_displayName varchar(20)
)

The idea being, in code I can reference the systemName which will never change. If the user wants to change the display name it won't affect anything. By referencing in code, the systemName instead of the key value, the code will be much more readable.

Am I way off base? Does this sound like a solid design? Has anyone run into a problem like this before?

Any guidence would be appreciated.



Nic

Onamuji
Aged Yak Warrior

504 Posts

Posted - 2003-01-17 : 12:45:55
make one stored procedure that loads parameters for the values ...

CREATE TABLE Status (
StatusID INT NOT NULL PRIMARY KEY NONCLUSTERED
Name VARCHAR(64) NOT NULL UNIQUE CLUSTERED
)

INSERT INTO Status (StatusID, Name) VALUES (1, 'Complete')
INSERT INTO Status (StatusID, Name) VALUES (1, 'Incomplete')
INSERT INTO Status (StatusID, Name) VALUES (1, 'Finaled')
GO

CREATE PROCEDURE GetStatusID
@Complete INT = NULL OUTPUT,
@Incomplete INT = NULL OUTPUT,
@Finaled INT = NULL OUTPUT
AS
SET NOCOUNT ON

SET @Complete = 1
SET @Incomplete = 2
SET @Finaled = 3
GO

CREATE PROCEDURE CompleteStuff AS
SET NOCOUNT ON

DECLARE @complete INT

EXEC GetStatusID @Complete = @complete OUTPUT

SELECT * FROM Stuff WHERE StatusID = @complete
GO

Go to Top of Page

Page47
Master Smack Fu Yak Hacker

2878 Posts

Posted - 2003-01-17 : 12:57:28
Bad Design? Yes ...
1.) You haven't defined a primary key.
2.) All your columns are nullable.
3.) You use hungarian notation for your table and column names.
4.) You name you table in the singular, when it is a set (plural).
5.) None of the status codes you mention are close to 20 characters.
6.) You provide no check contraints to define allowable names.
7.) I'm not sure I understand the diff between system and display. If a user wants to change "incomplete" to "not done" is that a new status? Smells like it to me ...

The surrogate key vs. natural key debate has been played out, but I believe in the case of "look-up" tables, a natural key is better.

Jay White
{0}
Go to Top of Page

nic
Posting Yak Master

209 Posts

Posted - 2003-01-17 : 13:45:43
Thanks for the suggestions.
Just to clarify, I wrote the CREATE table statement just to help clarify the issue. Adding all the key, null information makes it more difficult to read. I was just asking about general design ideas. Point well taken though.

That being said, if I make the key value to be a natural key, the whole issue is resolved. The key would be the systemName and the display would be the displayName (These would not be the actual column names obviously)

One last question. In regards to these "look-up" tables, is it a good idea to have a seperate table for each "look-up" type (even if that means having a large number of these tables?)

In the application I have many dropdown lists (states,userRoles,applicationTypes,driverCounts,statusCodes etc) Some of the non-crucial lists I have stored in a "General" dropdown table where I can provide the dropdown type and get the list in return.

This saves some effort but if it is not a good practice, I can put each list in a seperate table.

Would this be better?

Nic
Go to Top of Page

Page47
Master Smack Fu Yak Hacker

2878 Posts

Posted - 2003-01-17 : 14:08:31
quote:

One last question. In regards to these "look-up" tables, is it a good idea to have a seperate table for each "look-up" type (even if that means having a large number of these tables?)



Yes ... many look-up tables = good ...

Until someone comes up with a FK constraint with predicate logic built, the only way to enfore DRI is with many look-up tables.

Jay White
{0}

Edited by - Page47 on 01/17/2003 14:08:57
Go to Top of Page

robvolk
Most Valuable Yak

15732 Posts

Posted - 2003-01-17 : 14:43:15
Here's some ideas:

http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=22326

The structure I detailed there may or may not work for you, but it let me set up an unlimited number of categories using only two tables (and it's all natural keys! mostly anyway...codes were used for question groups/categories) It also had a "display name" in the form of QuestionText and AnswerText. The data for this would look like:

Questions
QuestionGroup Question QuestionText
WC Contact Are you the authorized account contact?
WC Address What is the authorized address for your account?
WC Units How many units did you purchase?

Answers
QuestionGroup Question Answer AnswerText
WC Contact Yes Yes, caller is authorized account contact
WC Contact No No, caller is just some schmuck
WC Address Yes Yes, caller has correct address
WC Address No No, caller doesn't know correct address
WC Units 1 One unit
WC Units 2 Two units
WC Units 3 Three units
WC Units 4 Four units
WC Units 5 Five units
WC Units 6+ Six or more units
The actual answers for each caller were stored something like this:
CustomerResponses
CustomerName QuestionGroup Question Answer
Joe Smith WC Contact Yes
Joe Smith WC Address Yes
Joe Smith WC Units 5
For your dropdown lists, you can change the Questions table and get rid of the QuestionGroup column; each question would be a separate dropdown name or category. The Answers table would also lose the QuestionGroup column, so would CustomerResponses. However, if you had multiple applications, keeping that kind of column as "AppName" or "Category" would allow you to manage every app's controls, questions, and answers from 3 tables. This would be the "predicate logic" Jay was looking for. You could certainly use more tables to save actual responses if you wanted to, but the DRI can still be done using 2 question and answer tables.

Just in case someone jumps into this thread about "you're wasting all that space using natural keys" A) get over it, space is cheap, and there's not one superfluous column in any table, B) I can get any query I need from one table and one table only, I only need to join to get the actual descriptions of the question and answer text, which I don't even need on a report because the values are descriptive enough, C) the clustering on this table makes the most frequent queries lightning fast, a join could only slow it down, D) all the columns make up the key and prevent duplicate answers, a surrogate key and an additional unique index would only slow it down (in the real system the customername actually was an account number generated by another system), and lastly E) if you can show me a surrogate key design that does the same thing using less than 5 tables and allows all reporting from one table only, post it here, I'd love to see it. Seriously, I'm not joking.

Also, using natural keys here PREVENTS people from making arbitrary requests like "I don't like Yes/No, I want to change it to Authorized/Unauthorized". This is not a limitation, it's a conscious design decision. Jay is right about incomplete and not done, are they really the same or not? If they're the same, why change it? If they're not, then they're two different things and need to be stored as such. If you used a surrogate key you could very easily change every single answer or description into something that means the exact opposite of the original value.

Edited by - robvolk on 01/17/2003 14:45:06
Go to Top of Page

Onamuji
Aged Yak Warrior

504 Posts

Posted - 2003-01-17 : 15:08:10
good point on data integrity ... just wish business integrity was the same ... positions change so damn much ... from Director of IT ... to Cheif Information Office ... to who knows whats next ... when someone hears a different buzz word they think its catchy and command their minions to change every as such ...

Go to Top of Page

byrmol
Shed Building SQL Farmer

1591 Posts

Posted - 2003-01-17 : 19:20:01
Jay,

Regarding "Look-ups". I recently had the displeasure of working on a large db application that used a "code table". It contained about 50 domains and of course had no integrity with the data tables. At first everything appeared to be fine as the DB was exposed only to 1 front-end. Demand grew for the data and many requests where made to the DB from "outside" of the original front-end. XML, bulk loads etc...

The result was as expected... garbage got into the db. Due to the app design, the front-end starts to throw errors due to the data. A good example not to do but it did get me thinking about a "better" code table...


--"Code Table"
CREATE TABLE Domains (Domain VARCHAR(35) NOT NULL, Value SQL_VARIANT NOT NULL CHECK(SQL_VARIANT_PROPERTY(Value,'TotalBytes') <= 865),
IsDefault BIT NOT NULL DEFAULT(0), Constraint PK_Domains Primary Key(Domain, Value))
go
--Integrity Function
CREATE FUNCTION fnIsDomain
(@Domain VARCHAR(35),
@Value SQL_VARIANT)
RETURNS BIT
AS
BEGIN
DECLARE @BIT BIT
SET @BIT = 0
IF EXISTS(SELECT 1 FROM Domains WHERE Domain = @Domain and Value = @Value)
SET @BIT = 1
RETURN @BIT
END
GO
--Default generator
CREATE FUNCTION fnDefault
(@Domain VARCHAR(35))
RETURNS SQL_VARIANT
AS
BEGIN
RETURN (SELECT Value from Domains where Domain = @Domain and IsDefault = 1)
END
go
--Domain data (Gender is not a good example but you get the picture)
INSERT TABLE Domains (Domain, Value, IsDefault)
SELECT 'Gender','M',0
UNION ALL
SELECT 'Gender','F',0
UNION ALL
SELECT 'Gender','?',1
GO
--In use...
CREATE TABLE Person (PersonID int NOT NULL Primary Key, Gender CHAR(1) NOT NULL CHECK(dbo.IsDomain('Gender',Gender) = 1)
DEFAULT(CAST(dbo.fnDefault('Gender') AS CHAR(1))
GO

Note the CHECK constraint in the DOMAINS table for SQL Server's 900 byte index length.

Its not perfect but it is a little better...

Thoughts, comments...

DavidM

"SQL-3 is an abomination.."
Go to Top of Page

robvolk
Most Valuable Yak

15732 Posts

Posted - 2003-01-17 : 19:53:44
Hmmmm...I'm almost tempted to beat you up on the variant column but I see what you're doing there. How does it handle nvarchar vs. varchar though? Or does it pretty much convert them to nvarchar and compare it that way? I wonder how a long char/varchar string that's near the byte limit would affect it.

What about this as an alternate Person table structure:

CREATE TABLE Person (PersonID int NOT NULL Primary Key,
GenderDomain varchar(35) NOT NULL DEFAULT('Gender') CHECK (GenderDomain='Gender'),
Gender char(1) NOT NULL DEFAULT(CAST(dbo.fnDefault('Gender') AS CHAR(1)
CONSTRAINT FK_Domain FOREIGN KEY (GenderDomain, Gender) REFERENCES Domains(Domain, Value))


Yeah, I know: WTF is with the GenderDomain column? But think in terms of a smaller column maybe 2-3 characters, similar to the QuestionGroup column in the Questions table I posted earlier. I've used similar designs in other databases having multiple tables referencing a table like Questions->QuestionGroup, with a fixed column like GenderDomain. It made each table's references consistent, even though it used some extra space. Basically, if had to have two different CustomerResponses tables, one for group WC and another for group WVC, they would both reference Questions on the same columns.

YOUR thoughts, comments...? Feel free to tell me I'm smoking crack, I wouldn't disagree

Go to Top of Page

byrmol
Shed Building SQL Farmer

1591 Posts

Posted - 2003-01-18 : 05:08:28
The variant column is dodgy, but I feel it is as good a use for a variant as it gets...

Haven't tested all the data types yet or pushed the 865 byte length for strings..

I think both methods achieve the same level of integrity but with significant differences.

The Question example uses a Type (QuestionGroup) and then its Sub Type (Question) in the table. That is, QuestionGroup is a domain and so is Question.
The Domains example is just one type (Gender) and its values.
More a semantic difference but significant.

Explaination..
When you applied the technique to the Person table (in context to the Domain approach), we seem to have broken 3NF (Each attribute relies on the Key, the whole key and nothing but key...). We could create a view of the Person table that projects away the GenderDomain to give it a 3NF appearance though...
The Question example does not break 3NF.

The Question example uses built in RI while the Domains is custom made. That seems to make sense...



DavidM

"SQL-3 is an abomination.."
Go to Top of Page
   

- Advertisement -