Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it good or overkill when every column in a table is a foreign key?

I am building a database of possible vehicles, where each entry has a Make, Model, Year, and Engine.

I split that into a tables of Makes (Ford, Chevrolet, BMW, etc) and Models (Impala, Camaro, F-150, etc) and Years (1920, ... 2012) and Engines (327, 350, etc).

Since I now have a table for each of Make, Model, Year, and Engine, and they can each have unique primary key, every row in the main "MakesModelsAndYears" table is just made up of four foreign keys.

Is this overkill, or really stored more efficiently than just one big table where I created unique indexes? My concern with the "one big table" approach is that the years, like 1970, would be repeated many times (1970 Chevrolet Impala, 1969 Chevrolet Camaro, etc) as would have model and even engine.

Thanks for any guidance!

enter image description here

Followup:

For those following along, I incorporated the feedback in the answers and arrived that this schema. The image doesn't show the FKs in detail, but they're effectively what the answer suggested:

enter image description here

like image 875
Dave Avatar asked Oct 28 '25 14:10

Dave


1 Answers

Chevrolet doesn't make a Mustang. Ford didn't make a Mustang in 1960. Your structure will allow a lot of nonsense.

The issue isn't that every column is a foreign key; there's nothing necessarily wrong with that. The issue is that the foreign keys are wrong.

I split that into a tables of Makes (Ford, Chevrolet, BMW, etc) and Models (Impala, Camaro, F-150, etc) and Years (1920, ... 2012) and Engines (327, 350, etc).

And that's why they're wrong. When you normalize a relation, you start with the relation, identify the candidate keys, and work out the functional dependencies. Just making single-column "lookup" tables for every column isn't normalization, and it doesn't constrain your data in the way that's needed. (And in this particular case, the constraints are the missing piece, not normalization to 5NF.)

Make       Model   Yr    Engine
--
Ford       F-150   2012  3.7L V6
Ford       F-150   2012  3.5L V6 EcoBoost
Ford       F-150   2012  5.0L V8
Ford       F-150   2012  6.2L V8
Ford       F-150   2011  3.7L V6
Ford       F-150   2011  3.5L V6 EcoBoost
Ford       F-150   2011  5.0L V8
Ford       F-150   2011  6.2L V8
Chevrolet  Camaro  2012  3.6L V6
Chevrolet  Camaro  2011  3.6L V6
Chevrolet  Camaro  2011  6.2L V8
Chevrolet  Camaro  1980  229ci V6
Chevrolet  Camaro  1980  267ci V8
Chevrolet  Camaro  1980  305ci V8
Cadillac   CTS     2004  3.6L V6
Vauxhall   Astra   1979  1.3L
Vauxhall   Astra   1979  1.6L
Vauxhall   Astra   1979  1.8L
Opel       Astra   1979  1.5L
Opel       Astra   1979  2.0L

It should be clear that the only candidate key is {Make, Model, Yr, Engine}. So this table is all key, and it has no non-prime attributes.

To add "lookup" tables as constraints on data, it's not good enough to say that in the first column you have to choose from {Ford, Chevrolet, Cadillac, Vauxhall, Opel}, and in the second column you have to choose from {F-150, Camaro, CTS, Astra}. The right "lookup" table for make and model includes both make and model; you choose from {Ford F-150, Chevrolet Camaro, Cadillac CTS, Vauxhall Astra, Opel Astra}. (In this case, it goes even further. See the table model_years below.)

create table makes (
  make varchar(25) primary key
);

insert into makes values
('Ford'),
('Chevrolet'),
('Cadillac'),
('Vauxhall'),
('Opel');

create table models (
  make varchar(25) not null references makes (make),
  model varchar(25) not null,
  primary key (make, model)
);

insert into models values 
('Ford', 'F-150'),
('Chevrolet', 'Camaro'),
('Cadillac', 'CTS'),
('Vauxhall', 'Astra'),
('Opel', 'Astra');

create table model_years (
  make varchar(25) not null,
  model varchar(25) not null,
  year integer not null check (year between 1900 and 2050),
  primary key (make, model, year),
  foreign key (make, model) references models (make, model)
);

insert into model_years values
('Ford', 'F-150', 2012),
('Ford', 'F-150', 2011),
('Chevrolet', 'Camaro', 2012),
('Chevrolet', 'Camaro', 2011),
('Chevrolet', 'Camaro', 1980),
('Cadillac', 'CTS', 2004),
('Vauxhall', 'Astra', 1979),
('Opel', 'Astra', 1979);

create table model_year_engines (
  make varchar(25) not null,
  model varchar(25) not null,
  year integer not null,
  engine varchar(25) not null,
  primary key (make, model, year, engine),
  foreign key (make, model, year) references model_years (make, model, year)
);

insert into model_year_engines values
('Ford', 'F-150', 2012, '3.7L V6'),
('Ford', 'F-150', 2012, '3.5L V6 EcoBoost'),
('Ford', 'F-150', 2012, '5.0L V8'),
('Ford', 'F-150', 2012, '6.2L V8'),
('Ford', 'F-150', 2011, '3.7L V6'),
('Ford', 'F-150', 2011, '3.5L V6 EcoBoost'),
('Ford', 'F-150', 2011, '5.0L V8'),
('Ford', 'F-150', 2011, '6.2L V8'),
('Chevrolet', 'Camaro', 2012, '3.6L V6'),
('Chevrolet', 'Camaro', 2011, '3.6L V6'),
('Chevrolet', 'Camaro', 2011, '6.2L V8'),
('Chevrolet', 'Camaro', 1980, '229ci V6'),
('Chevrolet', 'Camaro', 1980, '267ci V8'),
('Chevrolet', 'Camaro', 1980, '305ci V8'),
('Cadillac', 'CTS', 2004, '3.6L V6'),
('Vauxhall', 'Astra', 1979, '1.3L'),
('Vauxhall', 'Astra', 1979, '1.6L'),
('Vauxhall', 'Astra', 1979, '1.8L'),
('Opel', 'Astra', 1979, '1.5L'),
('Opel', 'Astra', 1979, '2.0L');

No engine can go in this table unless its row first exists in model_years. No year can go in model_years unless its row first exists in models. And no row can go in models unless its row first exists in makes.

You can make a good case for using ON UPDATE CASCADE in a schema like this. You can also make a good case for not using it. Oracle doesn't support ON UPDATE CASCADE, which is one reason you see ID numbers peppering Oracle tables, and why you sometimes see people say "Primary key values must never change."

These are the kinds of tables you need to implement your known requirements.

like image 177
Mike Sherrill 'Cat Recall' Avatar answered Oct 31 '25 05:10

Mike Sherrill 'Cat Recall'



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!