Dynamic column alias from another column value in SELECT - sql

I was wondering if there a way, in a SELECT statement on Postgres, to alias a column with the value of another column in the same data set.
Given this table:
id
key
value
1
a
d
2
a
e
3
b
f
This would be the result:
id
a
b
1
d
NULL
2
e
NULL
3
NULL
f
Where for each instance the name of the column is determined from the value of key while the value is the value of the column value, not knowing what kind of values will be provided by the column key.
This is a possible (not working) query:
SELECT "id", "value" AS "t"."key" FROM testTable as t;

One way to achieve pivot in Postgres is using CASE :
select id,
max(case when (key='a') then value else NULL end) as a,
max(case when (key='b') then value else NULL end) as b
FROM TestTable
group by id
order by id;

It seems that there is no way to create the column alias dynamically without knowing the values since the beginning. As many commented the only way to achieve this kind of "table re-mapping" is to use the crosstab function.
Crosstab function summary
This function takes 2 arguments:
The first one is a SQL statement that must return 3 columns:
The first column contains the values identifying each instance and that must be grouped in order to get the final result.
The second column contains the values that are used as categories in the final pivot table: each value will create a separate column.
The third column contains the values used to compile the new columns formed: for each category this column has the value of the instance that had the category value in the original table.
The second argument is not mandatory and is a SQL statement that returns the distinct values the function should use as categories.
Example
In the example above we must pass a query to crosstab that:
Returns as the first column the identifier of each final instance (in this case id)
As second column the values used as categories (all values in key)
As third column the values used to fill the categories (all values in value)
So the final query should be:
select * from crosstab(
'select "id", "key", "value" from testTable order by 1, 2;',
'select distinct "key" from testTable order by 1;'
) as result ("id" int8, "a" text, "b" text);
Since the crosstab function requires a column definition for the final pivot table, there is no way to determine the column alias dynamically.
Dynamically infer column names with client
A possible way to do that, with a PostgreSQL client, is to launch the second query we passed as argument to crosstab in order to retrieve the final columns and then infer the final crosstab query.
As an example, with pseudo-javascript:
const client;
const aliases = client.query(`select distinct "key" from testTable order by 1;`);
const finalTable = client.query(`select * from crosstab(
'select "id", "key", "value" from testTable order by 1, 2;',
'select distinct "key" from testTable order by 1;'
) as result ("id" int8, ${aliases.map(v => v + ' data_type').join(',')});`)
Useful articles
https://learnsql.com/blog/creating-pivot-tables-in-postgresql-using-the-crosstab-function/

Related

Select column value if column exists in that table else create that column and set it's value to null in BigQuery

I want to select total 450 fixed columns from the table which may or may not have all 450 columns always. When it doesn't have all columns then it should create the missing column and set it's value as null.
In Sql there is a function
if exists()
But in bigquery I am unable to use it wisely.
Any suggestion will help a lot
I assume in the following that you have a source table (the one with potentially "missing" columns) and an existing target table (with the desired schema).
In order to get the information of the columns of these tables, you just need to look into the INFORMATION_SCHEMA.COLUMNS table.
The solution below uses dynamic SQL, to 1) generate the desired SQL, 2) run it.
DECLARE column_selection STRING;
SET column_selection = (
WITH column_table AS (
SELECT
source.column_name AS source_colum,
tgt.column_name AS target_column
FROM
(SELECT
column_name
FROM `<yourproject>.<target_dataset>.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name='<target_table>') tgt
LEFT JOIN
(SELECT column_name
FROM `<yourproject>.<source_dataset>.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name='<source_table>') source
ON source.column_name = tgt.column_name
)
SELECT STRING_AGG(coalesce(source_column,
CONCAT("NULL AS `",target_column, "`")), ", \n") AS col_selection
FROM
column_table
)
EXECUTE IMMEDIATE
FORMAT("SELECT %s FROM `<yourproject>.<source_dataset>.<source_table>`", column_selection) ;
Explanation of the steps
Build a column_table for the columns we want to query:
a. first column containing the columns of the target table,
b. second one containing the corresponding source columns if they exist, or NULL if they don't
Once we have this table, we can build the desired SELECT statement: the name of the column is it's in the source table, or if it's NOT present, we want to have in our query " NULL AS `column_name_in_target` "
This is expressed in the
coalesce(source_column, CONCAT("NULL AS ``",target_column, "\``"))
We aggregate all these statement with STRING_AGG into the desired column selection.
Final step: putting together the rest of the query ( "SELECT" + <column_selection_string> + "FROM <your_source_table>" + ...), and we can EXECUTE IMMEDIATE it.

