While there are only few separate columns for students and staff members, I would keep it simple:
CREATE TABLE person (
person_id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, name text
, birthday date -- never age! bitrots in no time
, student_number int
, staff_number int
, salary numeric
, hired_at date
, staff_type text
, CONSTRAINT one_role_max CHECK (student_number IS NULL
OR (staff_number, salary, hired_at, staff_type) IS NULL)
, CONSTRAINT one_role_min CHECK (student_number IS NOT NULL
OR (staff_number, salary, hired_at, staff_type) IS NOT NULL)
);
CREATE TABLE users (
user_number int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, person_id int NOT NULL REFERENCES person
, password text -- encrypted !
);
This way, one person can have 0-n user accounts - which is the typical reality. You can restrict to a single account per person by adding UNIQUE (person_id) to table users.
The CHECK constraint one_role_max enforces that either student columns or staff columns must stay NULL.
The CHECK constraint one_role_min enforces that at least one of both must have any values.
Adapt what must/can be filled in to your needs. The expressions work excellently for the current design. See:
While it's strictly "either/or" and the only student column is student_number, this query answers your question:
SELECT CASE WHEN student_number IS NULL THEN 'staff' ELSE 'student' END AS user_role
FROM person
WHERE person_id = (SELECT person_id FROM users WHERE user_number = 98242);
Or remove one or both CHECK constraints to allow the same person to be student and staff, or neither. Adapt above query accordingly.
You could use inheritance for this (like Abelisto demonstrates), but I'd rather stay away from it. There once was the idea of an object-relational DBMS. But the community has largely moved on. It works, but with caveats. Partitioning used to be a major use case. But declarative partitioning in Postgres 10 mostly superseded the inheritance-based implementation. There is not too much interest in it any more.
What about all those empty columns? Am I wasting a lot of space there? The opposite is the case. The disk footprint won't get much smaller than this. NULL storage is very cheap. See: