SQL Calculate a sum by replacement - sql

So I have this table of badges (kinda' like STO has)
| user_id | grade |
---------------------
| 1 | bronze |
| 1 | silver |
| 2 | bronze |
| 1 | gold |
| 1 | bronze |
| 3 | gold |
| 1 | gold |
And I want to calculate the total sum of badge-points for user id 1.
Every bronze badge should be equal to 5, every silver - 50, gold - 200.
So in the end I need to get 460 for this sample.

For a specific user_id:
select
sum(case grade
when 'bronze' then 5
when 'silver' then 50
when 'gold' then 200
end
)
from tablename
where user_id = 1

Pretty basic conditional aggregation:
sum (case when grade = 'bronze' then 5
when grade = 'silver' then 50
when grade = 'gold' then 200
else 0 end)

You would use case and sum():
select user_id,
sum(case when grade = 'bronze' then 5
when grade = 'silver' then 50
when grade = 'gold' then 200
end)
from t
group by user_id;

Put those badge values into a another table and join to it. Or at least use a table expression.
SELECT user_id, SUM(BadgeVal) AS Total
FROM T
INNER JOIN
(VALUES ('bronze',5)
,('silver',50)
,('gold',200))
AS BadgeValues(grade, BadgeVal)
ON T.grade = BadgeValues.grade
GROUP BY user_id
ORDER BY user_id;
Note that this syntax works with SQL Server, does not work with MySQL to my knowledge, and I have no idea about Oracle or Postgres or any other DBMS.

Here you go using decode function :
SELECT SUM(t.grade_point)
FROM (SELECT id,grade,decode(grade,'bronze',5,'silver',50,'gold',200) AS grade_point
FROM badges where id =1) t;

Related

SQL count where where column is greater than the other in group by?

Suppose I have a table money_table like:
team_id | money_spent | money_budget
--------------------------------------
123 | 3456.32 | 3466
964 | 236.32 | 200
123 | 9663 | 9400
964 | 3456.32 | 3466
The output table should be:
team_id | total_money_spent | total_money_budget | days_over_spent | days_under_spent
--------------------------------------
123 | 13119.32 | 12866 | 2 | 0
964 | 3692.64 | 3666 | 1 |. 1
The first 2 columns are easy with a group BY, I am wondering about the last 2 columns and how to tackle that. My initial query was:
SELECT
team_id,
SUM(money_spent) as total_money_spent,
SUM(money_budget) as total_money_budget
FROM money_table
GROUP BY team_id
ORDER BY team_id ASC
The works fine for the first 2 columns, but I am unable to think of how to get days_over_spent and days_under_spent.
Any suggestions?
Edit:
days_over_spent is the number of rows where money_spent > money_budget
days_under_spent is the number of rows where money_spent < money_budget
You could do the calculations for "over the budget" in a CTE
with tmp (t, s, b, o, u) as (
select
team,
spent,
budget,
case when spent > budget then 1 else 0 end,
case when spent < budget then 1 else 0 end
from budget
)
select
t as team,
sum(s) as total_spent,
sum(b) as total_budget,
sum(o) as days_over,
sum(u) as days_under
from tmp
group by t
Of course you can also just add the case into the query itself
select
team,
sum(spent),
sum(budget),
sum(case when spent > budget then 1 else 0 end),
sum(case when spent < budget then 1 else 0 end)
from budget
group by team
SELECT
team_id,
SUM(money_spent) as total_money_spent,
sum(money_budget) as total_money_budget,
sum(case when money_spent > money_budget then 1 else 0 end) as days_over_spent,
sum(case when money_spent < money_budget then 1 else 0 end) as days_under_spent
FROM money_table
GROUP BY team_id
ORDER BY team_id ASC

How to include row totals in pivot statement in Oracle?

I have a table of data, see
Using a pivot statement, I am able to break down the count by title
select * from (
select * from ta
)
pivot (
COUNT(title)
for title in ( 'worker', 'manager') )
So the result looks like this:
STATUS 'worker' 'manager'
started 3 1
finished 4 5
ready 3 4
What I need to add a third column for the row totals
STATUS 'worker' 'manager' Total
started 3 1 4
finished 4 5 9
ready 3 4 7
Any idea how I can accomplish this within the same statement?
demo is at http://sqlfiddle.com/#!4/740fd/1
I would just use conditional aggregation rather than pivot. This gives you the extra flexibility that you need:
select
status,
sum(case when title = 'worker' then 1 else 0 end) worker,
sum(case when title = 'manager' then 1 else 0 end) manager,
count(*) total
from ta
group by status
Demo on DB Fiddle:
STATUS | WORKER | MANAGER | TOTAL
:------- | -----: | ------: | ----:
started | 3 | 1 | 4
finished | 4 | 5 | 9
ready | 3 | 4 | 7
Use the SUM() analytic function to get the total and then use PIVOT
select
status,
sum(case
when title = 'worker'
then 1
else 0
end) worker,
sum(case
when title = 'manager'
then 1
else 0
end) manager,
count(*) total
from ta
group by status
Give an alias for the whole query(such as q) in order to qualify the all columns with asterisk(q.*), and then sum up all the columns to yield total column next to it :
select q.*, worker + manager as total
from ta
pivot
(
count(title)
for title in ( 'worker' as worker, 'manager' as manager )
) q
Demo
I think the other examples are much simpler, but here is a different approach using cube and grouping before pivoting:
select *
from (
select decode(grouping(title),1,'total',0,title) title,
status,
count(*) cnt
from ta
group by status, cube(title) )
pivot(
sum(cnt) for title in ('worker','manager','total')
)
Output:
| STATUS | 'worker' | 'manager' | 'total' |
|----------|----------|-----------|---------|
| finished | 4 | 5 | 9 |
| ready | 3 | 4 | 7 |
| started | 3 | 1 | 4 |
http://sqlfiddle.com/#!4/740fd/13/0
Adding the cube into the group by clause will give you a subtotal for that column. It will show as null in that column by default. You can use the grouping function in the select clause to differentiate between the total row and the normal rows (the total row will be 1, normal rows are 0). Using a decode will force those total rows to be 'total' which becomes one of the values that you can pivot on.

Percentage per user of total - reach best perfomance

I want to get a percentage of items/user in our warehouse (only items which are out of stock).
item_id | partner_id | item_name | stock_sum | manager_id
---------------------------------------------------------------------------
23020 | 232 | cola |  0 | 237
39935 | 232 | sprite | 0 | 89
23030 | 232 | fanta   | 60 | 32
15331 | 232 | water   | 20 | 237
So i have to get:
manager_id = 237 -> 0,25 -> 25%
manager_id = 89 -> 0,25 -> 25%
total -> 0,50 -> 50%
My first idea was something like this:
select skk.manger_id, count(*), count(skk.item_id/sk.item_id) as prcntg
from stock skk
inner join stock sk on skk.item_id = sk.item_id and skk.manager_id = sk.manager_id
where skk.stock_sum = 0
group by skk.manager_id
But i think this is not the right solution.. so i need help.
[Output] expected:
manager_id | total | % of total (prcntg)
----------------------------------------------
237 | 0,5 | 0,25
89 | 0,5 | 0,25
32 | 0,5 | 0
You seem to be describing this a calculation using window functions:
select manager_id, count(*) as num_items,
sum(case when stock_sum = 0 then 1 else 0 end) as num_out_of_stock,
avg(case when stock_sum = 0 then 1.0 else 0 end) as manager_avg,
sum(case when stock_sum = 0 then 1 else 0 end) / count(*) as overall_avg
from stock s
group by manager_id;
The overall_avg is what you seem to be looking for.
select manager_id, sum(av) pcnt
from (
select manager_id, count(case stock_sum when 0 then 1 end) / sum(count(1)) over () as av
from stock group by manager_id)
group by rollup(manager_id)
dbfiddle
Divide counted zeros by number of all entries (analytical sum of all counts). Use outer rollup if you need total value.

Removing group of results if total is 0

I am using the following table to create a stacked bar chart - its quite a bit larger than this:
ID | Name | foodEaten | total
1 | Sam | Burger | 3
1 | Sam | Pizza | 1
1 | Sam | Kebab | 0
1 | Sam | Cheesecake| 3
1 | Sam | Sandwich | 5
2 | Jeff | Burger | 0
2 | Jeff | Pizza | 0
2 | Jeff | Kebab | 0
2 | Jeff | Cheesecake| 0
2 | Jeff | Sandwich | 0
I need to find a way to remove results like Jeff. Where the entire total for what he ate is 0. I can't think of the easiest way to achieve this. I've tried grouping the entire result by Id and creating a total, but its just not happening.
If the person has eaten a total of 0 food, then he needs to be excluded. But if he hasn't, and he hasn't eaten any kebabs, as shown in my above table, this needs to be included in the result!
So the output needed is:
ID | Name | foodEaten | total
1 | Sam | Burger | 3
1 | Sam | Pizza | 1
1 | Sam | Kebab | 0
1 | Sam | Cheesecake| 3
1 | Sam | Sandwich | 5
Assuming that you want the data as it appears, and not the aggregate out and then exclude:
WITH CTE AS (
SELECT ID,
[Name],
foodEaten,
total,
SUM(total) OVER (PARTITION BY [Name]) AS nameTotal
FROM YourTable)
SELECT ID,
[Name],
foodEaten,
total
FROM CTE
WHERE nameTotal > 0;
select id, name, foodEaten, sum(total) as total from <table> group by ID having sum(total) > 0
Does this work for you?
You can try below -
select id,name
from tablename a
group by id,name
having sum(total)>0
OR
DEMO
select * from tablename a
where not exists (select 1 from tablename b where a.id=b.id group by id,name
having sum(total)=0)
Try this
;WITH CTE (ID , Name , foodEaten , total)
AS
(
SELECT 1 , 'Sam' , 'Burger' , 3 UNION ALL
SELECT 1 , 'Sam' , 'Pizza' , 1 UNION ALL
SELECT 1 , 'Sam' , 'Kebab' , 2 UNION ALL
SELECT 1 , 'Sam' , 'Cheesecake', 3 UNION ALL
SELECT 1 , 'Sam' , 'Sandwich' , 5 UNION ALL
SELECT 2 , 'Jeff' , 'Burger' , 0 UNION ALL
SELECT 2 , 'Jeff' , 'Pizza' , 0 UNION ALL
SELECT 2 , 'Jeff' , 'Kebab' , 0 UNION ALL
SELECT 2 , 'Jeff' , 'Cheesecake', 0 UNION ALL
SELECT 2 , 'Jeff' , 'Sandwich' , 0
)
SELECT ID , Name ,SUM( total) AS Grandtotal
FROM CTE
GROUP BY ID , Name
HAVING SUM( total) >0
Result
ID Name Grandtotal
----------------------
1 Sam 14
Using DELETE with HAVING SUM(total) = 0 will remove the group of result which their total is 0
DELETE FROM TableName
WHERE ID IN (SELECT Id FROM TableName GROUP BY ID HAVING SUM(total) = 0)
or if you want to remvoe and select only the records which has sum of total is zero, then
SELECT * FROM TableName
WHERE ID NOT IN (SELECT Id FROM TableName GROUP BY ID HAVING SUM(total) = 0)
Assuming total is never negative, then probably the most efficient method is to use exists:
select t.*
from t
where exists (select 1
from t t2
where t2.name = t.name and
t2.total > 0
);
In particular, this can take advantage of an index on (name, total).

SQL join problems - users betting on matches

I have the following table:
scores:
user_id | match_id | points
1 | 110 | 4
1 | 111 | 3
1 | 112 | 3
2 | 111 | 2
Users bet on matches and depending on the result of the match they are awarded with points. Depending on how accurate the bet was you are either awarded with 0, 2, 3 or 4 points for a match.
Now I want to rank the users so that i can see who is in 1st, 2nd place etc...
The ranking order is firstly by total_points. If these are equal its ordered by the amount of times a user has scored 4 points then by the amount of times a user scored 3 points and so on.
For that i would need the following table:
user_id | total_points | #_of_fours | #_of_threes | #_of_twos
1 | 10 | 1 | 2 | 0
2 | 2 | 0 | 0 | 1
But i cant figure out the join statements which would help me get it.
This is as far as i get without help:
SELECT user_id, COUNT( points ) AS #_of_fours FROM scores WHERE points = 4 GROUP BY user_id
Which results in
user_id | #_of_fours
1 | 1
2 | 0
Now i would have to do that for #_of_threes and twos aswell as total points and join it all together, but i cant figure out how.
BTW im using MySQL.
Any help would be really apreciated. Thanks in advance
SELECT user_id
, sum(points) as total_points
, sum(case when points = 4 then 1 end) AS #_of_fours
, sum(case when points = 3 then 1 end) AS #_of_threes
, sum(case when points = 2 then 1 end) AS #_of_twos
FROM scores
GROUP BY
user_id
Using mysql syntax, you can use SUM to count the matching rows easily;
SELECT
user_id,
SUM(points) AS total_points,
SUM(points=4) AS no_of_fours,
SUM(points=3) AS no_of_threes,
SUM(points=2) AS no_of_twos
FROM Table1
GROUP BY user_id;
Demo here.