Average Multiple Columns in Postgres - sql

I have a table with multiple rows, each of which contains these four columns:
product
price_instore
price_pickup
price_delivery
price_ship
1
$13.00
$13.50
$14.50
$18.00.
2
$4.00
$4.00
NULL
NULL
3
$10.00
$10.00
$12.00
NULL
I'd like to have a fifth column average_price that gives the average price for a product, but does not count NULLS towards the sum price, or towards the count used to divide the average.
So average price of product 1: ($13+$13.50+$14.50+$18)/4=$14.75
Average price of product 2: ($4+$4)/2 = $4.00
Average price of product 3: ($10+$10+$12)/3 = $10.67
Is there any way to do this is SQL? I've tried subqueries such as the one below without success:
(select coalesce((PRICE_INSTORE + PRICE_PICKUP + PRICE_DELIVERY + PRICE_SHIP) / 4,
PRICE_INSTORE, PRICE_PICKUP, PRICE_DELIVERY, PRICE_SHIP)
but then I only get one price if any of them are null

select
*,
(select avg(x) from unnest(array[price_instore,price_pickup,price_delivery,price_ship]) as x) as avg_price
from
your_table;

I'm drunk, so there's probably a much better way to do this than pivoting, but I cannot see it now with the room spinning around me like it is.
with j as (
select product, to_jsonb(mytable) - 'product' as jdata
from mytable
)
select j.product, avg(e.value::numeric) as avg_price
from j
cross join lateral jsonb_each(j.jdata) as e(key, value)
where e.value is not null
group by j.product.

Try this
select coalesce (PRICE_INSTORE, 0 ) + coalesce (PRICE_PICKUP, 0) + coalesce (PRICE_DELIVERY , 0) + coalesce (PRICE_SHIP , 0) /
((case when PRICE_INSTORE is null then 0 else 1 end) + (case when PRICE_PICKUP is null then 0 else 1 end) +
(case when PRICE_DELIVERY is null then 0 else 1 end) + (case when PRICE_SHIP is null then 0 else 1 end) )
from product

One method is a lateral join:
select t.*, avg_price
from t cross join lateral
(select avg(price) as avg_price
from (values (price_instore), (price_pickup), (price_delivery), (price_ship)
) v(price)
) x

try this:
select coalesce(PRICE_INSTORE,0) + coalesce(PRICE_PICKUP,0) + coalesce(PRICE_DELIVERY,0) + coalesce(PRICE_SHIP,0)) / 4

This one is probably the easiest to digest. Performance shouldn't be too bad unless you have a very large table that is also, not indexed
with cte (product, prices) as
(select product, price_instore from t union all
select product, price_pickup from t union all
select product, price_delivery from t union all
select product, price_ship from t)
select product, avg(prices)
from cte
group by product;
You an either use the output as is, or wrap it around another CTE and use that to join on your original table to get the averages

Related

How to calculate Total records Counts in table with my scenario

I am Writing a SQL Code which basically count stars values Like ratings Feedback forms.To calculate how many peoples rate
1 star,2,3 and so on i am using pivot unpivot SQL properties to calculate count against of each number hits.Now i want to calculate count of all records in the table along with my this query.
i want to achieve my desire result without sub-query.
WITH survey AS (
SELECT
*
FROM
(
SELECT
student_id,
performance,
teacher_behaviour,
survey_id
FROM
survey_feedback
WHERE
survey_id = 1
GROUP BY
student_id,
performance,
teacher_behaviour,
survey_id
) UNPIVOT ( star
FOR q
IN ( performance AS 'PERFORMANCE',
teacher_behaviour AS 'TEACHER_BEHAVIOUR'
) )
ORDER BY
1,
2
)
SELECT
*
FROM
survey PIVOT (
COUNT ( student_id )
FOR star
IN ( 1, 2, 3, 4, 5 )
)
ORDER BY
q;
enter image description here
Hmmm . . . I'm not a fan of PIVOT and UNPIVOT. They really offer no new functionality and are quite bespoke and not powerful enough.
So, I would just unpivot and use conditional aggregation:
SELECT survey_id, which, COUNT(rating) as total,
SUM(CASE WHEN rating = 1 THEN 1 ELSE 0 END) as rating_1,
SUM(CASE WHEN rating = 2 THEN 1 ELSE 0 END) as rating_2,
SUM(CASE WHEN rating = 3 THEN 1 ELSE 0 END) as rating_3,
SUM(CASE WHEN rating = 4 THEN 1 ELSE 0 END) as rating_4,
SUM(CASE WHEN rating = 5 THEN 1 ELSE 0 END) as rating_5
FROM (SELECT sf.*,
(CASE WHEN which = 'PERFORMANCE' THEN performance
WHEN which = 'TEACHER_BEHAVIOUR' THEN teacher_behavior
END) as rating
FROM survey_feedback sf CROSS JOIN
(SELECT 'PERFORMANCE' as which FROM DUAL UNION ALL
SELECT 'TEACHER_BEHAVIOUR' FROM DUAL
) n
) sf
WHERE sf.survey_id = 1
GROUP BY sf.survey_id, which;
In Oracle 12C+, I would use a lateral join for unpivoting.

