Concatenate the result of a query into a variable in PostgreSQL - postgresql

Is it possible to concatenate the result of a query into a variable in postgresql?
Something like this in MSSQL:
DECLARE #Names_tmp NVARCHAR(max);
select #Names_tmp =
COALESCE(#Names_tmp + ' UNION ALL ', '') +
FromTable.Name
from FromTable
FromTable structure:
Key Name Other Columns ...
1 name_1 asd
2 name_2 asd
3 name_3 asd
PRINT CAST(#Names_tmp AS NTEXT)
result:
name_1 UNION ALL name_2 UNION ALL name 3

I see no need to use plpgsql for this matter. PostgreSQL aggregate functions should do it:
CREATE TEMPORARY TABLE t (id INT, name TEXT, asd TEXT);
INSERT INTO t VALUES (1,'name_1','asd'),
(2,'name_2','asd'),
(3,'name_3','asd');
SELECT ARRAY_TO_STRING(ARRAY_AGG(name),' UNION ALL ') FROM t;
SELECT STRING_AGG(name, ' UNION ALL ') FROM t;
Result:
------------------------------------------
name_1 UNION ALL name_2 UNION ALL name_3
(1 Zeile)

Use STRING_AGG
[SQL Fiddle][1]
Query 1:
select string_agg(name,' UNION ALL ') as res from t
Results:
| res |
|------------------------------------------|
| name_1 UNION ALL name_2 UNION ALL name_3 |

Related

Aggregating distinct values from JSONB arrays combined with SQL group by

I am trying to aggregate distinct values from JSONB arrays in a SQL GROUP BY statement:
One dataset has many cfiles and a cfile only ever has one dataset
SELECT * FROM cfiles;
id | dataset_id | property_values (jsonb)
----+------------+-----------------------------------------------
1 | 1 | {"Sample Names": ["SampA", "SampB", "SampC"]}
2 | 1 | {"Sample Names": ["SampA", "SampB", "SampD"]}
3 | 1 | {"Sample Names": ["SampE"]}
4 | 2 | {"Sample Names": ["SampA", "SampF"]}
5 | 2 | {"Sample Names": ["SampG"]}
This query works and returns the correct result I want but it's a mess.
SELECT distinct(datasets.id) as dataset_id,
ARRAY_TO_STRING(
ARRAY(
SELECT DISTINCT * FROM unnest(
STRING_TO_ARRAY(
STRING_AGG(
DISTINCT REPLACE(
REPLACE(
REPLACE(
REPLACE(
cfiles.property_values ->> 'Sample Names', '",' || chr(32) || '"', ';'
), '[' , ''
), '"' , ''
), ']' , ''
), ';'
), ';'
)
) ORDER BY 1 ASC
), '; '
) as sample_names
FROM datasets
JOIN cfiles ON cfiles.dataset_id=datasets.id
GROUP BY datasets.id
dataset_id | sample_names
------------+-----------------------------------
1 | SampA; SampB; SampC; SampD; SampE
2 | SampA; SampF; SampG
Is there a better way to write this query without all the string manipulation?
I tired jsonb_array_elements but it gave me the error subquery uses ungrouped column "cfiles.property_values" from outer query. So then I added cfiles.property_values to the GROUP BY but it no longer grouped just by the dataset_id
Not the result I want:
SELECT DISTINCT datasets.id as dataset_id,
ARRAY_TO_STRING(
ARRAY(
SELECT DISTINCT * FROM jsonb_array_elements(
cfiles.property_values -> 'Sample Names'
) ORDER BY 1 ASC
), '; '
) as sample_names
FROM datasets
JOIN cfiles ON cfiles.dataset_id=datasets.id
GROUP BY datasets.id, cfiles.property_values
dataset_id | sample_names
------------+---------------------------
1 | "SampA"; "SampB"; "SampC"
1 | "SampA"; "SampB"; "SampD"
1 | "SampE"
2 | "SampA"; "SampF"
2 | "SampG"
SQL for creating demo
CREATE TABLE datasets (
id INT PRIMARY KEY
);
CREATE TABLE cfiles (
id INT PRIMARY KEY,
dataset_id INT,
property_values JSONB,
FOREIGN KEY (dataset_id) REFERENCES datasets(id)
);
INSERT INTO datasets values (1),(2);
INSERT INTO cfiles values
(1,1,'{"Sample Names":["SampA", "SampB", "SampC"]}'),
(2,1,'{"Sample Names":["SampA", "SampB", "SampD"]}'),
(3,1,'{"Sample Names":["SampE"]}');
INSERT INTO cfiles values
(4,2,'{"Sample Names":["SampA", "SampF"]}'),
(5,2,'{"Sample Names":["SampG"]}');
jsonb_array_elements is a set returning function and should be used in the FROM clause. Using it in the SELECT list makes things unnecessarily complicated:
select c.dataset_id, string_agg(distinct n.name, '; ' order by n.name)
from cfiles c
cross join jsonb_array_elements_text(c.property_values -> 'Sample Names') as n(name)
group by c.dataset_id
order by c.dataset_id;
Online example

Postgres find max column value from multiple tables

I have list of tables that have specific column names like
SELECT table_name
FROM information_schema.columns
WHERE column_name = 'column1'
I need to find the max value of column1 for each tables. I expect result like the following
|--------|--------------|
| Table | Max column1 |
|--------|--------------|
| Table1 | 100 |
| Table2 | 200 |
| ... | ... |
|--------|--------------|
How can I construct a query?
You can use a variation of the row count for all tables approach:
select t.table_name,
(xpath('/row/max/text()', xmax))[1]::text::int
from (
SELECT table_name, data_type,
query_to_xml(format('select max(%I) from %I.%I', column_name, table_schema, table_name), true, true, '') as xmax
FROM information_schema.columns
WHERE column_name = 'column1'
and table_schema = 'public'
) as t;
query_to_xml() runs a select max(..) from .. for each column returned from the query. The result of that is something like:
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<max>42</max>
</row>
The xpath() function is then used to extract the value from the XML. The derived table (sub-query) is not really needed, but makes the xpath() expression more readable (in my opinion).
You may create a generic function that returns a TABLE type by constructing a UNION ALL query from information_schema.columns
CREATE OR REPLACE FUNCTION public.get_max(TEXT )
RETURNS TABLE(t_table_name TEXT, t_max text )
LANGUAGE plpgsql
AS $BODY$
DECLARE
p_colname TEXT := $1;
v_sql_statement TEXT;
BEGIN
SELECT STRING_AGG( 'SELECT '''||table_name||''','||' MAX('
|| column_name||'::text'
|| ') FROM '
|| table_name
,' UNION ALL ' ) INTO v_sql_statement
FROM information_schema.columns
WHERE column_name = p_colname
--and table_schema = 'public';
IF v_sql_statement IS NOT NULL THEN
RETURN QUERY EXECUTE v_sql_statement;
END IF;
END
$BODY$;
Execute and get the results like this.
knayak=# select * FROM get_max('id');
t_table_name | t_max
--------------+-------
f | 2
t2 | 1
friends | id4
person | 2
customer |
employe |
diary | 4
jsontable | 6
atable |
t_json | 2
ingredients | 1
test | 2
accts |
mytable | 30
(14 rows)

Concatenating NULL returns a value in T-SQL (CONCAT function)

Why does this return . when col1 contains a blank value?
CONCAT(NULLIF([COL1],''),'.')
I have 3 columns that i need to concatenate with a . in between, sometimes the column contains a blank value. In that case the trailing . should not be concatenated. What functions do I use?
col1 col2 col3
A 1 x
B 2
expected results:
A.1.X
B.2
test code:
DECLARE #tbl TABLE(a varchar(100),b varchar(100),c varchar(100))
INSERT INTO #tbl
SELECT 'A','1','X' UNION
SELECT 'B','2','' UNION
SELECT 'C','','' UNION
SELECT '','1','X' UNION
SELECT 'B','','' UNION
SELECT 'C','',''
SELECT CONCAT ( Nullif(a,''),'.' + nullif(b,''), '.' + nullif(c,'')) AS Contact_Result FROM #tbl;
You can use SQL CONCAT this way
SELECT CONCAT ( a,IIF((NULLIF(a,'')+NULLIF(b,'')) IS NULL,'','.'),b,IIF((NULLIF(b,'')+NULLIF(c,'')) IS NULL,'','.'), c) AS Contact_Result FROM #tbl;
Test code below
DECLARE #tbl TABLE(a varchar(100),b varchar(100),c varchar(100))
INSERT INTO #tbl
SELECT 'A','1','X' UNION
SELECT 'B','2',NULL UNION
SELECT 'C',NULL,NULL
SELECT CONCAT ( a,IIF((NULLIF(a,'')+NULLIF(b,'')) IS NULL,'','.'),b,IIF((NULLIF(b,'')+NULLIF(c,'')) IS NULL,'','.'), c) AS Contact_Result FROM #tbl;
Contact_Result
A.1.X
B.2
C
Another common use of this kind of concats is to Concat a Full Name in this case the . (dot) is replaced by a ' ' (space), it makes things easier because you can use trim
DECLARE #tbl TABLE(a varchar(100),b varchar(100),c varchar(100))
INSERT INTO #tbl
SELECT 'FirtName','MiddleName','LastName' UNION
SELECT 'FistName','','LastName' UNION
SELECT '','','FullName'
SELECT LTRIM(CONCAT ( a,' ' + b,' ' + c)) AS Contact_Result FROM #tbl;
Result
FullName
FirtName MiddleName LastName
FistName LastName
THIS IS AN ANSWER THAT COVERS ALL POSSIBILITIES
SELECT SUBSTRING(CONCAT ('.' + NULLIF(a,''),'.' + NULLIF(b,''),'.' + NULLIF(c,'')),2,10000) AS Contact_Result FROM #tbl;
Complete test cases
DECLARE #tbl TABLE(a varchar(100),b varchar(100),c varchar(100))
INSERT INTO #tbl
SELECT 'a','','' UNION
SELECT 'a','b','' UNION
SELECT 'a','b','c' UNION
SELECT 'a','','c' UNION
SELECT '','b','c' UNION
SELECT '','b','' UNION
SELECT '','','c' UNION
SELECT '','',''
SELECT SUBSTRING(CONCAT ('.' + NULLIF(a,''),'.' + NULLIF(b,''),'.' + NULLIF(c,'')),2,10000) AS Contact_Result FROM #tbl;
Results
c
b
b.c
a
a.c
a.b
a.b.c
You'll have to go old school. I'm on my phone and can't test this.
ISNULL(NULLIF([COL1],'') + '.', '') +
ISNULL(NULLIF([COL2],'') + '.', '') +
ISNULL(NULLIF([COL3],''), '');
---------------- EDIT ----------------
Okay, this was not as easy on my phone as I thought. Here's my updated solution that works with SQL Server 2005+
-- sample data
declare #table table
(
id int identity,
col1 varchar(10),
col2 varchar(10),
col3 varchar(10)
);
insert #table (col1, col2, col3)
values ('a','b','c'), ('aa','bb',''), ('x','','z'), ('','p','pp'),
('!!!','',''), ('','','fff'), ('','','');
-- My solution
select col1, col2, col3, concatinatedValue =
case when cv like '.%' then stuff(cv, 1, 1,'') else cv end
from #table
cross apply (values
(isnull(nullif([col1],''), '') +
isnull('.'+nullif([col2],''), '') +
isnull('.'+nullif([col3],''), ''))) v(cv);
RETURNS
cv col1 col2 col3 concatinatedValue
--------- ----- ----- ----- -------------------
a.b.c a b c a.b.c
aa.bb aa bb aa.bb
x.z x z x.z
.p.pp p pp p.pp
!!! !!! !!!
.fff fff fff
<----------- blank line ----------->

Microsoft TSQL change text row to column

I want to change the 20 rows with one column to 1 row with 20 columns to insert it later in a second database
Name
----------
- Frank
- Dora
- ...
- Michael
to
Name1 | Name2 | ... | Name20
Frank | Dora | ... | Michael
I tried
SELECT *
FROM (SELECT TOP 20 firstname AS NAME
FROM database) AS d
PIVOT (Min(NAME)
FOR NAME IN (name1,
name2,
name3,
name4,
name5,
name6,
name7,
name8,
name9,
name10,
name11,
name12,
name13,
name14,
name15,
name16,
name18,
name19,
name20) ) AS f
But all names are NULL. DEMO
You were close... But your inner select must carry the new column name. Try it like this:
DECLARE #tbl TABLE(Name VARCHAR(100));
INSERT INTO #tbl VALUES('Frank'),('Dora'),('Michael');
SELECT p.*
FROM
(
SELECT 'Name' + CAST(ROW_NUMBER() OVER(ORDER BY Name) AS VARCHAR(150)) AS ColumnName
,Name
From #tbl
) AS tbl
PIVOT
(
MIN(Name) FOR ColumnName IN(Name1,Name2,Name3)
) AS p

How to get all combinations of comma separated values in a T-SQL query

I have table with three columns: column 2 and 3 contains comma-separated values.
-col1----col2---col3--
| 1 | 1,2,3 | 4,5 |
----------------------
What is the most efficient way to get a table of three columns that contains all the combinations of values of these three columns, like this:
1 | 1 | 4
1 | 2 | 4
1 | 3 | 4
1 | 1 | 5
1 | 2 | 5
1 | 3 | 5
Using query and nodes:
DECLARE #t TABLE (col1 VARCHAR(100), col2 VARCHAR(100), col3 VARCHAR(100))
INSERT #t VALUES ('1', '1,2,3', '4,5')
;WITH cte AS
(
SELECT
col1 = CAST('<x>' + REPLACE(col1, ',','</x><x>') + '</x>' AS XML),
col2 = CAST('<x>' + REPLACE(col2, ',','</x><x>') + '</x>' AS XML),
col3 = CAST('<x>' + REPLACE(col3, ',','</x><x>') + '</x>' AS XML)
FROM #t
)
SELECT
col1.n.query('.[1]').value('.', 'int'),
col2.n.query('.[1]').value('.', 'int'),
col3.n.query('.[1]').value('.', 'int')
FROM
cte
CROSS APPLY col1.nodes('x') AS col1(n)
CROSS APPLY col2.nodes('x') AS col2(n)
CROSS APPLY col3.nodes('x') AS col3(n)
SQL Fiddle
Try this:
DECLARE #T1 TABLE (COL1 VARCHAR(25), COL2 VARCHAR(25), COL3 VARCHAR(25))
INSERT INTO #T1 (COL1,COL2,COL3)
VALUES ('1','1,2,3','4,5')
DECLARE #COL1 TABLE (VAL1 VARCHAR(25))
DECLARE #COL2 TABLE (VAL2 VARCHAR(25))
DECLARE #COL3 TABLE (VAL3 VARCHAR(25))
INSERT INTO #COL1 (VAL1)
SELECT DISTINCT Split.a.value('.', 'VARCHAR(max)') AS String
FROM (SELECT CAST ('<M>' + REPLACE(CAST(COL1 AS VARCHAR), ',', '</M><M>') + '</M>' AS XML) AS String
FROM #t1) AS A
CROSS APPLY String.nodes ('/M') AS Split(a)
INSERT INTO #COL2 (VAL2)
SELECT DISTINCT Split.a.value('.', 'VARCHAR(max)') AS String
FROM (SELECT CAST ('<M>' + REPLACE(CAST(COL2 AS VARCHAR), ',', '</M><M>') + '</M>' AS XML) AS String
FROM #t1) AS A
CROSS APPLY String.nodes ('/M') AS Split(a)
INSERT INTO #COL3 (VAL3)
SELECT DISTINCT Split.a.value('.', 'VARCHAR(max)') AS String
FROM (SELECT CAST ('<M>' + REPLACE(CAST(COL3 AS VARCHAR), ',', '</M><M>') + '</M>' AS XML) AS String
FROM #t1) AS A
CROSS APPLY String.nodes ('/M') AS Split(a)
SELECT *
FROM #COL1
CROSS APPLY #COL2
CROSS APPLY #COL3
ORDER BY VAL1,VAL2,VAL3