selecting top k column values in a row in postgresql - postgresql

I have below table structure
CREATE TABLE myTable (
id1 INTEGER,
key1 VARCHAR,
key2 VARCHAR,
key3 VARCHAR,
key4 VARCHAR,
key5 VARCHAR,
val1 DOUBLE PRECISION,
val2 DOUBLE PRECISION,
val3 DOUBLE PRECISION,
val4 DOUBLE PRECISION,
val5 DOUBLE PRECISION
);
I am trying to write query to select top 3 val columns and their corresponding keys amongst given 5 key-val pairs.
something like
id1, key1, val1, key2, val2, key3, val3.
where val1, val2 and val3 are top3 amongst all five values.
I was able to write it for top value and key like below
SELECT CASE WHEN val1 = GREATEST(val1, val2, val3, val4, val5) THEN key1
WHEN val2 = .... THEN val2
.....
END AS top_key
,GREATEST(val1, val2, val3, val4, val5) AS top_val
FROM myTable
WHERE id1 = ?
but struggling to do it for top k columns !
Is there any custom function to find top k elements out of given elements ?
any other direct way to do this ?
Thanks in Anticipation !

Try this. It basically makes a "temporary" table of (key, value) pairs for every row and then selects top three of the pairs for every row.
SELECT t.id,
(array_agg((a.k, a.v) ORDER BY a.v DESC))[1] AS kv1,
(array_agg((a.k, a.v) ORDER BY a.v DESC))[2] AS kv2,
(array_agg((a.k, a.v) ORDER BY a.v DESC))[3] AS kv3
FROM t
CROSS JOIN LATERAL (
SELECT * FROM (VALUES (t.k1, t.v1), (t.k2, t.v2), (t.k3, t.v3), (t.k4, t.v4), (t.k5, t.v5)) AS a(k, v)
) AS a
GROUP BY t.id
Fiddle here: http://sqlfiddle.com/#!17/6b0c5/2

Related

Error when duplicating rows and changing a value in SQL

