views:

117

answers:

3

This is a very open question, but I think it can be very beneficial for SQL readability.

So you have a Java program, and you are trying to call a monster SQL statement from it, with many subqueries and joins. The starting point for my question is a string constant like this:

static string MONSTER_STATEMENT = 
  "SELECT  " +
  "   fields" +
  "WHERE "+
  "   fieldA = (SELECT a FROM TableC) " +
  "AND fieldB IN (%s)" +
  "AND fieldC = %d " +
  "FROM "
  "   tableA INNER JOIN tableB ON ...";

It later gets filled using String.format and executed.

What are you tricks for making this kind of stuff readable? Do you separate your inner joins. Do you indent the SQL itself inside the string? Where do you put the comments? Please share all of the tricks in your arsenal.

A: 

Such complex SQL should be in a prepared statement, not put together by a String.format to avoid SQL injection issues.

I do one of two things: Wrap it in a callable statement or put it in a configuration/xml file. Java source files are a bad place for complex SQL.

That being said, IDEA has a language formatting feature which will format strings in Java to look like a different language (based on the method they are passed to or based on an annotation), so that can help.

Edit: Given your scenario, I agree callable statements may not be workable. Prepared statements are still worth it, even for internal development, just because a parameter with an apostrophe can blow everything up (even if there is no attacker), so it is still worth it, IMO, to do the extra effort to create the string dynamically (String.format or anything else) in code only for the parts that cannot be passed as parameters (such temporary table names as in your example), and then use that as a prepared statement passing proper parameters for the rest.

As for keeping it less ugly, it looks like a configuration/xml type file is what I would go for.

Yishai
I would love to use prepared/callable statements, but I've found these problems:1) They are a huge pain to deploy -- this is for an internal dashboard and we are using continuous deployment. In a related issue, they are a big to pain to edit.2) They use prepared SQL, and it's not as flexible -- I need to be able to for example generate to a table with a new name (e.g, INSERT INTO TableForExperiment_${DATE}).3) Callable statements are not much easier to comment or make readable (for example, I have no way to put a subquery in a separate string).
Artem
@Artem: You can still use an ad hoc `PreparedStatement`, can't you? As long as **you** control the text that varies, things aren't bad. The real problem is when users can inject their own text which is rendered as in-line sql.
Chris Farmer
@Chris, what do you mean exactly by an ad-hoc PreparedStatement? Are we talking a stored procedure or something else? I don't find stored procedure logic to be easier to read, and I've always wanted to be able to program inside the server process. Programining procedural SQL is only somewhat better than programming in XML (ANT - yuck).
Artem
No, I just mean creating a java PreparedStatement with SQL you provide in your app. `PreparedStatement stmt = conn.prepareStatement("SELECT * FROM foo WHERE bar = ?"); stmt.setString(1, "hello");` I thought your argument was that prepared statements are a hassle, but you can use a Java prepared statement with app-provided sql and still take advantage of parameters to help avoid injection attacks. So you'd build up your main sql in a string but use "?" to mark your parameters, and use the prepared statement to supply the parameter values.
Chris Farmer
So, to address your point in the first comment to Yishai's answer, you'd do `PreparedStatement stmt = conn.prepareStatement("INSERT INTO TableForExperiment_" + someStringWithTheDate + " (foo) values (?)"); stmt.setString(1, someUserProvidedStringValue);` This way you can more safely use ad hoc queries with your flexible table names, but still use parameters to guard against unsafe injection from your users.
Chris Farmer
+1  A: 

My inclination is to format my SQL statement just as I would want it if I queried directly against the database using a management console like Management Studio. Thus, when I build a SQL statement, I take the time put in spaces and line breaks. The additional space consumed is inconsequential compared with the time savings of being able to read the SQL should I print it out or capture it using something SQL Profiler. So, I would be inclined to use StringBuilder to assemble my SQL like so:

StringBuilder sql = new StringBuilder();
sql.append("Select ....");
sql.append("\t\n, AdditionalCol, ...");
sql.append("\nFrom ...");
sql.append("\n  Inner Join ...");
sql.append("\n      On ColA =  ...");
sql.append("\nWhere Col1 = (");
sql.append("\n          Select a");
sql.append("\n          From TableC");
sql.append("\n          ");
sql.append("\n  And ColB In(%s)");
sql.append("\n  And ColC = %d");
Thomas
+1 for readability, but you should also use parameterized queries to avoid sql injection - and use a method similar to http://stackoverflow.com/questions/1254940/is-there-a-way-to-get-the-full-sql-text-back-from-sqlcommand-after-param-substitu/1255040#1255040 (in C#) to log the SQL with its actual parameters
ckarras
A: 

A helper class that wrapps around the building process may help you. That could be one: http://openhms.sourceforge.net/sqlbuilder/

Anyway, always go with prepared statements.

PeterMmm
This is an interesting alternative. Has someone used for complex statements successfully? (see above for my problems with prepared statements)
Artem