How Can I Put Many Table Fields In Single Query Column?

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

I am making a system to show why employees are leaving a company, based on a
five-point system. 1 being an important reason, 5 being unimportant. At the
moment, an employee will, upon leaving, be asked to fill in an electronic
questionaire.

As an example, let me say that their are 3 fields, [Pay], [Argument],
[Retirement]. (Brackets for ease of reading.

So for example, if I was leaving the company due to being underpaid, I would
put a 1 for Pay, a 3 for Argument since I don't like my boss, and a 5 in
retirement.

My question is this; how can I create a query to display the average reasons
people have said they are leaving, in ascending order? Since if each record
is summed, then divided by the total number of records in the database, the
lowest value will be the most important reason.

I currently have a query, showing for each field, the Sum of values in that
field, and a count of records. I can then make a query to show for each
field, what the average is. But how can I then display these values in a way
that can be sorted?
 
I would not go any further until the table(s) are normalized. The reason for
leaving should be a single field. Each reason creates a new record in a
table. You should be able to add more types of reasons without changing any
tables, forms, queries, reports,...

Do you understand where I am going with this? If not, try search the web for
Normalization and/or
http://office.microsoft.com/en-us/assistance/HA010563211033.aspx
 
I did consider normalisation when creating this database, but I'm not sure if
it can work in my situation. There are many more different fields that need
to be stored, such as what industry the employee will be working in, if they
were head-hunted for their new position etc.

In this situation, I am not sure if it is possible to normalise this table.
 
You can use your current table and add a primary key field. Then just pull
out the "repeating" columns. You can leave the "industry the employee will
be working in" unless you have fields like [Manufacturing], [Medical],
[Technology], ...
 
Duane said:
The reason for
leaving should be a single field.
Each reason creates a new record in a
table. You should be able to add more types of reasons without changing any
tables, forms, queries, reports,...

I can think of two good reasons against doing this.

First, you are splitting attributes across tables. If reason for
leaving is an attribute of an employee then put it in the employees
table.

Second, it would make constraints harder to write. For example, if one
reason for leaving was due to disciplinary action (i.e. was fired) and
another reason was due to age related retirement, how would you ensure
you did not end up with a rows for both being added for the same
individual?

You are correct that only one column should be used. The standard
approach is to use bitwise values (I think that's the term) that can be
added to make unique values: 1=normal retirement, 2=disciplinary
action, 4=resigned, 8=death etc. You can then use row level CHECK
constraints to disallow illegal combinations.

Jamie.

--
 
"Reason for leaving" is not an attribute of employee. This would be an
attribute of an employee termination.

You can set up constraints based on referential integrity and business
rules.

I can think of no good reason to use the bitwise values on this when the
proper normalization and table structures can handle all requirements.
 
Duane said:
"Reason for leaving" is not an attribute of employee. This would be an
attribute of an employee termination.

You can set up constraints based on referential integrity and business
rules.

With your model, I don't think I can. But I'm willing to try. I assume
you mean *you* could come up with the required constraints, so if I
promise to try my best, will you fill in the gaps for me, please?

You implied a table of EmployeeTerminations, so we need tables for
Employees and Terminations.

I'll start with the easy one: Terminations. We can use a surrogate key
as the primary key (PK). A description is required and we don't want
duplicates:

CREATE TABLE Terminations (
termination_ID INTEGER NOT NULL PRIMARY KEY,
termination_description VARCHAR(100) NOT NULL UNIQUE,
CHECK (LEN(termination_description) > 0)
)
;
INSERT INTO Terminations (termination_ID, termination_description)
VALUES (1,'Pay')
;
INSERT INTO Terminations (termination_ID, termination_description)
VALUES (2,'Argumnet')
;
INSERT INTO Terminations (termination_ID, termination_description)
VALUES (3,'Retired')
;
INSERT INTO Terminations (termination_ID, termination_description)
VALUES (4,'Died')
;

Next is Employees. We are required to use social security number (SSN)
as a key. I don't know what a SSN looka like in your region, so change
my LEN(ssn)>9 for something more appropriate <g>. We hit the first
issue: given the existence of an EmployeeTerminiations table, is
employment_end_date an attribute of an employee or a termination? Let's
put it in the Employees table for now:

CREATE TABLE Employees (
ssn CHAR(9) NOT NULL PRIMARY KEY,
CHECK(LEN(ssn) = 9),
employment_start_date DATETIME DEFAULT DATE() NOT NULL,
employment_end_date DATETIME,
CHECK (employment_start_date <= employment_end_date)
)
;

EmployeeTerminations: call it what you will - a relationship table,
junction table, lookup table, etc - I prefer 'relationship table'. It
holds a foreign key (FK) from other tables but we shouldn't forget to
give it its own PK to avoid duplicate relationships:

CREATE TABLE EmployeeTerminations (
ssn CHAR(9) NOT NULL,
termination_ID INTEGER NOT NULL,
CONSTRAINT pk__eeterm PRIMARY KEY (ssn, termination_ID),
CONSTRAINT fk__eeterm__ee FOREIGN KEY (ssn)
REFERENCES Employees (ssn)
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk__eeterm__term FOREIGN KEY (termination_ID)
REFERENCES Terminations (termination_ID)
ON UPDATE CASCADE
ON DELETE CASCADE
);

Let's hire someone:

INSERT INTO Employees (ssn) VALUES ('123456789');

We have another issue here: because we have split the 'reason for
leaving' attribute off from the employee, we have no constraint to
prevent us from creating EmployeeTerminations for this employee. We
need to make alterations, which will also address our earlier issue:

ALTER TABLE Employees
ADD CONSTRAINT uq__ee__ssn__end
UNIQUE (ssn, employment_end_date)
;
ALTER TABLE EmployeeTerminations
ADD employment_end_date DATETIME NOT NULL)
;
ALTER TABLE EmployeeTerminations
DROP CONSTRAINT fk__eeterm__ee
;
ALTER TABLE EmployeeTerminations
ADD CONSTRAINT fk__eeterm__ee
FOREIGN KEY (ssn, employment_end_date)
REFERENCES Employees (ssn, employment_end_date)
ON UPDATE CASCADE ON DELETE CASCADE
;

By making employment_end_date NOT NULL in EmployeeTerminations, we
enforce the rule that an employee must have left employment to have
rows in EmployeeTerminations

OK, let's make our employee a leaver:

UPDATE Employees
SET employment_end_date = DATE()
WHERE ssn = '123456789'
;

Right, here's problem number one: if I take no further action I have
a leaver with no corresponding rows in EmployeeTerminations. Our
business rule that at least one reason for leaving must be recorded for
a leaver remains unenforced. Suggested fix here, please.

OK, proceeding to record reasons for leaving, we note that the employee
had an argument relating to pay:

INSERT INTO EmployeeTerminations
(ssn, employment_end_date, termination_ID)
VALUES ('123456789', DATE(), 1)
;
INSERT INTO EmployeeTerminations
(ssn, employment_end_date, termination_ID)
VALUES ('123456789', DATE(), 2)
;
INSERT INTO EmployeeTerminations
(ssn, employment_end_date, termination_ID)
VALUES ('123456789', DATE(), 3)
;
INSERT INTO EmployeeTerminations
(ssn, employment_end_date, termination_ID)
VALUES ('123456789', DATE(), 4)
;

And there we have problem number two: while Argument + Pay is a legal
combination, + retirement + death are clearly violations. How do we
fix this one, please?

Jamie.

--
 
Duane said:
At a certain point, I would use code behind forms to enforce data integrity.

After all the effort I put in, I couldn't be more disappointed by your
reply.

Jamie.

--
 
Jamie Collins said:
After all the effort I put in, I couldn't be more disappointed by your
reply.
I think Duane was afraid he might start an "Argumnet" 8-)

