Creating columns from rows - sql

I have this table
ID colname F1 F2 F3 F4
1 P1 1 2 3 4
1 P2 5 6 7 8
1 P3 9 10 11 12
1 P4 13 14 15 16
2 P1 17 18 19 20
2 P2 21 22 23 24
2 P3 25 26 27 28
2 P4 29 30 31 32
I am trying to produce this result Pn value corresponds to Fn
ID P1 P2 P3 P4
1 1 6 11 16
2 17 22 27 32
can you please give me some hints and keywords on how it could be done with in SQL Server?
i've been playing with Pivot but is it the way to go?

Well you can do something like this:
select
id,
max(case when colname = 'P1' then F1 end) as P1,
max(case when colname = 'P2' then F2 end) as P2,
max(case when colname = 'P3' then F3 end) as P3,
max(case when colname = 'P4' then F4 end) as P4
from Table1
group by id
sql fiddle demo

If you want a solution that uses PIVOT:
SELECT *
FROM (
SELECT ID,colname,value
FROM MyTable
UNPIVOT (value FOR col in ([F1],[F2],[F3],[F4])) a
WHERE REPLACE(col,'F','P') = colname
) b
PIVOT (MAX(value) FOR colname in ([P1],[P2],[P3],[P4])) c
The UNPIVOT followed PIVOT method is extremely versatile for transforms, but it's usually easier and more readable to do it manually as in Roman's example.

Related

SQL Recursive Query on hirachical name

New to MS SQL.
Having the table like as follows,
ID Name Parent ID
1 AA 0
2 BB 1
3 CC 2
4 DD 3
5 EE 3
6 FF 4
7 FG 6
8 AD 1
9 AC 2
My expected Result is showed in the Hierarchical Name
ID Name Parent ID Hirarchical Name
1 AA 0 AA
2 BB 1 AA.BB
3 CC 2 AA.BB.CC
4 DD 3 AA.BB.CC.DD
5 EE 3 AA.BB.CC.EE
6 FF 4 AA.BB.CC.DD.FF
7 FG 6 AA.BB.CC.DD.FF.FG
8 AD 1 AA.AD
9 AC 2 AA.BB.AC
Thanks for your help in advance.
Recursive CTEs are a bit tricky. This does what you want:
with cte as (
select id, name, convert(varchar(max), name) as hier, 1 as lev
from t
where parentid = 0
union all
select t.id, t.name, cte.hier + '.' + t.name, lev + 1
from cte join
t
on t.parentid = cte.id
where lev < 10
)
select *
from cte;
Here is a db<>fiddle.

Average across multiple columns with varying data

I have 10 decimal columns and I would like to add a computed column to my table that contains the average of these 10. A complication is that not every record has all 10 columns filled in. Some records have 4 some have 8 and some have 10.
e.g.
ID D1 D2 D3 D4 D5 D6 D7 D8 D9 D10
1 12 19 13 14
2 32 53 34 54 65 34 12 09
3 41 54 33 61 71 12 09 08 08 12
How can I get the average of these where ID1 = 14.5, ID2 = 36.625 etc
I can't just do D1 + D2 + D3... / 10 as the 10 isn't always 10
The ideal would just be to do AVG(D1:D10) but clearly the world isn't ideal!
You can't use AVG aggregate function (because it works on rows) but you can calculate an average using the following query:
SELECT
(ISNULL(D1,0) + ISNULL(D2,0) +
ISNULL(D3,0) + ISNULL(D4,0) + ISNULL(D5,0) +
ISNULL(D6,0) + ISNULL(D7,0) + ISNULL(D8,0) +
ISNULL(D9,0) + ISNULL(D10,0)) /
CASE
WHEN
D1 IS NOT NULL
OR D2 IS NOT NULL
OR D3 IS NOT NULL
OR D4 IS NOT NULL
OR D5 IS NOT NULL
OR D6 IS NOT NULL
OR D7 IS NOT NULL
OR D8 IS NOT NULL
OR D9 IS NOT NULL
OR D10 IS NOT NULL
THEN
(
CASE
WHEN D1 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D2 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D3 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D4 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D5 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D6 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D7 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D8 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D9 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D10 IS NOT NULL THEN 1 ELSE 0
END
)
ELSE 1
END
FROM yourtable
AVG for each id:
select id, avg(d) from
(
select id, id1 as d from tablename
union all
select id, id2 as d from tablename
union all
select id, id3 as d from tablename
union all
select id, id4 as d from tablename
union all
select id, id5 as d from tablename
union all
select id, id6 as d from tablename
union all
select id, id7 as d from tablename
union all
select id, id8 as d from tablename
union all
select id, id9 as d from tablename
union all
select id, id10 as d from tablename)
group by id
Use Values table valued constructor to unpivot the data then find average per ID. Try this
select id,avg(data) from Yourtable
cross apply
(values(D1), (D2), (D3), (D4), (D5), (D6) ,(D7), (D8), (D9) ,(D10)) cs (data)
group by id
Or if your want decimal values then use this.
select id,sum(data)/sum(case when data is not null then 1.0 else 0 end) from Yourtable
cross apply
(values(D1), (D2), (D3), (D4), (D5), (D6) ,(D7), (D8), (D9) ,(D10)) cs (data)
group by id

