SQL - create column with row averages excluding null values - sql

I have a table like this
name
jan
feb
march
april
may
stan
3
null
7
null
3
dawn
2
3
9
2
null
and I'd like to ad a column that has averages rows while skipping nulls
name
jan
feb
mar
apr
may
AVG
stan
3
null
7
null
3
4.3
dawn
2
3
9
2
null
4
The following code results in a null value for all rows that are missing values
SELECT *, AVG(jan+feb+mar+apr+may)/5 as avg
FROM t
Thanks in advance!

I would use arrays and then unnest it to calculate the avg
with cte as
(select *, unnest(array[jan,feb,mar,apr,may]) as months
from t)
select name,jan, feb, mar, apr, may, avg(months)
from cte
group by name, jan, feb, mar, apr, may;

Related

How to use 2 columns as "key" to get MAX value of selection (and on to next "key") in a SQL query

using a SQL query I am trying to get a max value from multiple rows, using 2 columns as 'key', and then sum them and move on t next 'key'
Here is an example table. It has years, userid and points. Each year has several weeks.
What I want to do is to take each users MAX points for each year and SUM them.
year
userid
week
points
2020
1
1
3
2020
1
3
3
2020
1
3
5
2020
1
4
12
2020
2
1
4
2020
2
2
4
2020
2
3
6
2020
2
4
10
2021
1
1
4
2021
1
2
5
2021
1
3
8
2021
1
4
9
2021
2
1
3
2021
2
2
6
2021
2
3
7
2021
2
4
13
I'd like the result for each year to be
User 1:
2020, 1, 12
2021, 1, 9
User 2:
2020, 2, 10
2021, 2, 13
...and after summing them, sorted by points:
userid
points
2
33
1
21
...and so forth (adding on users and years)
Any help is very much appreciated.
Per Gordon's helpful answer this is the query:
SELECT username, userdb.userid, SUM(points) as points FROM (SELECT standing.*, row_number() over (partition by standing.userid, year ORDER BY points desc) AS seqnum FROM standing) t JOIN userdb on userdb.userid = t.userid WHERE seqnum = 1 GROUP BY userid ORDER BY points DESC
You can use two levels of aggregation:
select userid, sum(max_points)
from (select userid, year, max(points) as max_points
from t
group by userid, year
) uy
group by userid;
Alternatively, you could handle this by filtering such as by using a window function:
select userid, sum(points)
from (select t.*,
row_number() over (partition by userid, year order by points desc) as seqnum
from t
) t
where seqnum = 1
group by userid;

How do i transform calendar year column to multiple year to months column based on months column and calendar year columns

I have data like this
id MoYear CalenderYear jan feb mar dec
1 2017 2017 1 2 4 0
1 2017 2018 1 0 6 10
2 2018 2018 80 5 8 22
3 2017 2018 30 12 0 3
Now i want ouput like this
id MOyear jan_17 feb_17 mar_17 dec_17 jan_18 feb_18 mar_18 dec_18
1 2017 1 2 4 0 1 0 6 10
2 2018 null null null null 80 5 8 22
3 2017 null null null null 30 12 0 3
I have calendar year column and months columns, based on the calendar year and months column i need to make multiple year-months columns.
I can get to the solution by unpivoting and then after back to pivot. But, the data is so large it takes a lot of memory. The performance is very bad.
Not sure if this will be better approach but you can achieve your output using case statement as well if you don't want to do pivot/unpivot.
Data Creation:
select 1 as ID, 2017 as MOYEar, 2017 as calenderyear, 1 as Jan, 2 as feb,
4 as mar, 0 as dece into #temp union all
select 1 as ID, 2017 as MOYEar, 2018 as calenderyear, 1 as Jan, 0 as feb,
6 as mar, 10 as dece union all
select 2 as ID, 2018 as MOYEar, 2018 as calenderyear, 80 as Jan, 5 as feb,
8 as mar, 22 as dece union all
select 3 as ID, 2017 as MOYEar, 2018 as calenderyear, 30 as Jan, 12 as feb,
0 as mar, 3 as dece
Query:
Select ID, MOYEar, max(case when calenderyear = '2017' then Jan else null end) as Jan_17,
max(case when calenderyear = '2017' then Feb else null end ) as Feb_17,
max(case when calenderyear = '2017' then Mar else null end ) as Mar_17,
max(case when calenderyear = '2017' then Dece else null end) as Dece_17,
max(case when calenderyear = '2018' then Jan else null end ) as Jan_18,
max(case when calenderyear = '2018' then Feb else null end ) as Feb_18,
max(case when calenderyear = '2018' then Mar else null end ) as Mar_18,
max(case when calenderyear = '2018' then Dece else null end) as Dece_18 from #temp
Group by ID, MOYEar
Output:
ID MOYEar Jan_17 Feb_17 Mar_17 Dece_17 Jan_18 Feb_18 Mar_18 Dece_18
1 2017 1 2 4 0 1 0 6 10
3 2017 NULL NULL NULL NULL 30 12 0 3
2 2018 NULL NULL NULL NULL 80 5 8 22

Convert columns data into rows in PostgreSQL

