SQL Server convert columns to rows - sql

I have a sql table with current value and previous value.
Id Value1 PValue1 Value2 PValue2
1 A A V V1
2 B B1 W W1
3 C C1 X X
I want to compare them and display in a the following table if the value has changes.
Id Column Value Pvalue
1 Value2 V V1
2 Value1 B B1
2 Value2 W W1
3 Value1 C C1
Is it possible in SQL 2008 without looping each column?

You can use a CROSS APPLY to unpivot the data:
SELECT t.id,
x.Col,
x.Value,
x.PValue
FROM YourTable t
CROSS APPLY
(
VALUES
('Value1', t.Value1, t.PValue1),
('Value2', t.Value2, t.PValue2)
) x (Col, Value, PValue)
where x.Value <> x.PValue;
See SQL Fiddle with Demo.
Just because I love using the pivot function, here is a version that uses both the unpivot and the pivot functions to get the result:
select id,
colname,
value,
pvalue
from
(
select id,
replace(col, 'P', '') colName,
substring(col, 1, PatIndex('%[0-9]%', col) -1) new_col,
val
from yourtable
unpivot
(
val
for col in (Value1, PValue1, Value2, PValue2)
) unpiv
) src
pivot
(
max(val)
for new_col in (Value, PValue)
) piv
where value <> pvalue
order by id
See SQL Fiddle with Demo

Here is an easy way:
SELECT Id,
'Value1' [Column],
Value1 Value,
PValue1 PValue
FROM YourTable
WHERE ISNULL(Value1,'') != ISNULL(PValue1,'')
UNION ALL
SELECT Id,
'Value2' [Column],
Value2 Value,
PValue2 PValue
FROM YourTable
WHERE ISNULL(Value2,'') != ISNULL(PValue2,'')

How about using a union:
SELECT * FROM
(SELECT Id, 'Value1' [Column], Value1 [Value], PValue1 [PValue]
FROM table_name
UNION ALL
SELECT Id, 'Value2' [Column], Value2 [Value], PValue2 [PValue]
FROM table_name)tmp
WHERE Value != PValue
ORDER BY Id

Finally, and for completeness, there is an UNPIVOT command. However, since you have two columns you want to unpivot, it'd probably be simpler to use one of the other solutions.

Related

Create multiple columns from existing Hive table columns

How to create multiple columns from an existing hive table. The example data would be like below.
My requirement is to create 2 new columns from existing table only when the condition met.
col1 when code=1. col2 when code=2.
expected output:
Please help in how to achieve it in Hive queries?
If you aggregate values required into arrays, then you can explode and filter only those with matching positions.
Demo:
with
my_table as (--use your table instead of this CTE
select stack(8,
'a',1,
'b',2,
'c',3,
'b1',2,
'd',4,
'c1',3,
'a1',1,
'd1',4
) as (col, code)
)
select c1.val as col1, c2.val as col2 from
(
select collect_set(case when code=1 then col else null end) as col1,
collect_set(case when code=2 then col else null end) as col2
from my_table where code in (1,2)
)s lateral view outer posexplode(col1) c1 as pos, val
lateral view outer posexplode(col2) c2 as pos, val
where c1.pos=c2.pos
Result:
col1 col2
a b
a1 b1
This approach will not work if arrays are of different size.
Another approach - calculate row_number and full join on row_number, this will work if col1 and col2 have different number of values (some values will be null):
with
my_table as (--use your table instead of this CTE
select stack(8,
'a',1,
'b',2,
'c',3,
'b1',2,
'd',4,
'c1',3,
'a1',1,
'd1',4
) as (col, code)
),
ordered as
(
select code, col, row_number() over(partition by code order by col) rn
from my_table where code in (1,2)
)
select c1.col as col1, c2.col as col2
from (select * from ordered where code=1) c1
full join
(select * from ordered where code=2) c2 on c1.rn = c2.rn
Result:
col1 col2
a b
a1 b1

add custom rows in the select statemen in sql

I have a sql statement that combines values from a row from table and the final result has one column with a computed string.Example:
select Value1 + ' and ' + Value2 +' and another string' from MyTable
and the result would be
m1 and n1 and another string
m1 and n2 and another string
How can I update my query to add a fixed string on each row after the computed value from my table. I want to have something like(the new string needs to be on a new line):
m1 and n1 and another string
test
m1 and n2 and another string
test
I'm using sql server 2014.
You need to cross apply the extra row with a values table constructor:
select v.Value
from MyTable t
cross apply (values
(Value1 + ' and ' + Value2 +' and another string'),
('test')
) as v(Value)
Each row goes inside a set of (), columns separated by , and doing it as an apply instead of join means you can access outer columns.
You'll need an ordering mechanism to guarantee reproducible order of your output. Here is one way
with cte1 (value1, value2) as
(select 'm1', 'n1'
union all
select 'm1', 'n2'),
cte2 (col_1, row_num) as
(select value1 + 'and' + value2 + 'another string', row_number() over (order by value1)
from cte1),
cte3 (col_1, row_num) as
(select col_1, row_num
from cte2
union all
select 'test', row_num + 0.5 --to sandwich the 'test' between rows
from cte2)
select col_1
from cte3
order by row_num;
If you wish to use a more compact version
select col_1
from (select *, value1 + 'and' + value2 + 'another string' as col_1
from t
union all
select *, 'test'
from t) a
order by value1, value2, case when col_1 = 'test' then 1 else 0 end asc;

SQL Server - minimum value on row

