Is $wpdb->prepare escaping to much? How to use it properly?

What you did wrong here was to prepare those items in the first place.

You only run “data” variables through prepare(). You don’t run table names, or sort directions, or limits through it. These are part of the SQL command itself, they are not data that refers to information which is stored in a column in the database.

Your SELECT query has no data inputs… therefore it does not have any inputs that can be prepare’d.

For the specific case of a SELECT, data is the stuff in the WHERE clause. column = %s and so on. That is a variable piece of data that can be potentially unsafe because it possibly comes from user input. So that data must be run through prepare to make it safe. But if you have a hardcoded “DESC” for the ordering, then there’s no point in running it through prepare. It’s “DESC”. It’s safe as is. Only data that you do not know what it is can be unsafe.

Edit: That said, if data does come from user input, like if the user can select the number of items to display, then that data must be sanitized. So limits can be run through prepare as they are integers and might come from a user selecting “5” or “10”.

However, you wouldn’t allow a user to directly input the table name, or sort order. They would pick a direction to display things, but you’d convert that into ASC or DESC. They would not pick “books” directly, but might select from a list which you interpret as “books”. That sort of thing.