If you have a large number of users to delete, you might consder using the wp user delete wp-cli command to avoid script timeouts.
Here's an example of a SQL query to delete all users without posts of any type and status.
You can therefore try this
wp user delete $(wp db query "SELECT ID FROM wp_users WHERE ID NOT IN (SELECT DISTINCT post_author FROM wp_posts ) AND ID NOT IN (1,2,3)" | tail -n +2 ) --reassign=1
or in expanded form:
wp user delete $(wp db query
WHERE ID NOT IN (
SELECT DISTINCT post_author FROM wp_posts
) AND ID NOT IN (1,2,3)" | tail -n +2
Note that we added an extra
AND ID NOT IN (1,2,3) restriction to make sure these users are not deleted (e.g. admin users). You will have to adjust it to your needs and also the table prefix
When I briefly tested this for couple of users, I noticed I had to add the
tail -n +2 part to avoid the top 3 lines in the header and the table border of the
wp db query output.
Here we reassign all posts to user 1, to avoid the notice:
--reassign parameter not passed. All associated posts will be deleted. Proceed? [y/n]
Hope you can adjust it further to your needs, like relaxing the user delete conditions by adding
WHERE post_status = 'publish'.
Note: Remember to backup before testing!