I have a table with a single row and two columns. Can I obtain the minimum value from the row using an SQL query?
value1 value2
1 43 39
The query should return the value 39.
The simplest method is probably apply:
select t.*, v.min_val
from t cross apply
(select min(val) as min_val
from (values (value1), (value2)) v(val)
) v;
For just two values that are not-null, you a case expression is also simple:
select t.*,
(case when value1 < value2 then value1 else value2 end) as min_val
from t;
However, this does not ignore null values. And it does not generalize quite as easily as one would like.
Certainly Gordon's answer (+1) is more performant and would be my first choice, but if by chance you are looking for a more "generalized" version, here is a simplified JSON approach.
This may be helpful if you have numerous or variable columns.
Example
Declare #YourTable table (ID int,value1 int,value2 int)
Insert into #YourTable values
(1,43,39)
Select A.*
,B.*
From #YourTable A
Cross Apply (
Select RowMin = min(Value)
From ( Select Value=try_convert(int,Value) -- << Set desired Datatype
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
Where [Key] not in ('ID')
) J
) B
Returns
ID value1 value2 RowMin
1 43 39 39

Sum the two lowest values from a record

I have a table like this:
I need to sum the two lowest values for each record. For example, in the first row 2 and 4 (2 + 4 = 6).
I can find the lowest value for each row using CROSS APPLY, but I can't find the two lowest values at once to sum them.
Thanks in advance.
I would do this as:
select id, sumval - maxval
from t cross apply
(select sum(val) as sumval, max(val) as maxval
from values (value1), (value2), (value3)) v(val)
) v;
If you have three items, the sum of the smallest two is the sum of all of them minus the largest.
More generally, I would use something like this:
select id, sum2
from t cross apply
(select sum(val) as sum2
from (select top (2) val
from values (value1), (value2), (value3) v(val)
order by val asc
) v
) v
SELECT
IIF (VALUE1 < VALUE3 AND VALUE2 < VALUE3,
VALUE1 + VALUE2,
IIF(VALUE1 < VALUE2 AND VALUE3 < VALUE2,
VALUE1 + VALUE3,
IIF(VALUE3 < VALUE1 AND VALUE2 < VALUE1,
VALUE2 + VALUE3, 0)))
-- You will have to decide what to do if none of the conditions are met: I set the result to zero. This gets unwieldy if you add more columns
Initial data:
DECLARE #Table TABLE (ID INT IDENTITY(1,1),Value1 INT, Value2 INT, Value3 INT);
INSERT INTO #Table (Value1,Value2,Value3) VALUES
(2,4,5)
,(3,7,2)
,(9,1,6)
;
The code:
SELECT a.ID,SUM(a.[Value]) AS [Sum]
FROM (
SELECT p.ID,p.Value
,ROW_NUMBER()OVER(PARTITION BY p.ID ORDER BY p.Value ASC) AS [rn]
FROM #Table t
UNPIVOT(Value FOR Param IN ([Value1],[Value2],[Value3])) p
) a
WHERE a.rn <= 2 /*pick up only two lowest*/
GROUP BY a.ID
;

Average of rows in SQL Server

I have the below table
Col1 Col2 Col3 Col4 Col5
TotalAvg 68.79 65.39 88.21 63.14
I am already saving the total of all columns in the TotalAvg row but now I want to calculate the Average of the TotalAvg row. Can someone please tell me how I can calculate row average.
I am looking for
Select Avg(Col2,Col3,Col4,Col5)
where Col1 = 'TotalAvg'
Thanks
If some of them may have NULL values, you could still use AVG() inside an APPLY.
SELECT
yourTable.Col1,
RowStats.avg
FROM
yourTable
CROSS APPLY
(
SELECT
AVG(x) AS avg
FROM
(
SELECT yourTable.col2 AS x
UNION ALL
SELECT yourTable.col3 AS x
UNION ALL
SELECT yourTable.col4 AS x
UNION ALL
SELECT yourTable.col5 AS x
)
pivot
)
AS rowStats
If by chance you need a more dynamic approach (i.e. variable columns), and IF you're open to a TVF, consider the following:
EDIT
The 1st parameter is a delimited list of columns to exclude. For example: 'IDNr,Year,AnyOtherNumericCol'.
Example
Select A.*
,B.*
From YourTable A
Cross Apply [dbo].[tvf-Stat-Row-Agg]('',(Select A.* for XML Raw)) B
Returns
Col1 Col2 Col3 Col4 Col5 RetCnt RetSum RetMin RetMax RetAvg RetStd
TotalAvg 68.79 65.39 88.21 63.14 4 285.53 63.14 88.21 71.3825 11.4562162892757
The TVF if Interested
CREATE FUNCTION [dbo].[tvf-Stat-Row-Agg](#Exclude varchar(500),#XML xml)
Returns Table
As
Return (
Select RetCnt = Count(Value)
,RetSum = Sum(Value)
,RetMin = Min(Value)
,RetMax = Max(Value)
,RetAvg = Avg(Value)
,RetStd = Stdev(Value)
From (
Select Item = convert(varchar(100),xAttr.query('local-name(.)'))
,Value = try_convert(float,xAttr.value('.','varchar(max)'))
From #XML.nodes('//#*') x(xAttr)
) S
Where charindex(','+S.Item+',',','+#Exclude+',')=0
);
EDIT 2
If the columns are fixed, and performance is paramount, then...
Select A.*
,B.*
From YourTable A
Cross Apply (
Select AvgVal = avg(Value)
From (values (Col2)
,(Col3)
,(Col4)
,(Col5)
) B1(Value)
) B