transform columns to rows - sql

I have a table table1 like below
+----+------+------+------+------+------+
| id | loc | val1 | val2 | val3 | val4 |
+----+------+------+------+------+------+
| 1 | loc1 | 10 | 190 | null | 20 |
| 2 | loc2 | 20 | null | 10 | 10 |
+----+------+------+------+------+------+
need to combine the val1 to val4 into a new column val with a row for each so that the output is like below.
NOTE: - I data I have has val1 to val30 -> ie. 30 columns per row that need to be converted into rows.
+----+------+--------+
| id | loc | val |
+----+------+--------+
| 1 | loc1 | 10 |
| 1 | loc1 | 190 |
| 1 | loc1 | null |
| 1 | loc1 | 20 |
| 2 | loc2 | 20 |
| 2 | loc2 | null |
| 2 | loc2 | 10 |
| 2 | loc2 | 10 |
+----+------+--------+

You can use lateral join for transform columns to rows :
SELECT a.id,a.loc,t.vals
FROM table1 a,
unnest(ARRAY[a.val1,a.val2,a.val3,a.val4]) t(vals);
If you want to this with a dynamic added columns:
CREATE OR REPLACE FUNCTION columns_to_rows(
out id integer,
out loc text,
out vals integer
)
RETURNS SETOF record AS
$body$
DECLARE
columns_to_rows text;
BEGIN
SELECT string_agg('a.'||attname, ',') into columns_to_rows
FROM pg_attribute
WHERE attrelid = 'your_table'::regclass AND --table name
attnum > 0 and --get just the visible columns
attname <> all (array [ 'id', 'loc' ]) AND --exclude some columns
NOT attisdropped ; --column is not dropped
RETURN QUERY
EXECUTE format('SELECT a.id,a.loc,t.vals
FROM your_table a,
unnest(ARRAY[%s]) t(vals)',columns_to_rows);
end;
$body$
LANGUAGE 'plpgsql'
Look at this link for more detail: Columns to rows

You could use a cross join with generate_series for this:
select
id,
loc,
case x.i
when 1 then val1
when 2 then val2
. . .
end as val
from t
cross join generate_series(1, 4) x (i)
It uses the table only once and can be easily extended to accommodate more columns.
Demo
Note: In the accepted answer, first approach reads the table many times (as many times as column to be unpivoted) and second approach is wrong as there is no UNPIVOT in postgresql.

I'm sure there's a classier approach than this.
SELECT * FROM (
select id, loc, val1 as val from #t a
UNION ALL
select id, loc, val2 as val from #t a
UNION ALL
select id, loc, val3 as val from #t a
UNION ALL
select id, loc, val4 as val from #t a
) x
order by ID
Here's my attempt with unpivot but cant get the nulls, perhaps perform a join for the nulls? Anyway i'll still try
SELECT *
FROM (
SELECT * FROM #t
) main
UNPIVOT (
new_val
FOR val IN (val1, val2, val3, val4)
) unpiv

It will not work in postgress as needed by user. Saw when it was mentioned in comments.
I am finding a way to handle "NULL"
select p.id,p.loc,CASE WHEN p.val=0 THEN NULL ELSE p.val END AS val
from
(
SELECT id,loc,ISNULL(val1,0) AS val1,ISNULL(val2,0) AS val2,ISNULL(val3,0) AS val3,ISNULL(val4,0) AS val4
FROM Table1
)T
unpivot
(
val
for locval in(val1,val2,val3,val4)
)p
Test
EDIT:
Best Solution from my Side:
select a.id,a.loc,ex.val
from (select 'val1' as [over] union all select 'val2' union all select 'val3'
union all select 'val1' ) pmu
cross join (select id,loc from Table1) as a
left join
Table1 pt
unpivot
(
[val]
for [over] in (val1, val2, val3, val4)
) ex
on pmu.[over] = ex.[over] and
a.id = ex.id
Test

Related

Postgres - Select list of records and one row with total

I have the following query:
with charges as (
select some_fields, (sum(subtotal_cents) as total from my_table
where <condition>
group by some_field
)
select <some_fields>, charges.total
from my_table
<lots_of_joins!>
left join charges on <condition>
where <condition>
Assuming the sum was 100, this gives me the following output:
field_a | field_b | total
-----------------------
val1 | val2 | 100
val3 | val4 | 100
Would it be possible to instead get something like this (or close to it):
field_a | field_b | total
-----------------------
val1 | val2 |
val3 | val4 |
total | | 100
Yes, by using a UNION and some tricky order by stuff:
with charges as (
select some_fields, (sum(subtotal_cents) as total from my_table
where <condition>
group by some_field
)
SELECT *
FROM
(
select <some_fields>, CAST(NULL AS INTEGER) AS TOTAL, 0 AS sortorder
from my_table
<lots_of_joins!>
UNION ALL
SELECT 'total' as field_a, NULL as some_fields_2, NULL as some_fields_3, ..., charges.total, 1 as sortorder
)
ORDER BY sortorder;
The important things here are:
We are using a UNION to stack the results of two queries (your query results and your CTE results)
The fields must line up between those two Union'd queries in both ordinal and type otherwise you will error out.
We add a new column for sort order, shove the whole beast into a subquery, and then ORDER BY the sortorder field so your CTE results are on the bottom of the UNION'd results.
Yes, by using GROUPING SETS. The official documentation doesn't have much in the way of examples, but let's say you have a table with three columns: a, b, and total.
A simple group by will get you this:
# SELECT a, b, SUM(total) FROM foo GROUP BY a, b;
a | b | sum
------+------+-----
val1 | val2 | 100
val3 | val4 | 100
That SQL is equivalent to SELECT a, b, SUM(total) FROM foo GROUP BY GROUPING SETS ((a, b)). You can add to that list of grouping sets to get the total overall:
# SELECT a, b, SUM(total) FROM foo GROUP BY GROUPING SETS ((a, b));
a | b | sum
------+------+-----
val1 | val2 | 100
val3 | val4 | 100
# SELECT a, b, SUM(total) FROM foo GROUP BY GROUPING SETS ((a, b), ());
a | b | sum
------+------+-----
val1 | val2 | 100
val3 | val4 | 100
| | 200
Obviously, you'll have to adjust this for your more complicated use case, but it should be fairly straightforward.
edit: I've created a sqlfiddle with my simple example case.

How to take sum of all columns in hive

Is is possible to take sum of all columns in hive table. I mean any single way to take sum
Table
col col_1 col_2 col_3
Ouptut
sum(col), sum(col_1), sum(col_2) sum(col_3)
create table mytable (i int,j int,k int);
insert into mytable values (1,2,3),(4,5,6),(7,8,9);
select pos+1 as col
,sum (val) as sum_col
from mytable t
lateral view posexplode(array(*)) pe
group by pos
;
+-----+---------+
| col | sum_col |
+-----+---------+
| 1 | 12 |
| 2 | 15 |
| 3 | 18 |
+-----+---------+
Or (So help me god)
select map_values
(
str_to_map
(
concat_ws
(
','
,sort_array
(
collect_list
(
concat_ws
(
':'
,lpad(cast(pos as string),10,'0')
,cast(sum_val as string)
)
)
)
)
)
) as sum_col_array
from (select pos
,sum (val) as sum_val
from mytable t
lateral view posexplode(array(*)) pe
group by pos
) t
;
+------------------+
| sum_col_array |
+------------------+
| ["12","15","18"] |
+------------------+

Column to rows, rows to rows in SQL Server

I have table like this
id | vname1 | vname2 | vname3
1 | vala | valb | valc
I want this to convert like this
id | vname | vals
1 | vname1 | vala
1 | vname2 | valb
1 | vname3 | valc
I thought about pivoting but here I think is not the case
Do a UNION ALL, with one SELECT for each vname column:
select id, 'vname1' as vname, vname1 as vals from tablename
union all
select id, 'vname2' as vname, vname2 as vals from tablename
union all
select id, 'vname3' as vname, vname3 as vals from tablename
You can use the UNPIVOT function to convert the columns into rows:
Sample Example:
select Id,
indicatorname,
from yourtable
unpivot
(
indicatorvalue
for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;
Link for reference: UnPivot

SQL: Pick highest and lowest value (int) from one row

I am looking for a way to pick the highest and lowest value (integer) from a single row in table. There are 4 columns that i need to compare together and get highest and lowest number there is.
The table looks something like this...
id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4
1 | John | 5 | 5 | 2 | 1
2 | Peter | 3 | 2 | 4 | 1
3 | Josh | 3 | 5 | 1 | 3
Can you help me, please? Thanks!
You can do this using CROSS APPLY and the VALUES clause. Use VALUES to group all your compared columns and then select the max.
SELECT
MAX(d.data1) as MaxOfColumns
,MIN(d.data1) as MinOfColumns
,a.id
,a.name
FROM YOURTABLE as a
CROSS APPLY (
VALUES(a.col_to_compare1)
,(a.col_to_compare2)
,(a. col_to_compare3)
,(a.col_to_compare4)
,(a. col_to_compare5)
) as d(data1) --Name the Column
GROUP BY a.id
,a.name
Assuming you are looking for min/max per row
Declare #YourTable table (id int,name varchar(50),col_to_compare1 int,col_to_compare2 int,col_to_compare3 int,col_to_compare4 int)
Insert Into #YourTable values
(1,'John',5,5,2,1),
(2,'Peter',3,2,4,1),
(3,'Josh',3,5,1,3)
Select A.ID
,A.Name
,MinVal = min(B.N)
,MaxVal = max(B.N)
From #YourTable A
Cross Apply (Select N From (values(a.col_to_compare1),(a.col_to_compare2),(a.col_to_compare3),(a.col_to_compare4)) N(N) ) B
Group By A.ID,A.Name
Returns
ID Name MinVal MaxVal
1 John 1 5
3 Josh 1 5
2 Peter 1 4
These solutions keep the current rows and add additional columns of min/max.
select *
from t cross apply
(select min(col) as min_col
,max(col) as max_col
from (
values
(t.col_to_compare1)
,(t.col_to_compare2)
,(t.col_to_compare3)
,(t.col_to_compare4)
) c(col)
) c
OR
select *
,cast ('' as xml).value ('min ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as min_col
,cast ('' as xml).value ('max ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as max_col
from t
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4 | min_col | max_col |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 1 | John | 5 | 5 | 2 | 1 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 2 | Peter | 3 | 2 | 4 | 1 | 1 | 4 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 3 | Josh | 3 | 5 | 1 | 3 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
A way to do this is to "break" apart the data
declare #table table (id int, name varchar(10), col1 int, col2 int, col3 int, col4 int)
insert into #table values (1 , 'John' , 5 , 5 , 2 , 1)
insert into #table values (2 , 'Peter' , 3 , 2 , 4 , 1)
insert into #table values (3 , 'Josh' , 3 , 5 , 1 , 3)
;with stretch as
(
select id, col1 as col from #table
union all
select id, col2 as col from #table
union all
select id, col3 as col from #table
union all
select id, col4 as col from #table
)
select
t.id,
t.name,
agg.MinCol,
agg.MaxCol
from #table t
inner join
(
select
id, min(col) as MinCol, max(col) as MaxCol
from stretch
group by id
) agg
on t.id = agg.id
Seems simple enough
SELECT min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table
Gives you the Min and Max for each column.
Following OP's comment, I believe he may be looking for a min/max grouped by the person being queried against.
So that would be:
SELECT name, min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table GROUP BY name

SQL rank/dense_rank and how to query/calculate with the result

So I have a table where it dense_ranks my rows.
Here is the table:
COL1 | COL2 | COL3 | DENSE_RANK |
a | b | c | 1 |
a | s | r | 1 |
a | w | f | 1 |
b | b | c | 2 |
c | f | r | 3 |
c | q | d | 3 |
So now I want to select any rows where the rank was only represented once, so the 2 is all alone, but not the 1 or 3. I want to select all the rows where this occurs, but how do I do that?
Some ideas:
-COUNT DISTINCT (RANK())
-COUNT RANK()
but neither of those are working, any ideas? please and thank you!
happy hacking
actual code:
SELECT events.event_type AS "event",
DENSE_RANK() OVER (ORDER BY bw_user_event.pad_id) as rank
FROM user_event
WHERE (software_events.software_id = '8' OR software_events.software_id = '14')
AND (software_events.event_type = 'install')
WITH Dense_ranked_table as (
-- Your select query that generates the table with dense ranks
)
SELECT DENSE_RANK
FROM Dense_ranked_table
GROUP BY DENSE_RANK
HAVING COUNT(DENSE_RANK) = 1;
I don't have SQL Server to test this. So please let me know whether this works or not.
I would think you can add a COUNT(*) OVER (PARTITION BY XXXXX) where XXXXX is what you include in your dense rank.
Then wrap this in a Common Table Expression and select where your new Count is = 1.
Something like this fiddler:
http://sqlfiddle.com/#!6/ae774/1
Code included here as well:
CREATE TABLE T
(
COL1 CHAR,
COL2 CHAR,
COL3 CHAR
);
INSERT INTO T
VALUES
('a','b','c'),
('a','s','r'),
('a','w','f'),
('b','b','c'),
('c','f','r'),
('c','q','d');
WITH CTE AS (
SELECT COL1 ,
COL2 ,
COL3,
DENSE_RANK() OVER (ORDER BY COL1) AS DR,
COUNT(*) OVER (PARTITION BY COL1) AS C
FROM dbo.T AS t
)
SELECT COL1, COL2, COL3, DR
FROM CTE
WHERE C = 1
Would return just the
b, b, c, 2
row from your test data.