Conditional UNPIVOT in TSQL

Let's say I have a table with an ID column, and several property columns
MyTable (ID, PI, P2, P3, P4)
ID P1 P2 P3 P4
1 A1 B C1 D1
2 C1 C2 B NULL
3 C2 Z NULL NULL
4 X A1 C1 NULL
So, I need to write a query to find out how many distinct property values out there, no matter in which column they are.
Value Count
A1 2
B 2
C1 3
C2 2
X1 1
...
I think I can get this by using UNPIVOT (correct me, if I am wrong)
Now, how can I get similar count but grouped by a number of non-null values in the row (the count of non-null values per row may, or may not include key columns, doesn't matter), i.e. output like this:
Value NonNullCount Count
A1 3 1
A1 4 1
B 3 1
B 4 1
C1 2 3
C1 4 1
C2 3 1
C2 2 1
...
Here is one method, using cross apply for the unpivot:
select vals.p, t.NonNullCount, count(*)
from (select t.*,
((case when p1 is not null then 1 else 0 end) +
(case when p2 is not null then 1 else 0 end) +
(case when p3 is not null then 1 else 0 end) +
(case when p4 is not null then 1 else 0 end)
) as NonNullCount
from table t
) t cross apply
(values (p1), (p2), (p3), (p4)) vals(p)
where vals.p is not null
group by vals.p, t.NonNullCount;

Creating a SQL view with columns from other table records

I have a table with data like:
A1 FID A2
0 0 39
1 0 23
0 1 16
1 1 64
2 1 12
0 2 76
0 3 11
0 4 87
And I want to create a view that will list this:
FID Col0 Col1 Col2
0 39 23 null
1 16 64 12
2 76 null null
3 11 null null
4 87 null null
How can this be possible in T-SQL?
You could use something like this:
SELECT FID,
Col0 = MAX(CASE WHEN A1 = 0 THEN A2 END),
Col1 = MAX(CASE WHEN A1 = 1 THEN A2 END),
Col2 = MAX(CASE WHEN A1 = 2 THEN A2 END)
FROM T
GROUP BY FID;
Or you could use PIVOT
SELECT pvt.FID,
[Col0] = pvt.[0],
[Col1] = pvt.[1],
[Col2] = pvt.[2]
FROM T
PIVOT
( MAX(A2)
FOR A1 IN ([0], [1], [2])
) pvt
Examples of Both on SQL Fiddle
If you have an unknown number of values for A1, and therefore an unknown number of columns you cannot do this in a view. You would need to use Dynamic SQL, although this is usually better handled in the application layer, not in SQL-Server itself

Query to pickup certain criteria records

I have following table data
ID Type Code Opt Line Status
26985444 1 100 1 1 S0
26987422 1 25 1 1 S0
26987422 1 25 2 1 S1
26987422 1 25 2 2 S2
26987422 4 25 2 3 S0
26987422 2 30 1 1 S1
26987422 2 30 1 2 S2
26987422 2 30 1 3 S0
26987422 3 35 1 1 S0
26985333 1 75 1 1 S0
26985000 1 55 1 1 S0
26985000 1 65 1 1 S0
Out of above I need to select ONLY following records
26985444 1 100 1 1 S0
26985333 1 75 1 1 S0
How can I write SQL query to this.
Thanks
I am interpreting your question as finding IDs that only appear once in the data.
You can do this with aggregation, looking for the singletons:
select ID, min(Type) as Type, min(Code) as Code, min(Opt) as Opt,
min(Line) as Line, min(Status) as Status
from t
group by id
having count(*) = 1;
If your version of Sybase supports window functions:
select ID, Type, Code, Opt, Line, Status
from (select t.*,
count(*) over (partition by id) as cnt
from t
) t
where cnt = 1
The only sorting I see in your above table is if you were to sort by the Code field while it's converted to a char(). Here's a guess at what you might be looking for using max() and min():
select y.*
from yourtable y
join (
select max(cast(Code as char(3))) maxCode,
min(cast(Code as char(3))) minCode
from yourtable
) t on y.code = t.maxCode or y.code = t.minCode
Condensed Fiddle Example