87

I'm analysing a rather horrible legacy database/codebase, trying to reduce server load by combining queries into joins (including an email alert cron job that typically invokes well over a million separate queries).

SELECT * FROM 
class_alerts_holding ah 
INNER JOIN class_listings l ON l.id = ah.lid 
INNER JOIN class_users u ON u.id = ah.uid
LEFT JOIN class_prodimages pi ON pi.pid = ah.lid

This spits out 120 columns...

aid | id | lid | uid | oid | catName | searchtext | alertfreq | listType | id | owner | title | section | shortDescription | description | featured | price | display | hitcount | dateadded | expiration | url | notified | searchcount | repliedcount | pBold | pHighlighted | notes | ...

To assist my analysis of how to construct the new queries it would be awesome if I could prefix the columns in the result with the table they came from in the JOIN e.g.

class_alerts_holding.aid | class_alerts_holding.id | class_listings.lid | ...

Is there a way to achieve this?

sleep
  • 4,855
  • 5
  • 34
  • 51
  • 2
    I'm afraid the only possibility is typing out / generating the `SELECT` statement manually – John Dvorak Oct 31 '12 at 07:49
  • 1
    Possible duplicate of [SQL select join: is it possible to prefix all columns as 'prefix.\*'?](http://stackoverflow.com/questions/329931/sql-select-join-is-it-possible-to-prefix-all-columns-as-prefix) – Steve Chambers Oct 19 '16 at 09:33
  • For the benefit of future readers: If your editor supports regex find and replace, then you can make the job easier - find each column name with this pattern: "( ?)(.*?)," and replace with this pattern" "\1PREF.\2 AS PREF_\2," – Teddy Feb 23 '17 at 15:08
  • Haven't tried but I think that if you first compile your tables into a struct, you can then deploy them with a prefix? – neydroydrec Sep 17 '20 at 07:13

11 Answers11

58

You could

select ah.*, l.*, u.*, pi.* from ...

then the columns will be returned ordered by table at least.

For better distinction between every two sets of columns, you could also add "delimiter" columns like this:

select ah.*, ':', l.*, ':', u.*, ':', pi.* from ...

(Edited to remove explicit aliases as unnecessary, see comments.)

Andriy M
  • 76,112
  • 17
  • 94
  • 154
koljaTM
  • 10,064
  • 2
  • 40
  • 42
  • @AndriyM Just a note to simplify the query slightly: I found that without the `AS` I got the same result e.g. `select ah.*, ':', l.*, ':', ...` – sleep Nov 01 '12 at 00:00
  • @JarrodSmith: Ah, indeed, explicit aliases aren't needed in MySQL in this case. I used them because I work in SQL Server, and its Management Studio provides default names that are too long, which isn't very nice. So, that was a nice piece of optimisation on your side, I'll edit the answer to reflect that. – Andriy M Nov 01 '12 at 06:02
  • 9
    Columns with same names (e.g. id, name, description) disappear! – Ярослав Рахматуллин Jun 25 '16 at 13:55
  • 1
    I like this answer as it's a quick and simple way of knocking up queries e.g. for debugging purposes. As a small modification, when there are a lot of tables it is useful to put each table name before the colon, e.g. `'my_table:', mt.*` - avoids having to keep looking in the query to find the *n*th table. – Steve Chambers Oct 19 '16 at 09:38
  • 1
    This is a good answer, tried to upvote and found I already did! Also double quotes `"` throws an error in Redshift so make sure you are using single quotes `'`. – callpete Oct 29 '20 at 18:43
38

You could name the fields in your query and give them aliases:

SELECT     ah.whateverfield1 AS 'ah_field1',
           ah.whateverfield2 AS 'ah_field2',
           l.whateverfield3 AS 'l.field3',
           [....]
FROM       class_alerts_holding ah 
INNER JOIN class_listings l ON l.id = ah.lid 
INNER JOIN class_users u ON u.id = ah.uid
LEFT JOIN  class_prodimages pi ON pi.pid = ah.lid

Its a bit of work to manually set up if you have that many fields, but you can simplify this with this query...

SHOW FULL FIELDS FROM your_table_name;

...and a good text editor and copy & paste.

Bjoern
  • 15,934
  • 4
  • 43
  • 48
  • 35
    So there is no way to just go table.* as table and have all the columns from table returned as table.columnName? – James111 Apr 18 '16 at 02:27
  • If your editor supports regex find and replace, then you can find each column name with this pattern -> ( ?)(.*?), and replace with this pattern -> \1PREF.\2 AS PREF_\2, – Teddy Feb 23 '17 at 14:54
28

I am convinced that such feature to prefix and/or postfix fields names with a table name in a join SHOULD BE INCLUDED INTO ANSI SQL STANDARD. Currently, in year 2019, there is still no elegant cross-platform way to do it, and all what's left is ugly-looking and error-prone manual hacking with aliases, or platform-specific solutions involving dynamic sql. Everyone would really benefit from having ability to specify custom prefix or/and postfix to fields denoted by 'dot-star' (.*). Sample select after adding such feature would be:

select a.* use prefix,b.* use postfix '_b' from table_a a inner join table_b b on a.id=b.id

As you can see, by default prefix or postfix would equal table name (or alias name), and can be overridden with any desired string literal.

Also what's aching to be added to standard, is ability to exclude certain fields from 'starred' (*) output, which is a shortcut to select all fields. I would add except keyword to list fieds which I do not want to be included for reasons of reducing network data transfer or/and brevity, e.g. :

select * except large_binary_data_field,another_notneeded_field,etc from my_table

Such feature would allow to avoid necessity of explicitly specifying full (and potentially large) list of fields which are needed as opposed to only specifying star and a few fields which are not needed.

So please, whoever reading this post and being able to reach out to ANSI SQL standard influencers, you know what to do )

