does count automatically sum up similar values without a group by statement - mysql

THIS IS THE INPUT
team_1 team_2 winner
Aus India India
Eng NZ NZ
India SL India
SA Eng Eng
SL Aus Aus
OUTPUT
team_name matches_played no_of_wins
India 2 2
SL 2 NULL
SA 1 NULL
Eng 2 1
Aus 2 1
NZ 1 1
This is the MYSQL solution for the problem:
WITH CTE AS (SELECT team_1 team_name,winner FROM icc_world_cup
UNION ALL
SELECT team_2 team_name,winner FROM icc_world_cup)
SELECT DISTINCT team_name, # first column
COUNT(team_name) as Macthes_played, #second column
(SELECT COUNT(*) FROM
(SELECT IF(team_1=winner,team_1,team_2) win_team FROM icc_world_cup )a
WHERE team_name=win_team GROUP BY win_team) no_of_wins #third column
FROM CTE GROUP BY team_name
The above output is what I got from the code which I have written but the problem is
If I remove the GROUP BY statement in the third column that is
GROUP BY win_team
Then the output was something like this
team_name matches_played no_of_wins
India 2 2
SL 2 0
SA 1 0
Eng 2 1
Aus 2 1
NZ 1 1
How the count is able sum up team india's wins that is 2 without a group by statement, does it have something to with the where clause condition and
NOTICE that the NULL values in the third column were replaced by 0's.
How is it possible that without a group by statement my count function is able to sum up similar values and how the null are changed to 0.

I would use a union approach here:
SELECT team_name, COUNT(*) AS matches_played, SUM(win) AS no_of_wins
FROM
(
SELECT team_1 AS team_name, IF(team_1 = winner, 1, 0) AS win FROM yourTable
UNION ALL
SELECT team_2, IF(team_2 = winner, 1, 0) FROM yourTable
) t
GROUP BY team_name;

Related

Aggregate information from one table to another with a different “layout” (mysql)

this is my starting table which provides sales information by Id.
Id
Store_Name
Market
Sales
Main_Product
1
StoreA
Rome
10
a
2
StoreB
Rome
15
b
3
StoreC
Rome
9
c
4
Mag1
Paris
10
a
5
Mag2
Paris
23
b
6
Mag3
Paris
12
c
7
Shop1
London
11
a
8
Shop2
London
31
b
9
Shop3
London
45
c
10
Shop4
London
63
d
In order to build a report and create some dynamic sentences, I will need the dataset to be "paginated" as per below table:
Id
Dimension
Dimension_Name
Sales
Main_Product
1
ShoppingCentre
StoreA
10
a
1
Market
Rome
34
a
2
ShoppingCentre
StoreB
15
b
2
Maket
Rome
34
b
3
ShoppingCentre
StoreC
9
c
3
Market
Rome
34
c
Do you have any tip about how to build the last table starting from the first one?
To sum-up:
The new table will be always by Id
Aggregation of market sales happens at row level where every single shopping centre is located
This is the query that I have built so far but wondering if there is a better and more efficient way to accomplish the same:
with store_temp_table as (
select
id
,Store_Name
,Market
, Main_Product
, sum(Sales) as Sales
from Production_Table
where 1=1
group by
1,2,3,4
)
, market_temp_table as (
select
market
, sum(Sales) as Sales
from Production_Table
where 1=1
group by
1
)
, store_temp_table_refined as(
Select
a.id
,a.Main_Product
, 'ShoppingCentre' as Dimension_Name
,SUM(a.Sales) as Sales
FROM store_temp_table a INNER JOIN
market_temp_table b on a.market = b.market
group by
1,2,3
)
, market_temp_table_refined as (
Select
a.id
,a.Main_Product
, 'Market' as DimensionName
,SUM(b.Sales) as Sales
FROM store_temp_table a INNER JOIN
market_temp_table b on a.market = b.market
group by
1,2,3
)
select * from store_temp_table_refined
union all
select * from market_temp_table_refined
Thank you
Use a CTE that returns the dimensions that you want and cross join it to a query that returns the columns of the table and an additional column with the total sales of each market:
WITH Dimensions(id, Dimension) AS (VALUES
ROW(1, 'ShoppingCentre'),
ROW(2, 'Market')
)
SELECT p.Id,
d.Dimension,
CASE d.id WHEN 1 THEN p.Store_Name ELSE p.Market END Dimension_Name,
CASE d.id WHEN 1 THEN p.Sales ELSE p.MarketSales END Sales,
p.Main_Product
FROM Dimensions d
CROSS JOIN (SELECT *, SUM(Sales) OVER (PARTITION BY Market) AS MarketSales FROM Production_Table) p
ORDER BY p.id, d.id;
Or, with UNION ALL:
SELECT Id,
'ShoppingCentre' Dimension,
Store_Name Dimension_Name,
Sales,
Main_Product
FROM Production_Table
UNION ALL
SELECT Id,
'Market',
Market,
SUM(Sales) OVER (PARTITION BY Market),
Main_Product
FROM Production_Table
ORDER BY Id,
CASE Dimension WHEN 'ShoppingCentre' THEN 1 WHEN 'Market' THEN 2 END;
See the demo.