If it means anything, I was certainly impressed by the
work you put into it. When it slows down here at work,
I want to go back and think about what you have
posted.

Gary Walter
 
Gary said:
I think Duane was afraid he might start an "Argumnet" 8-)

But I sincerely admire his honesty. He was decent enough to admit has
When it slows down here at work,
I want to go back and think about what you have
posted.

Bear in mind what I posted concluded that Duane's model leads to a
situation where AFAIK constraints cannot enforce the business rules. So
instead, consider the following simpler model, based on the idea that
reason for leaving *is* an attribute of an employee, with appropriate
constraints:

CREATE TABLE Employees (
ssn CHAR(9) NOT NULL PRIMARY KEY,
employment_start_date DATETIME DEFAULT DATE() NOT NULL,
employment_end_date DATETIME,
reasons_for_leaving INTEGER,
CONSTRAINT ssn_valid_format CHECK(LEN(ssn) = 9),
CONSTRAINT employment_date_order
CHECK (employment_start_date <= employment_end_date),
CONSTRAINT leaving_code_requires_employment_end_date
CHECK(
(employment_end_date IS NULL AND reasons_for_leaving IS NULL)
OR (employment_end_date IS NOT NULL AND reasons_for_leaving IS NOT
NULL)),
CONSTRAINT leaving_code_valid
CHECK(reasons_for_leaving IN (1,2,3,4,8))
)
;