P.S. yet another ugly, but at least automated & generic dynamic sql wrapper

For the Python advocates who work with psycopg, here is the convenient sub I use (strictly internally, as it's prone to possible sql injections)

def get_table_fields(table,alias,prefix='',suffix='',excluding=''):
    if type(excluding)==str: excluding=excluding.split(',')
    cur.execute('select * from '+table+' where 0=1');cur.fetchall()
    if not (cur.description is None):        
        return ','.join([alias+'.'+col.name+' '+prefix+col.name+suffix for col in cur.description if not (col.name in excluding)])

And the calling code, where I am joining 3 tables and want to avoid fetching large data field from the datasets table:

sql="""select %s,%s,%s from tasks t,features_sets f,datasets d 
        where 
                t.is_active=true and f.is_active=true 
                and f.task=t.id and t.train_dataset=d.id 
    """ % (
        get_table_fields('tasks','t',prefix='ts_'),
        get_table_fields('features_sets','f',prefix='fs_'),
        get_table_fields('datasets','d',prefix='ds_',excluding='data')
    )

which gets unrolled for me into mighty

select t.id ts_id,t.project ts_project,t.name ts_name,***,
    fs_id,f.task fs_task,f.name fs_name,f.description fs_description,***,
    d.id ds_id,d.project ds_project,d.name ds_name,***
from tasks t,features_sets f,datasets d 
    where 
        t.is_active=true and f.is_active=true 
        and f.task=t.id and t.train_dataset=d.id 

where *** means tons of other useful fields, some of them are common for more than one table (hence the need for prefixing). cur is obviously the psycopg cursor, and 0=1 condition is intended to retrieve only fields names without real data.

Anatoly Alekseev
  • 2,011
  • 24
  • 27
12

The way to dynamically name columns is to generate a prepared statement that references the information_schema. This would give you the results you were looking for.

SET @sql = NULL;
SELECT CONCAT(
   'SELECT ',GROUP_CONCAT(c.TABLE_NAME,'.',c.COLUMN_NAME,' AS `',c.TABLE_NAME,'.',c.COLUMN_NAME,'`'),'
    FROM class_alerts_holding 
    INNER JOIN class_listings ON class_listings.id = class_alerts_holding.lid 
    INNER JOIN class_users ON class_users.id = class_alerts_holding.uid
    LEFT JOIN class_prodimages ON class_prodimages.pid = class_alerts_holding.lid'
)
INTO @sql
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME IN ('class_alerts_holding','class_listings',
                       'class_users','class_prodimages');    
PREPARE sql_statement FROM @sql;
EXECUTE sql_statement;

The GROUP_CONCAT() function has a default limit of 1024 characters, so depending on the number of columns in your tables, you may need to raise this limit in order to generate the prepared statement.

SET SESSION group_concat_max_len = 1000000;

This command will raise the group concat limit if needed. -

Alden W.
  • 1,362
  • 12
  • 19
  • 1
    Sure thing, here is a fiddle with this example done up, http://sqlfiddle.com/#!9/e99bf2/10 . Putting it together saw you would need to take out the table aliases and include the AS statements, so I've edited to include those. Fiddle seems to be odd about how it displays the column names. It show only id for the first column, for example. But in any other client you will see the specified `class_alerts_holding.id`. – Alden W. Mar 14 '16 at 21:32
10

I ended up just building the field set for the query, as as of 2020 this still isn't supported.

But, being a lazy programmer, I obviously didn't want to manually type this all out for all of the tables in my query. So I wrote a query to build the select statement:

SELECT
    CONCAT(table_name, ".", column_name, " AS ", CHAR(34), table_name, ".", column_name, CHAR(34)) field_names
FROM
    information_schema.columns
WHERE
    table_schema = "my_database"
    AND table_name IN(
        "table_1",
        "table_2"
    );

which will output something like:

| field_names                        |
|------------------------------------|
| table_1.id AS "table_1.id"         |
| table_1.name AS "table_1.name"     |
| table_2.id AS "table_2.id"         |
| table_2.number AS "table_2.number" |

That can then easily be copied into your SELECT statement.

Othyn
  • 829
  • 1
  • 9
  • 21
  • 3
    If you want to be even lazier, you could do `GROUP_CONCAT(CONCAT(table_name, ".", column_name, " AS \`", table_name, ".", column_name, "\`") SEPARATOR ", ")` so just 1 result is returned and they are already separated by commas. – Kade Oct 09 '20 at 14:08
3

I've found something usefull in this question MySQL concat() to create column names to be used in a query? . I think that this can be one of the solutions.

Community
  • 1
  • 1
Mihai Matei
  • 24,166
  • 5
  • 32
  • 50
3

Based on the solution proposed by koljaTM and AndriyM, maybe an even better solution is to write your query like this:

select
  '--TABLE_AAA:--', TABLE_AAA.*,
  '--TABLE_BBB:--', TABLE_BBB.*,
  '--TABLE_CCC:--', TABLE_CCC.*,
  '--TABLE_DDD:--', TABLE_DDD.*
from ...

Unfortunately this is still not good enough in cases when one (or more) of the tables contains more column names than can fit on the screen width. (So you might see on your screen 20 columns but still not be visible on the screen the name of the table from which they come.)

It would still have been better if SQL provided a way to automatically prefix the column names with the table names...

Sorin Postelnicu
  • 1,271
  • 1
  • 10
  • 15
  • 3
    re: screen width —PostgreSQL and MySQL shells have a vertical results mode where each value is printed as a column: value pair. Postgres: type `\x` in the shell to switch to extended mode. MySql: end the query with `\G` instead of `;`. – Mat Gessel Jan 01 '16 at 00:55
  • Thanks for the suggestions, @MatGessel ;) Still, I was referring to the case when you have more columns than can fit on your physical screen :D In that case you will still not know at which columns you are currently looking, because the 'prefix' is now outside the screen, so you will have to use a lot of scrolling from left to right and from right to left :) – Sorin Postelnicu Mar 13 '19 at 17:10