SQL nested query under WHERE

One of the test questions came by with following schemas, to look for the best doctor in terms of:
Best scored;
The most times/attempts;
For each medical procedures (in terms of name)
[doctor] table
id
first_name
last_name
age
1
Phillip
Singleton
50
2
Heidi
Elliott
34
3
Beulah
Townsend
35
4
Gary
Pena
36
5
Doug
Lowe
45
[medical_procedure] table
id
doctor_id
name
score
1
3
colonoscopy
44
2
1
colonoscopy
37
3
4
ulcer surgery
98
4
2
angiography
79
5
3
angiography
84
6
3
embolization
87
and list goes on...
Given solution as follow:
WITH cte AS(
SELECT
name,
first_name,
last_name,
COUNT(*) AS procedure_count,
RANK() OVER(
PARTITION BY name
ORDER BY COUNT(*) DESC) AS place
FROM
medical_procedure p JOIN doctor d
ON p.doctor_id = d.id
WHERE
score >= (
SELECT AVG(score)
FROM medical_procedure pp
WHERE pp.name = p.name)
GROUP BY
name,
first_name,
last_name
)
SELECT
name,
first_name,
last_name
FROM cte
WHERE place = 1;
It'll mean a lot to be clarified on/explain on how the WHERE clause worked out under the subquery:
How it worked out in general
Why must we match the two pp.name and p.name for it to reflect the correct rows...
...
WHERE
score >= (
SELECT AVG(score)
FROM medical_procedure pp
WHERE pp.name = p.name)
...
Thanks a heap!
Above is join with doctor and medical procedure and group by procedure name and you need doctor names with most attempt and best scored.
Subquery will join by procedure avg score and those who have better score than avg will be filtered.
Now there can be multiple doctor better than avg so taken rank by procedure count so most attempted will come first and then you taken first to pick top one

Mysql Select rows based on whether other column is null

I have a legacty table "wages" that I cannot change and I want to get results from this table such that i get allowedWage for a country based on whether there is a value for farmer or not otherwise get the allowedWage for worker
Input
id country farmer worker allowedWage
1 US 1 null 100
2 US null 1 50
3 AU 1 null 60
4 CA null 1 80
Expected Output
id country allowedWage
1 US 100
3 AU 60
4 CA 80
so I wrote the following query if someone wants to find the wage for country US, AU, CA and IN
select id, country, allowedWage from wages
where country in ('US', 'AU', 'CA', 'IN')
and ((farmer = 1 and worker is null) or (worker = 1 or farmer is null))
but this obviously gives all row and I am trying to figure out if there is a way to exclude worker data for a country if there is value of farmer
Actual Output
id country allowedWage
1 US 100
2 US 50
3 AU 60
4 CA 80
You can do it with conditional aggregation:
SELECT country,
COALESCE(MAX(CASE WHEN farmer THEN allowedWage END), MAX(allowedWage)) allowedWage
FROM wages
GROUP BY COUNTRY
For MySql 8.0+ you can do it with FIRST_VALUE() window function:
SELECT DISTINCT country,
FIRST_VALUE(allowedWage) OVER (PARTITION BY country ORDER BY farmer = 1 DESC) allowedWage
FROM wages
See the demo.
Results:
> country | allowedWage
> :------ | ----------:
> AU | 60
> CA | 80
> US | 100
Hmmm . . . this a prioritization query. You can use not exists to select the workers where necessary:
select id, country, allowedWage
from t
where farmer = 1 or
not exists (select 1
from t t2
where t2.country = t.country and t2.farmer = 1
);
Note that the worker column doesn't seem necessary because all the information needed is in the farmer column.
You could use row_number(), if you are running MySQL 8.0.
select *
from (
select t.*,
row_number() over(partition by country order by (farmer <=> 1) desc) rn
from mytable t
)
where rn = 1

