One common way is to add a classifier that is "inherited" like:
CREATE TABLE products
( product_id ... NOT NULL PRIMARY KEY
, ...
, product_type ... NOT NULL
, UNIQUE (product_id, products_type)
, CHECK (product_type IN ('food', 'furniture'))
);
product_type would typically be a code of some kind. Might be a foreign key to a "lookup" table instead of a check constraint. For the sub-types:
CREATE TABLE food
( product_id ... NOT NULL PRIMARY KEY
, ...
, product_type ... DEFAULT 'food' NOT NULL
, FOREIGN KEY (product_id, product_type)
REFERENCES products (product_id, product_type)
, CHECK (product_type = 'food')
);
and a similar one for furniture. The constraints guarantee that product_type is consistent between super- an sub- tables.
There are products (I've heard) that allow sub-selects in CHECK constraints, but the majority do not. For such product something like:
CREATE TABLE food
( product_id ... NOT NULL PRIMARY KEY
, ...
, FOREIGN KEY (product_id)
REFERENCES products (product_id)
, CHECK (
(SELECT product_type
FROM products p
WHERE p.product_id = product_id) = 'food'
)
);
could be used.
An alternative to the latter is to use before triggers for insert/update, and signal an exception if the wrong product_type is used. Personally I don't fancy using procedural code for integrity constraints, but I guess it is a matter of taste.
EDIT: If you for one reason or another want to abandon check constraints (personally I don't find them problematic) you can easily replace them with:
CREATE TABLE food_types
( food_type text not null );
INSERT INTO food_types (food_type) VALUES ('food);
Now you can add a foreign key constraint instead of the check constraint like:
CREATE TABLE food
( product_id ... NOT NULL PRIMARY KEY
, ...
, product_type ... DEFAULT 'food' NOT NULL
, FOREIGN KEY (product_id, product_type)
REFERENCES products (product_id, product_type)
, FOREIGN KEY (product_type)
REFERENCES food_types (food_type)
);