Select distinct count after count?

I'll cut right to the chase: I have a select I'm currently writing with a rather lengthy where clause, what I want to do is calculate percentages.
So what I need is the count of all results and then my each distinct counts.
SELECT distinct count(*)
FROM mytable
WHERE mywhereclause
ORDER BY columnIuseInWhereClause
works fine for getting each individual values, but I want to avoid doing something like
Select (Select count(*) from mytable WHERE mywhereclause),
distinct count(*)
FROM mytable
WHERE mywhereclause
because I'd be using the same where-clause twice which just seems unnecessary.
This is for OracleDB but I'm only using standard SQL syntax, nothing database specific if I can help it.
Thanks for any ideas.
Edit:
Sample Data
__ID__,__someValue__
1 | A
2 | A
3 | B
4 | C
I want the occurances of A, B, C as numbers as well as the overall count.
__CountAll__,__ACounts__,__BCounts__,__CCounts__
4 | 2 | 1 | 1
So I can get to
100% | 50% | 25% | 25%
That last part I can probably figure out on my own. Excuse my lack of experience or even logic thinking, it's early in the morning. ;)
Edit2:
I do have written a query that works but is clumsy and long as all holy heck, this one is for trying with group by.
Try:
select count(*) as CountAll,
count(distinct SomeColumn) as CoundDistinct -- The DISTINCT goes inside the brackets
from myTable
where SomeOtherColumn = 'Something'
Use case expressions to do conditional counting:
select count(*) as CountAll,
count(case when someValue = 'A' then 1 end) as ACounts,
count(case when someValue = 'B' then 1 end) as BCounts,
count(case when someValue = 'C' then 1 end) as CCounts
FROM mytable
WHERE mywhereclause
Wrap it up in a derived table to do the % part easy:
select 100,
ACounts * 100 / CountAll,
BCounts * 100 / CountAll,
CCounts * 100 / CountAll
from
(
select count(*) as CountAll,
count(case when someValue = 'A' then 1 end) as ACounts,
count(case when someValue = 'B' then 1 end) as BCounts,
count(case when someValue = 'C' then 1 end) as CCounts
FROM mytable
WHERE mywhereclause
) dt
Here's an alternative using window function:
with data_table(ID, some_value)
AS
(SELECT 1,'A' UNION ALL
SELECT 2,'A' UNION ALL
SELECT 3,'B' UNION ALL
SELECT 4,'C'
)
SELECT DISTINCT [some_value],
COUNT([some_value]) OVER () AS Count_All,
COUNT([some_value]) OVER (PARTITION BY [some_value]) AS 'Counts' FROM [data_table]
ORDER BY [some_value]
The advantage is that you don't have to hard-code your [some_value]

Sum distinct records in a table with duplicates in Teradata

I have a table that has some duplicates. I can count the distinct records to get the Total Volume. When I try to Sum when the CompTia Code is B92 and run distinct is still counts the dupes.
Here is the query:
select
a.repair_week_period,
count(distinct a.notif_id) as Total_Volume,
sum(distinct case when a.header_comptia_cd = 'B92' then 1 else 0 end) as B92_Sum
FROM artemis_biz_app.aca_service_event a
where a.Sales_Org_Cd = '8210'
and a.notif_creation_dt >= current_date - 180
group by 1
order by 1
;
Is There a way to only SUM the distinct records for B92?
I also tried inner joining the table on itself by selecting the distinct notification id and joining on that notification id, but still getting wrong sum counts.
Thanks!
Your B92_Sum currently returns either NULL, 1 or 2, this is definitely no sum.
To sum distinct values you need something like
sum(distinct case when a.header_comptia_cd = 'B92' then column_to_sum else 0 end)
If this column_to_sum is actually the notif_id you get a conditional count but not a sum.
Otherwise the distinct might remove too many vales and then you probably need a Derived Table where you remove duplicates before aggregation:
select
repair_week_period,
--no more distinct needed
count(a.notif_id) as Total_Volume,
sum(case when a.header_comptia_cd = 'B92' then column_to_sum else 0 end) as B92_Sum
FROM
(
select repair_week_period,
notif_id
header_comptia_cd,
column_to_sum
from artemis_biz_app.aca_service_event
where a.Sales_Org_Cd = '8210'
and a.notif_creation_dt >= current_date - 180
-- only onw row per notif_id
qualify row_number() over (partition by notif_id order by ???) = 1
) a
group by 1
order by 1
;
#dnoeth It seems the solution to my problem was not to SUM the data, but to count distinct it.
This is how I resolved my problem:
count(distinct case when a.header_comptia_cd = 'B92' then a.notif_id else NULL end) as B92_Sum

