The joins required for a SQL query are usually driven by the use case. You could end up with one repository method per use case, which would result in a large number of methods that are only called once or twice in the application. While there is nothing wrong with this, it does tend to make for cluttered and unorganized data access code.
There is no general or easy solution. A good Object-Relational Mapper (ORM) can give you lots of flexibility to configure and fine tune these queries, but that comes at the cost of increased complexity and configuration. This could be justifiable, though, given the number of queries and use cases.
Using ORM API calls outside your data access layer ends up coupling the ORM to areas of your application that should remain ignorant of how data is persisted. This coupling makes refactoring and testing harder. Numerous strategies have been developed to mitigate this, but they all have their drawbacks.
You could decide that more coupling to the data access layer is justified. For example, in an MVC application the controller could have direct knowledge of the data access code beyond dealing with interfaces or other abstractions. The controller could make calls to the ORM or hard code the table names as arguments to data access methods.
Still other strategies involve building additional abstractions over an ORM or existing data access code — layering abstraction on top of abstraction. Query builders are an example of such an abstraction, which provide an object-oriented way to create SQL queries. I've still found these to be leaky abstractions, though. Names of tables and columns inadvertently propagate throughout your code even as you attempt to hide theses details.
Command-Query Responsibility Segregation (CQRS) is a design pattern that at least attempts to clean up this problem. The model used to modify data is separate from the model used to retrieve data. This can give you more flexibility to couple persistence knowledge in an area of the application specializing in retrieval of data. These classes can implement interfaces to facilitate mocking and stubbing for unit tests, but again, this increases complexity.
Whether you pollute the code base with details of your ORM or table names, crank up complexity by introducing more abstractions over your data access tier, or create an enormous number of repository methods, you must make sacrifices. Just make sure the benefits outweigh the drawbacks, and try to be consistent. Even if the code is not ideal, if it is consistent it is at least predictable, which means it is livable.