I know there are similar questions on SO but I wasn't able to find my exact use case. I am trying to duplicate some rows in a PostgreSQL table and change one value in them. For example, I have a table with three values and I want to grab a selection of rows based on the third value and copy them, then change the third value. Here's what I have so far:
INSERT INTO my_table ( val1, val2, val3 )
VALUES
(
( SELECT val1 FROM my_table WHERE val3 = '1' LIMIT 2, ( SELECT val2 FROM my_table WHERE val3 = '1' LIMIT 2), '2' )
I am getting an error that says "more than one row returned by a subquery used as an expression". Also, I would like to be able to test the solution with just a couple rows, so ideally I would be able to limit the SELECT statements to just a couple. Is this possible?
The VALUES keyword is used when you're inserting manual values into the table. If you want to insert based on a query, you skip that.
INSERT INTO my_table (val1, val2, val3)
SELECT val1, val2, '2'
FROM my_table
WHERE val3 = '1'
LIMIT 2;

Get distinct values from the values of a certain column with some formatting

I have a table in pgadmin (old verion pgadmin3) of the following format. Please note the commas at the beginning and the end of the values
my_table
id column_name
1 , val1,
2 , val1, val2,
3 , val2, val4, val5,
id
column_name
1
, val1,
2
, val1, val2,
3
, val2, val4, val5,
I would like to get the following output
expected outcome
new_id column_name
1 val1
2 val2
3 val4
4 val5
new_id
column_name
1
val1
2
val2
3
val4
3
val5
So basically I want the output to be of distinct values.
I have been trying a different scripts with no success. (Please again note the commas at the beginning and at the end of the strings in that column. They need to be removed too in the process)
Can you please help me on this.
Thank you,
Shaun
Split the string and discard the empty strings. Then, for each value, take the lowest matching id.
SELECT DISTINCT ON (c.c)
m.id, c.c
FROM mytab AS m
CROSS JOIN LATERAL regexp_split_to_table(m.column_name, ', *') AS c(c)
WHERE c.c <> ''
ORDER BY c.c, m.id;
id | c
----+------
1 | val1
2 | val2
3 | val4
3 | val5
(4 rows)
First normalize the data by splitting my_column into rows (this is the t CTE) and then select distinct values. trim function is used twice to clean the commas and spaces from the list string.
with t as
(
select id, l.list_item
from my_table m
cross join lateral
(
select trim(s) as list_item
from unnest(string_to_array(trim(m.column_name, ', '), ',')) s
) l
)
select distinct on (list_item) id, list_item as column_name
from t
order by list_item, id;

Transpose on row with multiple (dynamic) columns to several rows (number of original columns) with two columns

I have a recordset (one row only):
Col1 Col2 ... Coln
Val1 Val2 ... Valn
I need to transpose it:
Field Value
Col1 Val1
Col2 Val2
... ...
Coln Valn
Can someone help me?
Thanks
Something like:
SELECT Field = 'Col1', Value = Val1
FROM TableA
UNION ALL
SELECT Field = 'Col2', Value = Val2
FROM TableA
UNION ALL
...

CONCATENATION, smallint, integer into an integer column

DB2 V9 z/os
Background: I have a 4 column table defined as (col1 int, col2 smallint, col3 int, col4 date)
Row 1 has values of (1,123,456,2012-08-23)
When I execute the following:
SELECT CAST(col2 AS VARCHAR(5)) CONCAT CAST(col3 AS VARCHAR(5))
FROM db.T1
WHERE col1 = 1;
Value 123456 is returned, which is exactly what i want.
When I execute the following:
UPDATE db.table2
SET col3 = SELECT CAST(col2 AS VARCHAR(5)) CONCAT CAST(col3 AS VARCHAR(5))
FROM db.T1
WHERE col1 = 1;
Error is:
SQL0408N A value is not compatible with the data type of its assignment target. Target name is "col3". SQLSTATE=42821
I understand the error is due to attempting to insert a varchar into an integer. What else can I do? I've tried using various CAST statements but cannot get a value to insert into col3. i need the value to appear joined as shown above.
Any help would be appreciated.
Wrapping all of the casts as a final cast( ... as integer) should work:
UPDATE db.table2
SET col3 = SELECT CAST(
CAST(col2 AS VARCHAR(5)) CONCAT CAST(col3 AS VARCHAR(5))
AS INTEGER)
FROM db.T1
WHERE col1 = 1;
depend on the MAXIMUM value of your small integers,
you can convert them to base-9,
then concatenate with '9'.
CONCAT( IFNULL(CONV(sint1, 10, 9),''),
'9',
IFNULL(CONV(tint2, 10, 9),'')
) AS combined
and another option, ALSO depend on the MAXIMUM value of your small integers,
to PAD-LEFT with zeros one of them.
CONCAT(
sint1,
LPAD(tint2,3,0)
) AS combined
See full code and explanation: https://mdb-blog.blogspot.com/2021/10/mysql-joincombine-2-smallinttinyint.html

T-SQL -- convert comma-delimited column into multiple columns

From the table below, how can I convert the Values column into multiple columns, populated with individual values that are currently separated by commas? Before the conversion:
Name Values
---- ------
John val,val2,val3
Peter val5,val7,val9,val14
Lesli val8,val34,val36,val65,val71,val
Amy val3,val5,val99
The result of the conversion should look like:
Name Col1 Col2 Col3 Col4 Col5 Col6
---- ---- ---- ---- ---- ---- ----
John val val2 val3
Peter val5 val7 val9 val14
Lesli val8 val34 val36 val65 val71 val
Amy val3 val5 val99
First, what database product and version are you using? If you are using SQL Server 2005 and later, you can write a Split user-defined function like so:
CREATE FUNCTION [dbo].[Split]
(
#DelimitedList nvarchar(max)
, #Delimiter varchar(2) = ','
)
RETURNS TABLE
AS
RETURN
(
With CorrectedList As
(
Select Case When Left(#DelimitedList, DataLength(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
+ #DelimitedList
+ Case When Right(#DelimitedList, DataLength(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
As List
, DataLength(#Delimiter) As DelimiterLen
)
, Numbers As
(
Select TOP (Coalesce(Len(#DelimitedList),1)) Row_Number() Over ( Order By c1.object_id ) As Value
From sys.objects As c1
Cross Join sys.columns As c2
)
Select CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen As Position
, Substring (
CL.List
, CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen
, CharIndex(#Delimiter, CL.list, N.Value + 1)
- ( CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen )
) As Value
From CorrectedList As CL
Cross Join Numbers As N
Where N.Value < Len(CL.List)
And Substring(CL.List, N.Value, CL.DelimiterLen) = #Delimiter
)
You can then split out the values in you want using something akin to:
Select Name, Values
From Table1 As T1
Where Exists (
Select 1
From Table2 As T2
Cross Apply dbo.Split (T1.Values, ',') As T1Values
Cross Apply dbo.Split (T2.Values, ',') As T2Values
Where T2.Values.Value = T1Values.Value
And T1.Name = T2.Name
)
Here is a solution that uses a recursive cte to generate a "table of numbers" (courtesy of Itzik Ben-Gan), which is useful for all manner of problems including string splitting, and PIVOT. SQL Server 2005 onwards. Full table create, insert and select script included.
CREATE TABLE dbo.Table1
(
Name VARCHAR(30),
[Values] VARCHAR(128)
)
GO
INSERT INTO dbo.Table1 VALUES ('John', 'val,val2,val3')
INSERT INTO dbo.Table1 VALUES ('Peter', 'val5,val7,val9,val14')
INSERT INTO dbo.Table1 VALUES ('Lesli', 'val8,val34,val36,val65,val71,val')
INSERT INTO dbo.Table1 VALUES ('Amy', 'val3,val5,val99')
GO
SELECT * FROM dbo.Table1;
GO
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
Numbers AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L3)
SELECT Name, [1] AS Column1, [2] AS Column2, [3] AS Column3, [4] AS Column4, [5] AS Column5, [6] AS Column6, [7] AS Column7
FROM
(SELECT Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY nums.n) AS PositionInList,
LTRIM(RTRIM(SUBSTRING(valueTable.[Values], nums.n, charindex(N',', valueTable.[Values] + N',', nums.n) - nums.n))) AS [Value]
FROM Numbers AS nums INNER JOIN dbo.Table1 AS valueTable ON nums.n <= CONVERT(int, LEN(valueTable.[Values])) AND SUBSTRING(N',' + valueTable.[Values], n, 1) = N',') AS SourceTable
PIVOT
(
MAX([VALUE]) FOR PositionInList IN ([1], [2], [3], [4], [5], [6], [7])
) AS Table2
GO
--DROP TABLE dbo.Table1
Which converts this output
Name Values
John val,val2,val3
Peter val5,val7,val9,val14
Lesli val8,val34,val36,val65,val71,val
Amy val3,val5,val99
to
Name Column1 Column2 Column3 Column4 Column5 Column6 Column7
Amy val3 val5 val99 NULL NULL NULL NULL
John val val2 val3 NULL NULL NULL NULL
Lesli val8 val34 val36 val65 val71 val NULL
Peter val5 val7 val9 val14 NULL NULL NULL