<ir> Home
One gets tired of typing the same join specifications over and over into the MySQL workbench, so...
/**
 * MySqlJoinGenerator traverses information_schema data to yield join specifications for the 
 * registered foreign keys in the passed connection's schema.  Only works for engines that
 * use DRI like InnoDb.  No copyright claimed - feel free to use this as you wish.
 * 

use getJoins method to retrieve strings like:

*/ public class MySqlJoinGenerator { private final static Comparator<String>IGNORE_CASE=(a,b) -> a.compareToIgnoreCase(b); private final Map<String,String>aliasMap = new TreeMap<String,String>(IGNORE_CASE); private final List<ForeignKeyRow>fkRows=new ArrayList<>(); private final Map<String,String>groupPrefixes = new TreeMap<String,String>(IGNORE_CASE); private final Set<String>ignorePrefixes = new TreeSet<String>(IGNORE_CASE); /** * Provided as a sample of usage: * @return a list of strings like "fromTable fromAlias inner join toTable toAlias on fromAlias.fromCol1=toAlias.toCol1" * */ public static List<String>dumpAll(Connection mysqlConn) throws SQLException { var ignorePrefixes = Set.of("t","g"); var groupPrefixes = Map.of( "gCarrier","ca", "gClaim","c", "gUser","gu", "tArticle","a", "tCompany","co", "tCustomer","cu", "tOrder","o", "tOrderRoom","orm", "tUser","u"); var fd = new MySqlJoinGenerator(mysqlConn,ignorePrefixes,groupPrefixes); for (var spec : fd.getJoins()) { System.out.println(spec); } } /** * @param mysqlConn using schema we care about * @param ignorePrefixes if you use t as table name prefix you may want to include t as an ignorePrefix * @param groupPrefixes if key is full table name, pre-registers alias, like tCustomer→c, or if you have * a tree of related tables like tOrder*, you can force them all to have the same first character, like o * @throws SqlException */ public MySqlJoinGenerator(Connection mysqlConn,Set<String>ignorePrefixes,Map<String,String>groupPrefixes) throws SQLException { this.ignorePrefixes.addAll(ignorePrefixes); this.groupPrefixes.putAll(groupPrefixes); this.fkRows.addAll(ForeignKeyRow.selectForSchema(mysqlConn)); for (var r : fkRows) { assignPreferred(r); } for (var r : fkRows) { assignArbitrary(r.fromTable); assignArbitrary(r.toTable); } // var valueCheck = new TreeSet<String>(IGNORE_CASE); for (var alias : aliasMap.values()) { if (! valueCheck.add(alias)) { StringKit.println(getClass().getSimpleName() + " FAIL: alias " + alias + " assigned multiple times."); } } } /** assigns alias to table passed as next available single character, or * if we've exhausted the alphabet, as two characters where second character * could be a-z or 1-9 */ private void assignArbitrary(String tableName) { if (aliasMap.containsKey(tableName)) { return; } var nameFrom = tableName; for (var ignorePrefix : ignorePrefixes) { if (tableName.toLowerCase().startsWith(ignorePrefix.toLowerCase())) { nameFrom = tableName.substring(ignorePrefix.length()); break; } } var charAlias = nameFrom.toLowerCase().charAt(0); while (charAlias!=0 && aliasMap.containsValue(charAlias + "")) { charAlias = nextFirst(charAlias); } if (charAlias != 0) { aliasMap.put(tableName, charAlias+""); return; } //try 2 chars var aliasPrefix = nameFrom.toLowerCase().substring(0,1); char aliasSuffix = nameFrom.length() > 1 ? nameFrom.toLowerCase().charAt(1) : 'a'; String strAlias = aliasPrefix + aliasSuffix; while (aliasMap.containsValue(strAlias) || badAlias(strAlias)) { aliasSuffix = nextSecond(aliasSuffix); if (aliasSuffix==0) { break; } strAlias = aliasPrefix + aliasSuffix; } if (! badAlias(strAlias)) { aliasMap.put(tableName, strAlias); } } /** assigns alias to table passed by group prefix */ private void assignByGroup(String tableName) { var prefixFullMatch = groupPrefixes.get(tableName); if (prefixFullMatch!=null) { setAlias(tableName,prefixFullMatch); return; } String groupMatch = null; for (var prefixKey : groupPrefixes.keySet()) { if (tableName.toLowerCase().startsWith(prefixKey.toLowerCase())) { var aliasPrefix = groupPrefixes.get(prefixKey).toLowerCase(); var tableNameSuffix = tableName.substring(prefixKey.length()); var aliasSuffix = tableNameSuffix.toLowerCase().charAt(0); var alias = aliasPrefix + aliasSuffix; while (aliasMap.containsValue(alias) || badAlias(alias)) { aliasSuffix = nextFirst(aliasSuffix); if (aliasSuffix==0) { break; } alias = aliasPrefix + aliasSuffix; } if (aliasSuffix!=0) { groupMatch = alias; } break; } } if (groupMatch != null) { setAlias(tableName,groupMatch); } } /**assigns from and to aliases based on group prefixes */ private void assignPreferred(ForeignKeyRow r) { for (var from : new boolean[] {true,false}) { var tableName = from ? r.fromTable : r.toTable; if (aliasMap.containsKey(tableName)) { continue; } assignByGroup(tableName); } } /** indicates whether alias is ok for use in MySQL */ private boolean badAlias(String v) { if (v.charAt(0)==0) { return true; } if (v.length()==1) { return false; } return v.charAt(1)==0 || v.equalsIgnoreCase("if") || v.equalsIgnoreCase("or"); } /** @return all joins for schema */ public List<String> getJoins() { return getJoins(null,null); } /** * @param String fromTableOrNullForAll a full table name or null for any from table * @param String toTableOrNullForAll a full table name or null for any to table * @return all joins meeting criteria from params */ public List<String> getJoins(String fromTableOrNullForAll,String toTableOrNullForAll) { var result = new ArrayList<String>(); for (var r : fkRows) { if (fromTableOrNullForAll!=null && ! fromTableOrNullForAll.equalsIgnoreCase(r.fromTable)) { continue; } if (toTableOrNullForAll!=null && ! toTableOrNullForAll.equalsIgnoreCase(r.toTable)) { continue; } result.add(r.getFromTo(aliasMap)); result.add(r.getToFrom(aliasMap)); } result.sort(IGNORE_CASE); return result; } /** yields the next character allowed as first of an alias, or 0 (not '0') on failure */ private char nextFirst(char what) { final String abc = "abcdefghijklmnopqrstuvwxyz"; var ix = abc.indexOf(what); if (ix < abc.length() - 1) { return abc.charAt(ix + 1); } return 0; } /** yields the next character allowed as second of an alias, or 0 (not '0') on failure */ private char nextSecond(char what) { final String abc2 = "abcdefghijklmnopqrstuvwxyz123456789"; var ix = abc2.indexOf(what); if (ix < abc2.length() - 1) { return abc2.charAt(ix + 1); } return 0; } private void setAlias(String table,String alias) { aliasMap.put(table,alias.toLowerCase()); } static class ForeignKeyRow { public final String[] fromColumns; public final String fromTable; public final String[] toColumns; public final String toTable; public static List selectForSchema(Connection mysqlConn) throws SQLException { final var sql=""" select tc.table_name as ft ,replace(f.ref_name,concat(schema(),'/'),'') as tt ,group_concat(fc.for_col_name) as fcs ,group_concat(fc.ref_col_name) as tcs from information_schema.Table_CONSTRAINTS tc inner join information_schema.INNODB_SYS_FOREIGN f on f.id=concat(schema(),'/',tc.constraint_name) inner join information_schema.INNODB_SYS_FOREIGN_COLS fc on f.id=fc.id inner join information_schema.columns c on c.table_name=tc.table_name and c.column_name=fc.for_col_name where tc.constraint_schema = schema() and tc.constraint_type like 'FOREIGN_KEY%' group by ft,tt,tc.constraint_name order by ft,tt,tc.constraint_name """; var result = new ArrayList(); try (var rs = mysqlConn.createStatement().executeQuery(sql)) { while (rs.next()) { result.add(new ForeignKeyRow(rs)); } rs.close(); } return result; } ForeignKeyRow(ResultSet rs) throws SQLException { this.fromTable = rs.getString(1); this.toTable = rs.getString(2); this.fromColumns = rs.getString(3).split(","); this.toColumns = rs.getString(4).split(","); } public String getFromTo(Map<String,String>aliasMap) { return getSpec(aliasMap,fromTable,fromColumns,toTable,toColumns); } private String getSpec(Map<String,String>aliasMap,String ft,String[] fca,String tt,String[]tca) { var fromAlias = aliasMap.get(ft); if (fromAlias == null) { fromAlias = ft; } var toAlias = aliasMap.get(tt); if (toAlias == null) { toAlias = tt; } var b = new StringBuilder("") .append(ft).append(" ").append(fromAlias) .append(" inner join ") .append(tt).append(" ").append(toAlias); var conjunction = " on "; for (int i=0;i<fca.length;i++) { b.append(conjunction).append(fromAlias).append(".").append(fca[i]).append("=") .append(toAlias).append(".").append(tca[i]); conjunction = " and "; } return b.toString(); } public String getToFrom(Map<String,String>aliasMap) { return getSpec(aliasMap,toTable,toColumns,fromTable,fromColumns); } } }