Note although the CHECK syntax has been part of Jet since Access2000,
the UI has lagged so you need either Access2003 or ADO to be able to
*execute* the DDL however, once created, the table may be used in the
earlier versions of the UI.

Jamie.

--
 
Hi, I appreciate the work you have put into this and I must admit I would
use code or create a class object to enforce data integrity.

Question on your solution: What happens if you add more values to your valid
reasons for leaving? I doubt Access/Jet allows you to create constraints
that would be required across tables other than foreign key type. I am not
that knowledgeable on this. I would not however, hard-code in acceptable
values for the reasons for leaving.

--
Duane Hookom
MS Access MVP


Gary said:
I think Duane was afraid he might start an "Argumnet" 8-)

But I sincerely admire his honesty. He was decent enough to admit has
When it slows down here at work,
I want to go back and think about what you have
posted.

Bear in mind what I posted concluded that Duane's model leads to a
situation where AFAIK constraints cannot enforce the business rules. So
instead, consider the following simpler model, based on the idea that
reason for leaving *is* an attribute of an employee, with appropriate
constraints:

CREATE TABLE Employees (
ssn CHAR(9) NOT NULL PRIMARY KEY,
employment_start_date DATETIME DEFAULT DATE() NOT NULL,
employment_end_date DATETIME,
reasons_for_leaving INTEGER,
CONSTRAINT ssn_valid_format CHECK(LEN(ssn) = 9),
CONSTRAINT employment_date_order
CHECK (employment_start_date <= employment_end_date),
CONSTRAINT leaving_code_requires_employment_end_date
CHECK(
(employment_end_date IS NULL AND reasons_for_leaving IS NULL)
OR (employment_end_date IS NOT NULL AND reasons_for_leaving IS NOT
NULL)),
CONSTRAINT leaving_code_valid
CHECK(reasons_for_leaving IN (1,2,3,4,8))
)
;

Note although the CHECK syntax has been part of Jet since Access2000,
the UI has lagged so you need either Access2003 or ADO to be able to
*execute* the DDL however, once created, the table may be used in the
earlier versions of the UI.

Jamie.

--
 
ChadNick said:
I am making a system to show why employees are leaving a company, based on a
five-point system. 1 being an important reason, 5 being unimportant. At the
moment, an employee will, upon leaving, be asked to fill in an electronic
questionaire.

As an example, let me say that their are 3 fields, [Pay], [Argument],
[Retirement]. (Brackets for ease of reading.

So for example, if I was leaving the company due to being underpaid, I would
put a 1 for Pay, a 3 for Argument since I don't like my boss, and a 5 in
retirement.

My question is this; how can I create a query to display the average reasons
people have said they are leaving, in ascending order? Since if each record
is summed, then divided by the total number of records in the database, the
lowest value will be the most important reason.

I currently have a query, showing for each field, the Sum of values in that
field, and a count of records. I can then make a query to show for each
field, what the average is. But how can I then display these values in a way
that can be sorted?

Hi Chad,

Initially it appeared (to me) you needed a tblExitSurvey
and tblQuestions with structures *similar to*:

CREATE TABLE tblQuestions (
QuestionID AUTOINCREMENT CONSTRAINT PK_QuestionID PRIMARY KEY,
Question TEXT(50)
);

CREATE TABLE tblExitSurvey (
SurveyID AUTOINCREMENT CONSTRAINT PK_SurveyID PRIMARY KEY,
EmpID LONG NOT NULL CONSTRAINT FK_EmpID
REFERENCES tblEmployees (EmployeeID),
SurveyDate DATETIME,
QID LONG NOT NULL CONSTRAINT FK_QID
REFERENCES tblQuestions (QuestionID),
Rank INTEGER
);

(assuming you had a tblEmployees w/pk EmployeeID)

So your tblQuestions might look like:

QuestionID Question
1 Pay
2 Argument
3 Retirement

assume tblEmployees

EmployeeID EmpName
1 Homer
2 Marge

and both quit and filled in your survey.

Some sample data for tblExitSurvey might be:

SurveyID EmpID SurveyDate QID Rank
1 1 2/18/2005 1 1
2 1 2/18/2005 2 3
3 1 2/18/2005 3 5
4 2 2/18/2005 1 1
5 2 2/18/2005 2 5
6 2 2/18/2005 3 5

Then I think you wanted a query like:

SELECT
tblQuestions.Question,
Avg(tblExitSurvey.Rank) AS AvgOfRank
FROM tblQuestions INNER JOIN tblExitSurvey
ON tblQuestions.QuestionID = tblExitSurvey.QID
GROUP BY tblQuestions.Question
ORDER BY Avg(tblExitSurvey.Rank);

giving

Question AvgOfRank
Pay 1
Argument 4
Retirement 5

see how making structure "thin, rather than wide"
makes the query so easy?

But...then you added
I did consider normalisation when creating this database, but I'm not sure if
it can work in my situation. There are many more different fields that need
to be stored, such as what industry the employee will be working in, if they
were head-hunted for their new position etc.

One *possibility* might be to add one more field ("Response") to tblExitSurvey,
then add these questions to tblQuestions:

CREATE TABLE tblExitSurvey (
SurveyID AUTOINCREMENT CONSTRAINT PK_SurveyID PRIMARY KEY,
EmpID LONG NOT NULL CONSTRAINT FK_EmpID
REFERENCES tblEmployees (EmployeeID),
SurveyDate DATETIME,
QID LONG NOT NULL CONSTRAINT FK_QID
REFERENCES tblQuestions (QuestionID),
Rank INTEGER,
Response TEXT(255)
);

For questions that have a Rank, fill that in and
leave Response blank...likewise for questions
that provide a Response, leave Rank blank.

I think (disclaimer: I have no true insight into what
Duane "thinks"....I am mostly a wiseacre with little
time to give serious thought to this) that was
what Duane was suggesting.

Gary Walter
 
Duane said:
I must admit I would
use code or create a class object to enforce data integrity.

Here's a short article on why that may not be the best approach:

http://www.dbazine.com/celko25.shtml

"There should be one and only one trusted source for the business
rules.
If the constraints are in the database, and not the application, this
kind of question is answered by an error message that pops up in
quality assurance testing. If you need to change the rule, then you can
change it in one and only one place. This has nothing to do with
databases; it is simply good software engineering. "
What happens if you add more values to your valid
reasons for leaving?

DROP the constraint and create a new one!
I would not however, hard-code in acceptable
values for the reasons for leaving.

Then how would you ensure only acceptable values are put into the
database? Hard coding in the front end is still hard coding.
I doubt Access/Jet allows you to create constraints
that would be required across tables other than foreign key type.

See the example in this KB article:

http://support.microsoft.com/default.aspx?scid=kb;en-us;201888
ACC2000: How to Create a Jet CHECK Constraint

"The new SQL grammar exposed by Microsoft Jet database engine version
4.0 allows users to specify business rules that can span more than one
table. These are called check constraints"

Jamie.

--
 
I won't argue with your observations and points. I applaud Joe Celko and
have a couple copies of books he has written.

I will continue to create tables and program using the methods that work
best for me. Dropping and creating constraints on an application that gets
deployed to end users doesn't seem like a good option. I suppose you could
code this with ddl queries. I haven't created apps that would require this
level of constraints that I wouldn't build into the front-end forms and
code. The constraints that I can easily put into the tables, I will put
there.

Based on the original question (that is why we are here isn't it) my
original solution would be the same.
 
Duane said:
I won't argue with your observations and points.
I will continue to create tables and program using the methods that work
best for me.

Oh well, I gave it my best shot at convincing you otherwise. Sincere
thanks for your courtesy.
Based on the original question (that is why we are here isn't it) my
original solution would be the same.

And I still think that for the scenario presented in this thread my
'bitwise column' approach is simpler and more robust. I trust the OP
will make their choice of approach based on merit.

Jamie.

--
 
Back
Top