Can I hard code a value into column in the SELECT clause

I want to populate column SURVEY_TYPE_ID with 1
Can I do this like so?
SELECT
SURVEY_ID,
SKILL_ID,
1 AS SURVEY_TYPE_ID,
From Table A
This should work as it is:
SELECT
SURVEY_ID,
SKILL_ID,
1 AS SURVEY_TYPE_ID
From Table A
Also, if you want to populate string in the column value just add quotes.
SELECT
SURVEY_ID,
SKILL_ID,
'id_123' AS SURVEY_TYPE_ID
From Table A
This query returns a result set with a third column whose value is 1:
SELECT SURVEY_ID, SKILL_ID, 1 AS SURVEY_TYPE_ID
FROM Table_A
(This is the same as your query with the spurious comma removed.)
If you want to change the value of the column in the table, then you need UPDATE:
UPDATE Table_A
SET SURVEY_TYPE_ID = 1;
If you want to add a new column to the table, then the syntax varies depending on the database, but generally something like this:
ALTER Table_A ADD COLUMN SURVEY_TYPE_ID INT DEFAULT 1;

SQL: How to update an empty column with pre-defined set of values

I have a table with, let's say, 100 records. The table has two columns. The first column (A) has unique values. The second column (B) has NULL values
For 4 elements from column A I'd like to associate some earlier defined values, and they are unique as well.
I don't care about which value from column B will be associated with the value from column A. I'd like to associate 4 unique values with another 4 unique values. Basically, like I'd cut and paste a block of values from one column to another in excel.
How can I do it without using cursors?
I'd like to use one Update statement for ALL rows instead one Update statement for EVERY row as I do now.
Try this:
UPDATE t
SET ColumnB = BValue
FROM Table t
INNER JOIN
(
SELECT 1 AValue, 'Mouse' BValue UNION
SELECT 2, 'Cat' UNION
SELECT 3, 'Dog' UNION
SELECT 4, 'Wolf'
) PreDefined ON(t.ColumnA = PreDefined.AValue)
Use any number you want in the 'PreDefined' table, as long as they are unique and within the range of values in columnA of your original table.
If you are only trying to fill a table for testing purposes, I guess you could:
A) Use the value from Column A itself (as it is already unique).
B) If they are to be different, use some function on the column A's value to obtain a column B value (something simple, like (ColumnA * 10), and this would give youA)
C) Create a temp table with a "dictionary" setting a B value for each possible A value, and then update the rows desired on your table looking up from values on this dictionary table.
Anyway, if you explain a little further your purpose it will be easier to try suggesting you a solution.
if your animal data is already in a database table, then you can use a single update statement like this:
update target_table t4
set columnb = (
select animal_name
from (select columna, animal_name
from (select rownum rowNumber, animal_name from animal_table) t1
join (select rownum rowNumber, columna from target_table t1 where columnb is null) t2
on t1.rowNumber = t2.rowNumber
) t3
where t4.columna = t3.columna
)
;
this works by selecting a sequence number and animal name from the source table, then selecting a sequence number and columna value from your target table. by joining those records on the sequence number you guarantee you get exactly 1 animal name for each columna value. you can then join those columna-to-animal records to your target table to do an update of columnb.
for more background on updating one table from values in another, you might consider the solutions presented here: Update rows in one table with data from another table based on one column in each being equal. the only difference is that in your example, you do not have any column that matches between your target table and your animal names table, so you need to use the rownum to create an arbitrary 1-to-1 matching of records.
if your unique options are in a text file or spreadsheet, then you can format them into a fixed-width space-padded string and pick the one you want using the rownum index like so:
update table_name
set columnb = trim(substr('mouse cat dog wolf ', rownum*6-6, 6))
where columnb is null;

update table with 4 columns specified, but only 2 columns are available