SQL Server Completion Percentage Category

im a bit new to sql server, so hopefully this isnt something too convoluted. if i have a table with a bunch of data that shows different records that have been complete or not...
TABLE 1
ID CATEGORY COMPLETE
1 reports yes
2 reports no
3 processes no
4 processes yes
5 reports no
6 events yes
...what would be the best way of creating a new field that would show the percentage complete for every category?
TABLE 2
ID CATEGORY PERCENTAGE
1 events 100%
2 processes 50%
3 reports 33%
any help would be greatly appreciated, thank you.
group by category column and use conditional sum to get only complete = 'yes' cases in the numerator.
select category,
100 * 1.0 * sum(case when complete = 'yes' then 1 else 0 end)/count(*) as pct
from tablename
group by category
You can use windowed functions and PARTITION BY Category:
SELECT DISTINCT Category,
[percentage] = ROUND(100 * SUM(CASE complete WHEN 'yes' THEN 1.0 ELSE 0.0 END)
OVER (PARTITION BY Category)/
COUNT(*) OVER (PARTITION BY Category),0)
FROM #tab;
LiveDemo
With insert to second table:
SELECT DISTINCT
[id] = IDENTITY(INT, 1,1)
,category
,[percentage] = ROUND(100 * SUM(CASE complete WHEN 'yes' THEN 1.0 ELSE 0.0 END)
OVER (PARTITION BY CATEGORY)/
COUNT(*) OVER (PARTITION BY Category),0)
INTO #table2
FROM #tab
ORDER BY [percentage] DESC;
SELECT *
FROM #table2;
LiveDemo2
I think the simplest approach is to use avg():
select category,
avg(case when complete = 'yes' then 100.0 else 0 end) as pct
from tablename
group by category;
If you want this as a number with a percentage, you need a bit more string manipulation:
select category,
str(avg(case when complete = 'yes' then 100.0 else 0 end)) + '%' as pct
from tablename
group by category;
However, I would recommend keeping the value as a number.

splitting the values in one column and a set of rows evenly and proportionally among other rows

I know, I confused you.
I have this data:
For the three items that are NULL, I need to:
Add them (1828.94 + 772.90 + 0.00).
Split this total among each ItemID evenly and proportionally based on quantity for each.
Note that there are ItemIDs that are the same but this is OK.
Basically the end result will be the same columns without the 3 rows that have ItemID = NULL and the Amount column will be increased by some because I'm splitting the amount among the ItemIDs.
I'm having a really hard time doing this without having to do a bunch of loops.
Can anyone give me a hand?
You can get the apportioned amount with this query:
select t.*,
x.AmountToSplit * t.qty / x.TotalQty as AmountToAdd
from t cross join
(select sum(case when itemId is null then amount end) as AmountToSplit,
sum(case when itemId is not null then Qty end) as TotalQty
from t
) x
where t.itemId is not null;
If you actually want to update the amounts, then use this as an updatable CTE:
with toupdate as (
select t.*,
x.AmountToSplit * t.qty / x.TotalQty as AmountToAdd
from t cross join
(select sum(case when itemId is null then amount end) as AmountToSplit,
sum(case when itemId is not null then Qty end) as TotalQty
from t
) x
where t.itemId is not null
)
update toupdate
set Amount = Amount + AmountToAdd;
Would this work for what you're trying to do?
DECLARE #NullAmounts MONEY,
#RowCount INT
SELECT #NullAmounts = SUM(CASE WHEN ItemID IS NULL THEN Amount ELSE 0 END),
#RowCount = COUNT(*)
FROM Table
UPDATE Table
SET Amount = Amount + (#NullAmounts / #RowCount)
WHERE
ItemID IS NOT NULL
Of course, after you've run the update, you can DELETE the rows so you don't have them return in a SELECT statement.
DELETE Table
WHERE ItemID IS NULL