Average across multiple columns with varying data - sql

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

Related

Sql query - I have three columns in one table and I need to check each column data into another table

For Example:
1 23 12
33 AB1 A1
5 AC4 B5
77 AD9 B5
7 93C 1
.
A B C D E F G
1 EA12 B5
2 B29 7
3 AD9 AC4
7 AB1 1
Result
A B C
1 1 23 null 12 null
33 null AB1 AB1 A1 null
5 5 AC4 AC4 B5 B5
77 null AD9 AD9 B5 B5
7 7 93C null 1 1
I think this is what you need:
with
x as (
select c as v from t2
union select e from t2
union select g from t2
)
select
a,
(case when a in (select v from x) then a end) as found_a,
b,
(case when b in (select v from x) then b end) as found_b,
c,
(case when c in (select v from x) then c end) as found_c
from t1

Selecting Entry and exit record for each entry in a table

The following is a sample from the table I have
F_ID R_ID DATE Col_A Col_B Col_C
12 158 20161008 01 99 99
12 158 20161012 01 01 99
12 158 20161019 01 02 10
12 158 20161022 99 01 10
12 160 20161006 01 99 01
12 160 20161011 99 01 99
12 160 20161017 99 01 10
17 167 20161013 99 01 01
17 167 20161016 99 02 99
17 167 20161020 02 01 10
17 174 20161010 99 01 01
17 174 20161012 01 02 11
17 174 20161017 99 99 10
I want to select such that I get the following result
F_ID R_ID DATE Col_A Col_B Col_C
12 158 20161008 01 01 99 - Entry record
12 158 20161022 99 01 10 - Exit Record
12 160 20161006 01 99 01 - Entry record
12 160 20161017 99 01 10 - Exit Record
17 167 20161013 99 01 01 - Entry record
17 167 20161020 02 01 10 - Exit Record
17 174 20161010 99 01 01 - Entry record
17 174 20161017 99 99 10 - Exit Record
For each F_ID, R_ID:
When Col_A or Col_B = '01' and Col_C <>'10' - **It is an entry record**
When Col_C = '10' - **It is an exit record**
Logic here is
1. Select the earliest entry record
**and**
2. Select the latest exit record for each F_ID, R_ID
I'm thinking of using union like below...
Select * from tbl1 T
where
T.Col_C = '10' and
T.DATE = (select max(T2.DATE) from tbl1 T2
where
T2.Col_C = '10' and
T2.R_ID = T.R_ID
T2.F_ID = T.F_ID
)
union
Select * from tbl1 K
where
(K.Col_A = '01' or K.Col_B = '01') and
K.Col_C <> '10' and
K.DATE = (select min(K2.DATE) from tbl1 K2 where
(K2.Col_A = '01' or K2.Col_B = '01') and
K2.Col_C <> '10' and
K2.R_ID = K.R_ID
K2.F_ID = K.F_ID
)
But using union like I did on the same table with self joins is returning me a garbage data.
If I correctly understand, you need this:
SELECT * FROM (
SELECT T1.*, ROW_NUMBER() OVER(PARTITION BY F_ID , R_ID order by DATE) rn, 'Entry record' as rec FROM (
SELECT * FROM your_table WHERE (Col_A = '01' or Col_B = '01') and Col_C <> '10'
) T1
union all
SELECT T2.*, ROW_NUMBER() OVER(PARTITION BY F_ID , R_ID order by DATE DESC) rn , 'Exit record' as rec FROM (
SELECT * FROM your_table WHERE Col_C = '10'
) T2
) t3
where rn = 1
edit
More simplified version (thanks to #ThorstenKettner)
SELECT * FROM (
SELECT your_table.*, ROW_NUMBER() OVER(PARTITION BY F_ID, R_ID order by DATE) as rn, 'Entry record' as rec FROM your_table WHERE (Col_A = '01' or Col_B = '01') and Col_C <> '10'
union all
SELECT your_table.*, ROW_NUMBER() OVER(PARTITION BY F_ID, R_ID order by DATE DESC) as rn, 'Exit record' as rec FROM your_table WHERE Col_C = '10'
) t3
where rn = 1
ORDER BY F_ID, R_ID, DATE

Creating columns from rows

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.

How would you do this using SQL Server 2005?

Let's say I have a table table1 with the following structure:
id date v1 v2 v3 v4 ... vn
------------------------------
1 03 Y N 89 77 ... x
1 04 N N 9 7 ... i
1 05 N Y 6 90 ... j
1 06 N Y 9 34 ... i
1 07 N Y 0 88 ... i
2 03 N N 9 77 ... f
2 04 Y Y 90 7 ... y
2 05 Y N 6 90 ... v
2 06 N Y 9 34 ... i
2 07 N N 10 88 ... i
As you might see, the table has five rows for each id. I'd like to create two new columns:
-summarystory:= This variable is computed for those rows having the date between 05 and 07 and is the sum of the variable v3 for the last three rows.
Let me explain this better: the first two rows (date 03 and 04) must have NULL values, but the row having date=05 is the sum of the last three v3 values, i.e, 89+9+6=104. Likewise, the row having date=06 must be equal to 9+6+9=24. This have to be done for each id and for each date.
This is the desired result:
id date v3 summarystory
-------------------------
1 03 89 NULL
1 04 9 NULL
1 05 6 104
1 06 9 24
1 07 0 15
2 03 9 NULL
2 04 90 NULL
2 05 6 105
2 06 9 105
2 07 10 25
VcountYN:= the number of Y for each row (based only on variables v1 and v2). So. for instance, for the first row it would be VcountYN=1. This variable must be computed for all the rows.
Any help is much appreciated.
Here's how to do the computations. Turning it into the new table is left as an exercise:
-- SQL 2012 version
Select
t.id,
t.[date],
Case When [Date] Between 5 And 7 Then
Sum(v3) over (
partition by
id
order by
[date]
rows between
2 preceding and current row
) Else Null End,
Case When v1 = 'Y' Then 1 Else 0 End +
Case When v2 = 'Y' Then 1 Else 0 End
From
table1 t;
-- SQL 2005 version
Select
t1.id,
t1.[date],
Case When t1.[date] Between 5 And 7 Then t1.v3 + IsNull(t2.v3, 0) + IsNull(t3.v3, 0) Else Null End,
Case When t1.v1 = 'Y' Then 1 Else 0 End +
Case When t1.v2 = 'Y' Then 1 Else 0 End
From
table1 t1
Left Outer Join
table1 t2
On t1.id = t2.id and t1.[date] = t2.[date] + 1
Left Outer Join
table1 t3
On t2.id = t3.id and t2.[date] = t3.[date] + 1
http://sqlfiddle.com/#!6/a1c45/2

SQL Server query for finding the sum of 4 consecutive values

Can somebody help me in finding the sum of 4 consecutive values i.e rolling sum of last 4 values.
Like:
VALUE SUM
1 NULL
2 NULL
3 NULL
4 10
5 14
6 18
7 22
8 26
9 30
10 34
11 38
12 42
13 46
14 50
15 54
16 58
17 62
18 66
19 70
20 74
21 78
22 82
23 86
24 90
25 94
26 98
27 102
28 106
29 110
30 114
31 118
32 122
33 126
34 130
35 134
36 138
37 142
38 146
Thanks,
select sum(select top 4 Value from [table] order by Value Desc)
or, perhaps
select sum(value)
from [Table]
where Value >= (Max(Value) - 4)
I haven't actually tried either of those- and can't at the moment, but they should get you pretty close.
Quick attempt, which gets the results you've posted in your question (except the 1st 3 rows are not NULL). Assumes that VALUE field is unique and in ascending order:
-- Create test TABLE with 38 values in
DECLARE #T TABLE (Value INTEGER)
DECLARE #Counter INTEGER
SET #Counter = 1
WHILE (#Counter <= 38)
BEGIN
INSERT #T VALUES(#Counter)
SET #Counter = #Counter + 1
END
-- This gives the results
SELECT t1.VALUE, x.Val
FROM #T t1
OUTER APPLY(SELECT SUM(VALUE) FROM (SELECT TOP 4 VALUE FROM #T t2 WHERE t2.VALUE <= t1.VALUE ORDER BY t2.VALUE DESC) x) AS x(Val)
ORDER BY VALUE
At the very least, you should see the kind of direction I was heading in.
Assuming ID can give you the last 4 rows.
SELECT SUM([SUM])
FROM
(
SELECT TOP 4 [SUM] FROM myTable ORDER BY ID DESC
) foo
Each time you query it, it will read the last 4 rows.
If this is wrong (e.g. you want the sum of each consecutive 4 rows), then please give sample output
Following would work if your Value column is sequential
;WITH q (Value) AS (
SELECT 1
UNION ALL
SELECT q.Value + 1
FROM q
WHERE q.Value < 38
)
SELECT q.Value
, CASE WHEN q.Value >= 4 THEN q.Value * 4 - 6 ELSE NULL END
FROM q
otherwise you might use something like this
;WITH q (Value) AS (
SELECT 1
UNION ALL
SELECT q.Value + 1
FROM q
WHERE q.Value < 38
)
, Sequential (ID, Value) AS (
SELECT ID = ROW_NUMBER() OVER (ORDER BY Value)
, Value
FROM q
)
SELECT s1.Value
, [SUM] = s1.Value + s2.Value + s3.Value + s4.Value
FROM Sequential s1
LEFT OUTER JOIN Sequential s2 ON s2.ID = s1.ID - 1
LEFT OUTER JOIN Sequential s3 ON s3.ID = s2.ID - 1
LEFT OUTER JOIN Sequential s4 ON s4.ID = s3.ID - 1
Note that the table qin the examples is a stub for your actual table. The actual statement then becomes
;WITH Sequential (ID, Value) AS (
SELECT ID = ROW_NUMBER() OVER (ORDER BY Value)
, Value
FROM YourTable
)
SELECT s1.Value
, [SUM] = s1.Value + s2.Value + s3.Value + s4.Value
FROM Sequential s1
LEFT OUTER JOIN Sequential s2 ON s2.ID = s1.ID - 1
LEFT OUTER JOIN Sequential s3 ON s3.ID = s2.ID - 1
LEFT OUTER JOIN Sequential s4 ON s4.ID = s3.ID - 1