Alguns designers muito bons usam NULLs em chaves estrangeiras sem consequências adversas. Eu mesmo me inclino assim. Um FK anulável representa um relacionamento opcional. Nos casos em que a entidade não tem relacionamento, o FK contém um NULL. A sobrecarga de espaço é mínima. Quando as junções (equijoins, mais precisamente) são feitas nas duas tabelas, as instâncias que contêm NULL no FK sairão da junção, e isso é apropriado.
Dito isso, vou recomendar um quarto método para você. Isso envolve um total de 4 tabelas, contas, widgets, tipos e custom_types. A tabela custom_types usa uma técnica chamada Shared-primary-key, descrita abaixo.
CREATE TABLE accounts (
account_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
# Other Columns...,
PRIMARY KEY (account_id)
);
CREATE TABLE widgets (
widget_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
type_id INT UNSIGNED NOT NULL,
PRIMARY KEY (widget_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE,
FOREIGN KEY (type_id) REFERENCES types(type_id)
);
CREATE TABLE types (
type_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (type_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id)
CREATE TABLE custom_types (
type_id INT NOT NULL,
account_id INT UNSIGNED NOT NULL,
PRIMARY KEY (type_id),
FOREIGN KEY (type_id) REFERENCES types(type_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id)
);
A coluna type_id em custom_types é uma chave primária compartilhada. Observe que ele é declarado AMBOS como chave primária e como chave estrangeira, e que não usa autonumber. É uma cópia da chave primária em tipos para a entrada correspondente. A tabela de tipos personalizados contém todos os dados presentes nos tipos personalizados, mas ausentes nos tipos predefinidos.
Para tipos predefinidos, uma entrada é feita em tipos, mas nenhuma entrada é feita em custom_types. Para custom_types, uma entrada é feita primeiro em tipos e, em seguida, o valor resultante de type_id é copiado para custom_types, junto com o account_id.
Se você usar os tipos INNER JOIN e custom_types, os tipos predefinidos serão excluídos da junção. Se você deseja tipos personalizados e predefinidos em uma única junção, você deve usar um LEFT JOIN ou um RIGHT JOIN para obter esse efeito. Observe que o resultado de um LEFT ou RIGHT JOIN conterá alguns NULLs, mesmo que esses NULLs não sejam armazenados no banco de dados.
Clicar nesta shared-primary-key fornecerá uma descrição mais detalhada da técnica de chave primária compartilhada.