I have data in the following format.
order_no rate jan feb mar ....
1 1200 2 4
2 1000 1 5
3 2400 14 3
Now I want to transpose this table to get the following output.
order_no rate month unit
1 1200 feb 2
1 1200 mar 4
2 1000 jan 1
2 2400 mar 5 and so on..
How can I do this?
You can create a "temporary" normalized view on the data using a cross join:
select o.order_no, o.rate, v.*
from orders o
cross join lateral (
values
('jan', jan),
('feb', feb),
('mar', mar),
...
('dec', dec)
) as v(month, unit)
If you want to exclude the months with no values, you can add
where v.unit is not null
to the query
Online example: http://rextester.com/PBP46544
One simple approach uses UNION:
SELECT order_no, rate, 'jan' AS month, jan AS unit UNION ALL
SELECT order_no, rate, 'feb', feb UNION ALL
...
SELECT order_no, rate, 'dec', dec
ORDER BY order_no;
Postgres also has CROSSTAB capabilities. But to use that, you have to be really good at SQL, which I'm not.
Try this
Select order_no, rate, 'jan' as month, jan as unit
from tbl
where jan is not null
union all
Select order_no, rate, 'feb' as month, feb as unit
from tbl
where feb is not null
union all
Select order_no, rate, 'mar' as month, mar as unit
from tbl
where mar is not null
order by order_no

Query which converts rows into columns

I have something like
ZoneNumber AverageSpeed
1 20
2 30
3 50
4 60
5 70
And I want to have a view like
AverageSpeed 1 AverageSpeed 2 AverageSpeed 3 AverageSpeed 4 AverageSpeed 5
20 30 50 60 70
Does anyone have any ideas what my query need to be written like?
You need to use Pivot.
Pivot rotates a table-valued expression by turning the unique values from one column in the expression into multiple columns in the output, and performs aggregations where they are required on any remaining column values that are wanted in the final output.
Basic example:
SELECT *
FROM (
SELECT year(invoiceDate) as [year], left(datename(month,invoicedate),3) as [month], InvoiceAmount as Amount
FROM Invoice
) as s
PIVOT
(
SUM(Amount)
FOR [month] IN (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec)
)AS pivot

Combining two T-SQL pivot queries in one

Suppose you had this table:
CREATE TABLE Records
(
RecordId int IDENTITY(1,1) NOT NULL,
CreateDate datetime NOT NULL,
IsSpecial bit NOT NULL
CONSTRAINT PK_Records PRIMARY KEY(RecordId)
)
Now a report needs to be created where the total records and the total special records are broken down by month. I can use these two queries separately:
-- TOTAL RECORDS PER MONTH
SELECT January, February, March, April, May, June,
July, August, September, October, November, December
FROM (
SELECT RecordId, DATENAME(MONTH, CreateDate) AS RecordMonth
FROM dbo.Records
) AS SourceTable
PIVOT (
COUNT(RecordId) FOR RecordMonth IN (January, February, March, April, May, June,
July, August, September, October, November, December)
) AS PivotTable;
-- TOTAL SPECIAL RECORDS PER MONTH
SELECT January, February, March, April, May, June,
July, August, September, October, November, December
FROM (
SELECT RecordId, DATENAME(MONTH, CreateDate) AS RecordMonth
FROM dbo.Records
WHERE IsSpecial = 1
) AS SourceTable
PIVOT (
COUNT(RecordId) FOR RecordMonth IN (January, February, March, April, May, June,
July, August, September, October, November, December)
) AS PivotTable;
The results might look like this:
Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec
total 0 0 2 2 1 0 0 1 2 1 2 4
total special 0 0 1 0 1 0 0 0 0 0 0 2
Is it possible to combine these two queries into a single more efficient query?
I would do it like this:
SELECT
CASE SQ.total_type
WHEN 1 THEN 'total special'
WHEN 2 THEN 'total expensive'
ELSE 'total'
END AS total_type,
SUM(CASE WHEN MONTH(R.CreateDate) = 1 THEN 1 ELSE 0 END) AS January,
SUM(CASE WHEN MONTH(R.CreateDate) = 2 THEN 1 ELSE 0 END) AS February,
SUM(CASE WHEN MONTH(R.CreateDate) = 3 THEN 1 ELSE 0 END) AS March,
...
FROM
dbo.Records R
INNER JOIN
(
SELECT 0 AS total_type UNION ALL -- All
SELECT 1 UNION ALL -- IsSpecial
SELECT 2 -- IsExpensive
) AS SQ ON
(R.IsSpecial | (R.IsExpensive * 2)) & SQ.total_type = SQ.total_type
GROUP BY
SQ.total_type
ORDER BY
SQ.total_type DESC
You can only have one aggregate (COUNT(RecordId)) per pivot so all you do is combine into one result set with a UNION ALL with a suitable extra column to identify each pivot.
Otherwise, you have no way to distinguish the 2 different aggregates in the pivot
Thanks for the solution Tom, that answers my pivot question.
Too bad for me I had the wrong question. For my problem I'm now feeling it would be better to use a plain grouping query like this instead:
SELECT DATENAME(MONTH, CreateDate) AS Month,
COUNT(*) AS Total,
SUM(CASE
WHEN IsSpecial = 1 THEN 1
ELSE 0
END) AS TotalSpecial,
SUM(CASE
WHEN IsExpensive = 1 THEN 1
ELSE 0
END) AS TotalExpensive
FROM Records
GROUP BY DATENAME(MONTH, CreateDate);
Then all that is left to do is rotate the results before they are presented. Nice to know eh?