SQL: count occurrences of values - sql

Let
user | fruit
------------
1 | apple
1 | apple
1 | apple
2 | apple
2 | apple
1 | pear
Trying to combine count and group by to get
user | apples | pears
---------------------
1 | 3 | 1
2 | 2 | 0
Any hints on how to proceed are appreciated.

Use case expressions to do conditional counting:
select user,
count(case when fruit = 'apple' then 1 end) as apples,
count(case when fruit = 'pear' then 1 end) as pears
from tablename
group by user

If you´re working on an Oracle, you would use the PIVOT-function:
SELECT *
FROM fruit t
PIVOT (COUNT(fruit) AS cnt
FOR(fruit) IN ('apple' AS apple
, 'pear' AS pear) );
More details and full samples on PIVOT / UNPIVOT you´ll find in the web (f.e. here https://oracle-base.com/articles/11g/pivot-and-unpivot-operators-11gr1 )

Related

Produce a value based on aggregated rows where I want to CASE on multiple values?

I have a table of the following form:
| PersonId | Eats |
|----------|-------------------|
| 1 | Meat |
| 1 | Animal Byproducts |
| 1 | Vegetables |
| 2 | Animal Byproducts |
| 2 | Vegetables |
| 3 | Vegetables |
And I want to turn it into a table like:
| PersonId | DietaryPreference |
|----------|--------------------|
| 1 | Omnivore |
| 2 | Vegetarian |
| 3 | Vegan |
My initial thought is do to something like
WITH cte AS (
SELECT
PersonId,
STRING_AGG (Eats,',') WITHIN GROUP (ORDER BY Eats ASC) AS EatsConcat
FROM MyTable
GROUP BY PersonId
)
SELECT
PersonId,
CASE EatsConcat
WHEN 'Animal Byproducts,Meat,Vegetables' THEN 'Omnivore'
WHEN 'Animal Byproducts,Vegetables' THEN 'Vegetarian'
WHEN 'Vegetables' THEN 'Vegan'
END AS DietaryPreference
FROM cte
but this seems like a messy way of doing it since there's no real reason to use string aggregration when what I'm really doing is set comparison. Is there a better way of doing this?
Assuming that simply someone that eats meat, regardless of what others they might not, is an Omnivore, and someone that eats Animal Byproducts (but not meat) is a Vegetarian, then you could do something like this:
WITH CTE AS(
SELECT V.PersonID,
COUNT(CASE V.Eats WHEN 'Meat' THEN 1 END) AS Meat,
COUNT(CASE V.Eats WHEN 'AnimalByproducts' THEN 1 END) AS AnimalByproducts,
COUNT(CASE V.Eats WHEN 'Vegetables' THEN 1 END) AS Vegetables
FROM (VALUES(1,'Meat'),
(1,'AnimalByproducts'),
(1,'Vegetables'),
(2,'AnimalByproducts'),
(2,'Vegetables'),
(3,'Vegetables'))V(PersonID,Eats)
GROUP BY V.PersonID)
SELECT C.PersonID,
CASE WHEN C.Meat > 0 THEN 'Omnivore'
WHEN AnimalByproducts > 0 THEN 'Vegetarian'
WHEN Vegetables > 0 THEN 'Vegan'
ELSE 'Very hungry'
END
FROM CTE C;
You can use conditional aggregation to process the hierarchy of values:
select personid,
coalesce(max(case when eats in ('Meat') then 'Omnivore' end),
max(case when eats in ('Animal Byproducts') then 'Vegetarian' end),
max(case when etas in ('Vegetables') then 'Vegan' end)
) as dietarypreference
from mytable
group by personid
You can also use just aggregation and no CASE.
The below prefixes each of the dietary preferences with a digit, so that the order of the selection hierarchy is also the lexicographic order and it can be used with MAX, and then finds the MAX and strips the digit off in the final SELECT
SELECT m.PersonId,
SUBSTRING(MAX(V.DietaryPreference), 2, 8000) AS DietaryPreference
FROM MyTable m
JOIN (VALUES ('Meat','3Omnivore'),
('AnimalByproducts','2Vegetarian'),
('Vegetables', '1Vegan')) V(Eats, DietaryPreference)
ON V.Eats = M.Eats
GROUP BY m.PersonId

SQL Calculate a sum by replacement

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;

Is there a way in SQL Server to solve for this conditional problem for prioritization?

I have the following schema of a table
Name Number
----- -------
A 200
A 322
B 200
B 322
C 322
C 200
D 322
D 234
I need some conditional statement to add another label column.
The conditions being that if a name has number 200, it should be prioritized over all other numbers and be labeled as 'Apple'
The next condition is that if a name does not have a number 200, the second priority is for number 322. So then those should be labeled as 'Mango'
I want my final result to look something like this which is grouped by name.
Name Number Label
----- ------- ------
A 200 Apple
B 200 Apple
C 200 Apple
D 322 Mango
With conditional aggregation:
select
name,
case min(case number when 200 then 0 when 322 then 1 end)
when 0 then 'Apple'
when 1 then 'Mango'
end Label
from tablename
group by name
See the demo.
Results:
> name | Label
> :--- | :----
> A | Apple
> B | Apple
> C | Apple
> D | Mango
If you want the column Number also do the aggregation inside a CTE:
with cte as (
select name, min(case number when 200 then 0 when 322 then 1 end) id
from tablename
group by name
)
select
name,
case id when 0 then 200 when 1 then 322 end Number,
case id when 0 then 'Apple' when 1 then 'Mango' end Label
from cte
See the demo.
Results:
> name | Number | Label
> :--- | -----: | :----
> A | 200 | Apple
> B | 200 | Apple
> C | 200 | Apple
> D | 322 | Mango
You can do something like that:
SELECT (CASE WHEN [Number]=200 THEN 'APPLE' WHEN [Number] =322 THEN 'MANGO' ELSE 'WHATEVER' END) [Label], [Number]
FROM [Yourtablename]
ORDER BY (CASE WHEN [Number]=200 THEN 2 WHEN [Number] =322 THEN 1 ELSE 0 END) DESC

A running summary of totals in SQL Server

Come up against an issue where I want to summarize results in a query.
Example as follows:
NAME | FRUIT | PRICE
-----+-------+------
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
DAVE | GRAPE | 3
DAVE | GRAPE | 3
DAVE | GRAPE | 3
This is my table at the moment, what i need though is to have a summary of Johns business, like below:
NAME | FRUIT | PRICE
-----+-------+------
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | TOTAL | 8
DAVE | GRAPE | 3
DAVE | GRAPE | 3
DAVE | GRAPE | 3
DAVE | TOTAL | 9
I have tried to group the information but it does not reflect what i want, plus if John were to have different fruit it would need to sum that up before it sums up the next part and it needs to have a running total for all values in the NAME field as there will be a number of customers.
Any advice would be great
EDIT
I have tried using Rollup but I keep getting totals of all values in a seperate column where as I would like to see it as the way it is formatted above
A solution with UNION and GROUP BY.
;WITH PricesWithTotals AS
(
SELECT
Name,
Fruit,
Price
FROM
YourTable
UNION ALL
SELECT
Name,
Fruit = 'TOTAL',
Price = SUM(Price)
FROM
YourTable
GROUP BY
Name
)
SELECT
Name,
Fruit,
Price
FROM
PricesWithTotals
ORDER BY
Name,
CASE WHEN Fruit <> 'Total' THEN 1 ELSE 999 END ASC,
Fruit
This will get you a running total per customer per fruit:
create table #Sales([Name] varchar(20), Fruit varchar(20), Price int)
insert into #Sales([Name], Fruit, Price)
values
('JOHN','APPLE',2),
('JOHN','APPLE',2),
('JOHN','APPLE',2),
('JOHN','APPLE',2),
('DAVE','GRAPE',3),
('DAVE','GRAPE',3),
('DAVE','GRAPE',3)
Select c.*
, SUM(Price) OVER (PARTITION BY c.[Name], c.[Fruit] ORDER BY c.[Name], c.[Fruit] rows between unbounded preceding and current ROW ) as RunningTotal
from #Sales c
order by c.[Name], c.[Fruit] asc
drop table #Sales
Output:
The solution to your problem is GROUPING SETS. However, your rows are not unique. Alas, so this adds a unique value, just so you can keep your original rows:
with t as (
select t.*, row_number() over (order by (select null)) as seqnum
from t
)
select name, ,
coalesce(fruit, 'Total') as fruit,
sum(price) as price
from t
group by grouping sets ( (name, fruit, seqnum), (name) )
order by name,
(case when fruit is not null then 1 else 2 end);

Sql query for getting group by on 2 columns

I am expecting the result of having count of 2 different columns values
Name | fruits
----------------
Vishal | orange
Manish | orange
Vishal | apple
Manish | orange
Manish | apple
Vishal | orange
Vishal | mango
Vishal | banana
Result should be
Name | Orange count | Apple count| mango | banana
--------------------------
Vishal | 2 | 1 | 1 | 1
Manish | 2 | 1 | 0 | 0
Another result should be
name | fruits
---------------
Vishal | orange, Apple , mango, banana
Manish | orange , Apple
You can use conditional aggregation for this:
select name,
count(case when fruits = 'orange' then 1 end) as orange_count,
count(case when fruits = 'apple' then 1 end) as apple_count,
count(case when fruits = 'mango' then 1 end) as mango_count,
count(case when fruits = 'banana' then 1 end) as banana_count
from the_table
group by name;
Some DBMS also support the ANSI SQL filter clause which makes this a bit more readable:
select name,
count(*) filter (where fruits = 'orange') as orange_count,
count(*) filter (where fruits = 'apple') as apple_count,
count(*) filter (where fruits = 'mango') as mango_count,
count(*) filter (where fruits = 'banana') as banana_count
from the_table
group by name;
Here is a generic pivot query which should work across most RDBMS:
SELECT Name,
SUM(CASE WHEN fruits = 'orange' THEN 1 ELSE 0 END) AS orange_count,
SUM(CASE WHEN fruits = 'apple' THEN 1 ELSE 0 END) AS apple_count,
SUM(CASE WHEN fruits = 'mango' THEN 1 ELSE 0 END) AS mango_count,
SUM(CASE WHEN fruits = 'banana' THEN 1 ELSE 0 END) AS banana_count
FROM yourTable
GROUP BY Name
If you are using SQL Server, Oracle, or Postgres, there are built-in PIVOT functions which can simplify this and possibly improve performance as well.