0

@alden-w, You may add TABLE_SCHEMA condition to where to do not mix up same table names from different schemas

WHERE c.TABLE_SCHEMA='YOUR_SCHEMA_NAME' AND c.TABLE_NAME IN (....)
iabr
  • 31
  • 2
0

You may try dynamic sql to create a query on the go as per the table definition.

declare @col varchar(max)
set @col = Select stuff( 
          (select ', ' + column_name + '.' + table_name 
           from information_schema.columns 
           where table_name in ( 'table1', 'table2' ...) for xml 
           path('')),1,1,'')

declare @query nvarchar(max) = '
select ' + @col + ' 
from table1 
inner join table2 on table1.id = table2.id '

exec sp_executesql @query

DarkRob
  • 3,843
  • 1
  • 10
  • 27
0
CREATE OR REPLACE FUNCTION getAlias (mytable text, my_alias text, my_prefix text)
RETURNS SETOF TEXT AS $$
   SELECT my_alias || column_name || ' as ' || my_prefix
   FROM information_schema.COLUMNS
   WHERE TABLE_NAME = mytable;
$$ LANGUAGE SQL

-- You can write function in db. This function need to be a standard in SQL.

Elikill58
  • 4,050
  • 24
  • 23
  • 45
0

This works for me following DarkRob suggestion in MS SQL. Doing it this way avoids the "Ambiguous column name ..." error message if both tables share some of the column names.

DECLARE @cols1 NVARCHAR(max)
SET @cols1 =  (SELECT STUFF(
          (SELECT ', ati.' + [COLUMN_NAME] + ' AS ' + 'ati_' + [COLUMN_NAME] FROM information_schema.columns 
           WHERE [TABLE_NAME] in ('audit_trans_inv') FOR XML PATH('')),1,1,''))

DECLARE @cols2 NVARCHAR(max)
SET @cols2 =  (SELECT STUFF(
          (SELECT ', ti.' + [COLUMN_NAME] + ' AS ' + 'ti_' + [COLUMN_NAME] from information_schema.columns 
           WHERE [TABLE_NAME] in ('transaccion_inv') FOR XML PATH('')),1,1,''))

DECLARE @sql NVARCHAR(max) = '
SELECT TOP 5 ' + @cols1 + ',' + @cols2 + '
FROM [millennium].[AUDIT_TRANS_INV] ati
INNER JOIN [millennium].[TRANSACCION_INV] ti ON [ti].[AUDIT_TRANS_INV] = [ati].[AUDIT_TRANS_INV]

EXEC sp_executesql @sql