Count only if field is filled and same as other field value

I have a question about an sql query i want to make. Supose i have an column with the follow values in table: school with column: grades.
SUI grades | Score
2 9 2
2 9
5 4 1
5 4 1
5 4
6 1 1
6 1
And Table Grade_scores
id score_1 score_2
1 4 1
Now i wan't an output where it groups the SUI that counts grades only if Score is filled and school.grades is same as Grade_scores.score_1 OR Grade_scores.score_2 . So my output will be:
SUI Count
5 2
6 1
The code so far...
SELECT SUI, Count(Grades)
FROM mytable
WHERE Score <> ''
GROUP BY SUI
You need to JOIN your school and grade_scores tables on grades being IN the two values in grade_scores:
SELECT s.SUI, COUNT(s.grades) AS `Count`
FROM grade_scores gs
JOIN school s ON s.Score IS NOT NULL AND s.grades IN (gs.score_1, gs.score_2)
GROUP BY s.SUI
Output:
SUI Count
5 2
6 1
Demo on dbfiddle
Note This query assumes the empty values in the Score column are NULL. If they are an empty string, replace s.Score IS NOT NULL with s.Score != ''
I prefer to do this using exists, because a join approach would double count if there are duplicate values in score_1/score_2:
select s.sui, count(*)
from scores s
where s.score is not null and
s.score in (select 1
from grade_scores gs
where s.grades in (gs.score_1, gs.score_2)
)
group by s.sui;
(untested):
SELECT SUI, count(GRADES) As Count
FROM school
INNER JOIN Gradescores ON s.grades = score_1 OR s.grades = score_2
WHERE Score>'' AND NOT Score IS NULL
GROUP BY SUI

How to get count greater than and less than average values group by name

I have a data set with name and their transaction ,how to get average and count of transactions grater than that average and less than that average..
Name Transaction
John 12
John 34
John 45
John 66
John 32
chris 26
chris 54
chris 56
chris 99
chris 13
chris 4
kim 22
kim 34
kim 7
kim 11
kim 34
O/P will be
Name Avg Count_greater_than_Avg Count_Less_than_Avg
John 37.8 2 3
chris 42 3 3
kim 21.6 3 2
Thanks in advance..
Try this:
SELECT t1.Name, t2.Aver,
COUNT(CASE WHEN Transaction < Aver THEN 1 END) Count_Less_than_Avg,
COUNT(CASE WHEN Transaction > Aver THEN 1 END) Count_greater_than_Avg
FROM mytable AS t1
JOIN (
SELECT Name, AVG(Transaction * 1.0) AS Aver
FROM mytable
GROUP BY Name
) AS t2 ON t1.Name = t2.Name
GROUP By Name
You need a derived table in order to calculate the average value per Name. You can then JOIN the original table to this derived table and use conditional aggregation in order to calculate less than, greater than number of transactions.
Demo here
This basically first add a column Your_Avg using a correlated query, and then wrap it with another select to select the count of the occurrences of times smaller then avg and times larger.
SELECT tt.name,tt.Your_Avg,
count(CASE WHEN tt.Your_Avg > tt.Transaction then 1 end) as Greater_Then_Avg,
count(CASE WHEN tt.Your_Avg > tt.Transaction then 1 end) as Smaller_Then_Avg
FROM(
SELECT t.name,
(SELECT avg(s.transaction) FROM YourTable s WHERE s.name = t.name) as Your_Avg,
t.transaction
FROM YourTable) tt
GROUP BY tt.name