Wow. Such a simple question with a complicated answer! The short answer is "it depends."
It depends on what your goals are. If processing time and resources don't matter, then it's whatever you want to do. When learning, as is the case here, it's best to learn and practice both methods.
Now, let's think about when something matters. If execution time is ...
There is no ? in cs50x 2018 version of sql; ":bound :variables" are used to mitigate sql injection attacks.
Of the three "options" this db.execute("INSERT INTO users (username, hash) VALUES (:username, :hash)", username, password) is closest to correct. But no cigar.
The part that's missing is where you tell the sql what to "substitute" for the bound ...
If by this
recommended syntax for adding a column using SQLite does not appear to work.
you mean foreign key is not enforced, check out section 2 in SQLite doc, which says
Assuming the library is compiled with foreign key constraints enabled, it must still be enabled by the application at runtime, using the PRAGMA foreign_keys command. For example: