I think you are doing well going for the 4th normal form and exploding your data. Now, as you found out, you need to de-normalise to support for historical data support: if the review will always account for a particular department's score whatever the current department of the reviewee_id is, then yes, you definitely need to add a department at the review table and make sure you know what/why it is there for.
Push the logic further: what if there is a 4th and 5th category added in a year? How do you calculate past and future averages then? On 3 or 5 categories? Then another year later, the first category gets removed? Will you delete all scores for that category, or change yet again the average's calculation based on the number of "category" at the time of the "date" of the "score"?
Sometime, too much normalisation is not the best course of action. You have a good core here. Time to custom-fit now. Take a step back, plan for the future, ask the end-users to make sure you support all their foreseeable needs, and look at the impacts of adding/removing/updating departments, employees and categories on historical reporting.
As for reporting, if you are using powerful data tools like IBM Cognos, it will be easy to generate data cubes, then let managers do their own reports. If you are using tools like Microsoft Excel with "external data", then the easiest course of action would be to pre-create Views or Stored Procedures that crunch up the data. The caveat here is that you will have to create a new View/SP for every manager that comes up with a new request/idea/variant.
IMHO you just have to pay attention on the order of cardinalities, and you should note that Issue Date is not a weak key, but it's not a key at all: in fact you could have two account statements of different people issued on the same date, so you need a foreign key (Issue Date plus Number of Account)
Best Answer
Before I begin, let's address a few issues with your current data model:
Which leads us to:
IDs are not by themselves keys
Everyone loves to slap auto-incrementing integers on tables and call them keys. They are row pointers, nothing more. Keys guarantee uniqueness of data, and
Ids
are not data, they are generated by the system.For example, in your current data model it would be permissible to insert the exact same information into the
Product
table until I run out of integers. There is no key.IDs can be keys, but only in addition to another key
The primary key on
Product
in your example is most likely something along the lines of(ProductCategory, ProductName, Color)
. This is rather wide (in bytes), so it's reasonable to demote that key to an alternate key and makeProductId
1 the primary key of the table.In this case, we say that
ProductId
is a surrogate for the original primary key.Limiting customers to 1 review per product
Within the context of your problem, the primary key of
Review
is(ProductId, CustomerId)
. If we try to insert two reviews of the sameProduct
authored by the sameCustomer
, this will violate the primary key and the insert will fail.As a benefit, we can abandon the useless
Id
column, which means we do not need an additional index to access aReview
for a givenProduct
2Other considerations
ProductCategory
should be it's own table andProduct
should have a foreign key constraint to that table to enforce consistency.Right now an
Order
can only fulfill a singleProduct
. Usually this is implemented asOrder
followed byOrderLine
(primary key(OrderId, LineNbr)
) which would allow aProduct
to be sold at different prices (useful for sales/discounts).I know it's just an example, but for the love of all that is sacred and holy, do not store the credit card information in the
Customer
table. ACustomer
could have multiple payment methods, and in any case it's much safer to have that data encrypted and in a table with very limited access.1A better key would be something like
ProductCd
which would reflect the manufacturer's part code, a barcode, or some internal signifier (internal to the business, not the database).2You would still need an index on
CustomerId
in order to more quickly access the reviews of a particularCustomer
.