I have one table called test, which has 4 columns:
id INT
v_out INT
v_in INT
label CHARACTER
I'm trying to update the table with the following query:
String sql = "
update
test
set
v_out = temp.outV
, v_in = temp.inV
, label = temp.label
from (
values(
(1,234,235,'abc')
,(2,234,5585,'def')
)
) as temp (e_id, outV, inV, label)
where
id = temp.e_id
";
When I execute it, I got the error:
org.postgresql.util.PSQLException: ERROR:
table "temp" has 2 columns available but 4 columns specified
Whats the problem, and how can i solve it?
The values for the values clause must not be enclosed in parentheses:
values (
(1,234,235,'abc'), (2,234,5585,'def')
)
creates a single row with two columns. Each column being an anonymous "record" with 4 fields.
What you want is:
from (
values
(1,234,235,'abc'),
(2,234,5585,'def')
) as temp (e_id, outV, inV, label)
SQLFiddle showing the difference: http://sqlfiddle.com/#!15/d41d8/2763
This behavior is documented, but that is quite hard to find:
http://www.postgresql.org/docs/current/static/rowtypes.html#AEN7362
It's essentially the same thing as select (col1, col2) from some_table vs. select col1, col2 from some_table. The first one returns one column with an anonymous composite type that has two fields. The second one returns two columns from the table.

SQL statement to return data from a table in an other sight

How would the SQL statement look like to return the bottom result from the upper table?
The last letter from the key should be removed. It stands for the language. EXP column should be split into 5 columns with the language prefix and the right value.
I'm weak at writing more or less difficult SQL statements so any help would be appreciated!
The Microsoft Access equivalent of a PIVOT in SQL Server is known as a CROSSTAB. The following query will work for Microsoft Access 2010.
TRANSFORM First(table1.Exp) AS FirstOfEXP
SELECT Left([KEY],Len([KEY])-2) AS [XKEY]
FROM table1
GROUP BY Left([KEY],Len([KEY])-2)
PIVOT Right([KEY],1);
Access will throw a circular field reference error if you try to name the row heading with KEY since that is also the name of the original table field that you are deriving it from. If you do not want XKEY as the field name, then you would need to break apart the above query into two separate queries as shown below:
qsel_table1:
SELECT Left([KEY],Len([KEY])-2) AS XKEY
, Right([KEY],1) AS [Language]
, Table1.Exp
FROM Table1
ORDER BY Left([KEY],Len([KEY])-2), Right([KEY],1);
qsel_table1_Crosstab:
TRANSFORM First(qsel_table1.Exp) AS FirstOfEXP
SELECT qsel_table1.XKEY AS [KEY]
FROM qsel_table1
GROUP BY qsel_table1.XKEY
PIVOT qsel_table1.Language;
In order to always output all language columns regardless of whether there is a value or not, you need to spike of those values into a separate table. That table will then supply the row and column values for the crosstab and the original table will supply the value expression. Using the two query solution above we would instead need to do the following:
table2:
This is a new table with a BASE_KEY TEXT*255 column and a LANG TEXT*1 column. Together these two columns will define the primary key. Populate this table with the following rows:
"AbstractItemNumberReportController.SelectPositionen", "D"
"AbstractItemNumberReportController.SelectPositionen", "E"
"AbstractItemNumberReportController.SelectPositionen", "F"
"AbstractItemNumberReportController.SelectPositionen", "I"
"AbstractItemNumberReportController.SelectPositionen", "X"
qsel_table1:
This query remains unchanged.
qsel_table1_crosstab:
The new table2 is added to this query with an outer join with the original table1. The outer join will allow all rows to be returned from table2 regardless of whether there is a matching row in the table1. Table2 now supplies the values for the row and column headings.
TRANSFORM First(qsel_table1.Exp) AS FirstOfEXP
SELECT Table2.Base_KEY AS [KEY]
FROM Table2 LEFT JOIN qsel_table1 ON (Table2.BASE_KEY = qsel_table1.XKEY)
AND (Table2.LANG = qsel_table1.Language)
GROUP BY Table2.Base_KEY
PIVOT Table2.LANG;
Try something like this:
select *
from
(
select 'abcd' as [key], right([key], 1) as id, expression
from table1
) x
pivot
(
max(expression)
for id in ([D], [E])
) p
Demo Fiddle