Select multiple sums with MySQL query and display them in separate columns - mysql

Let's say I have a hypothetical table like so that records when some player in some game scores a point:
name points
------------
bob 10
mike 03
mike 04
bob 06
How would I get the sum of each player's scores and display them side by side in one query?
Total Points Table
bob mike
16 07
My (pseudo)-query is:
SELECT sum(points) as "Bob" WHERE name="bob",
sum(points) as "Mike" WHERE name="mike"
FROM score_table

You can pivot your data 'manually':
SELECT SUM(CASE WHEN name='bob' THEN points END) as bob,
SUM(CASE WHEN name='mike' THEN points END) as mike
FROM score_table
but this will not work if the list of your players is dynamic.

In pure sql:
SELECT
sum( (name = 'bob') * points) as Bob,
sum( (name = 'mike') * points) as Mike,
-- etc
FROM score_table;
This neat solution works because of mysql's booleans evaluating as 1 for true and 0 for false, allowing you to multiply truth of a test with a numeric column. I've used it lots of times for "pivots" and I like the brevity.

Are the player names all known up front? If so, you can do:
SELECT SUM(CASE WHEN name = 'bob' THEN points ELSE 0 END) AS bob,
SUM(CASE WHEN name = 'mike' THEN points ELSE 0 END) AS mike,
... so on for each player ...
FROM score_table
If you don't, you still might be able to use the same method, but you'd probably have to build the query dynamically. Basically, you'd SELECT DISTINCT name ..., then use that result set to build each of the CASE statements, then execute the result SQL.

This is called pivoting the table:
SELECT SUM(IF(name = "Bob", points, 0)) AS points_bob,
SUM(IF(name = "Mike", points, 0)) AS points_mike
FROM score_table

SELECT sum(points), name
FROM `table`
GROUP BY name
Or for the pivot
SELECT sum(if(name = 'mike',points,0)),
sum(if(name = 'bob',points,0))
FROM `table

you can use pivot function also for the same thing .. even by performance vise it is better option to use pivot for pivoting... (i am talking about oracle database)..
you can use following query for this as well..
-- (if you have only these two column in you table then it will be good to see output else for other additional column you will get null values)
select * from game_scores
pivot (sum(points) for name in ('BOB' BOB, 'mike' MIKE));
in this query you will get data very fast and you have to add or remove player name only one place
:)
if you have more then these two column in your table then you can use following query
WITH pivot_data AS (
SELECT points,name
FROM game_scores
)
SELECT *
FROM pivot_data
pivot (sum(points) for name in ('BOB' BOB, 'mike' MIKE));

Related

Changing field fill of max value in column groups on SSRS 2014

I have a table in SSRS that has both row and column groups.
For each row group [Cat], I need to highlight the highest value in the column group, which is the sum of all counts for that category in a given month.
Can't for the life of me figure it out, so if anyone could help that would be great!
Thanks
Example of dataset
This is what I'm aiming for
Table in Design View
Current outcome
The problem you will face is that you will have to try to use nested aggregates with scopes defined. This might be possible (but I don't think it is...)
There is a fairly simple way to fix it though. I can;t give an exact answer as I don;t know what your dataset looks like but typically you would have to make some changes to your dataset, then its simple.
So assuming your dataset looks something like this
Cat myDate counts
A 20171001 90
A 20171001 6
B 20171001 18
C 20171001 1
A 20171101 100
A 20171101 20
....
....
Then aggregate everything so the report does not have to do any real aggregation with something like
SELECT
*
, max(counts) OVER(PARTITION BY Cat) as maxInCat
FROM (
SELECT
Cat, myDate
, SUM(counts) as counts
FROM myTable
GROUP BY Cat, myDate
) x
This will give you a dataset with an additional column maxInCat. This column will contain the maximum value in each category so we can compare against this in the report.
The expression can then be something like
=IIF(SUM(Fields!counts.Value)>0 and SUM(Fields!counts.Value) = Fields!maxInCat.Value, "Yellow", Nothing)
EDIT
I've updated the actual backcolor expression as it didn't account for blanks/zeros
Ignoring the fact the the columns are not sorted as I don't have time, here's the result
Here's an answer that I think does what you need:
declare #Table as table
(
[Cat] char(1),
[Sector] tinyint,
[Counts] int,
[Date] date
);
insert into #Table
(
[Cat],
[Sector],
[Counts],
[Date]
)
values
('A', 1, 4103, '2017-10-01'),
('A', 1, 3001, '2017-11-01'),
('A', 1, 1128, '2017-12-01'),
('A', 1, 5917, '2018-01-01'),
('A', 1, 9594, '2018-02-01'),
...
So you know where the data is coming from.
with [AggregatedData] as
(
select
t.Cat,
t.Sector,
t.Counts,
t.[Date],
sum(t.Counts) over (partition by t.Cat, t.[Date]) as [SumCounts]
from #Table as [t]
)
select
ad.Cat,
ad.Sector,
ad.Counts,
ad.[Date],
ad.SumCounts,
max(ad.SumCounts) over (partition by ad.[Date]) as [MaxSumCounts]
from [AggregatedData] as [ad]
Then in SSRS, you can use:
=iif(IsNothing(Fields!SumCounts.Value) = FALSE AndAlso Fields!SumCounts.Value = Fields!MaxSumCounts.Value, "Yellow", "Transparent")
Which gives:

How te get a percentage of rows in mysql

I have a table like this
user result
john +
mike -
john -
rita +
I want to get the percentage of - grouped by user. So for my example the result must be:
user %min
john 50%
mike 100%
rita 0%
Is that possible in mysql to create such a query?
Just use conditional aggregation. Here is a simple method:
select user, avg(result = '-') as percent_min
from t
group by user;
This will give the result as a value between 0 and 1, which can then be formatted as you desire.
Gordon's answer implies a database platform that implicitly casts a Boolean TRUE to 1 and a Boolean FALSE to 0. Which is not prescribed by the standard. Should you run into an error going something like "Function avg(boolean) does not exist", try a CASE expression:
WITH tb (usr,result) AS (
SELECT 'john','+'
UNION ALL SELECT 'mike','-'
UNION ALL SELECT 'john','-'
UNION ALL SELECT 'rita','+'
)
SELECT
usr
, AVG(CASE result WHEN '-' THEN 100 ELSE 0 END) AS percent_min
FROM tb
GROUP BY usr
ORDER BY usr;
Happy playing -
Marco

Select several max types for each datatype per distinct value in mysql

userid data_type, timespentaday
1 League of Legends 500
1 Hearthstone 1500
1 Hearthstone 1400
2 World of Warcraft 1200
1 Dota 2 100
2 Final Fantasy 500
1 Dota 2 700
Given this data. I would like to query the most time each user has spent on every.
Output desired:
User League Of Legends Hearthstone World of Warcraft Dota 2
1 500 1500 0 700
2 0 0 1200 0
Something along the lines of this is something I've tried
SELECT t1.* FROM user_info GROUP BY userid JOIN(
SELECT(
(SELECT max(timespentaday) where data_type='League of Legends'),
(SELECT max(timespentaday) where data_type='Hearhstone'),
(SELECT max(timespentaday) where data_type='Dota 2)'
FROM socialcount AS t2
) as t2
ON t1.userid = t2.userid
basically to do this you need the greatest n per group.. there is a good article on it but the gist is in mysql you have to use variables to even get close to this.. especially with doing a pivot on the table (a fake pivot since MySQL doesn't have native support for that).
SELECT userid,
MAX(CASE WHEN data_type = "League of Legends" THEN timespentaday ELSE 0 END) as "League of Legends",
MAX(CASE WHEN data_type = "Hearthstone" THEN timespentaday ELSE 0 END) as "Hearthstone",
MAX(CASE WHEN data_type = "Dota 2" THEN timespentaday ELSE 0 END) as "Dota 2",
MAX(CASE WHEN data_type = "World of Warcraft" THEN timespentaday ELSE 0 END) as "World of Warcraft",
MAX(CASE WHEN data_type = "Final Fantasy" THEN timespentaday ELSE 0 END) as "Final Fantasy"
FROM
( SELECT *, #A := if(#B = userid, if(#C = data_type, #A + 1, 1), 1) as count_to_use, #B := userid, #C := data_type
FROM
( SELECT userid, timespentaday, data_type
FROM gamers
CROSS JOIN(SELECT #A := 0, #B := 0, #C := '') temp
ORDER BY userid ASC, data_type ASC, timespentaday DESC
) t
HAVING count_to_use = 1
)t1
GROUP BY userid
DEMO
NOTE:
MySQL DOCS is quite clear on warnings about using user defined variables:
As a general rule, you should never assign a value to a user variable
and read the value within the same statement. You might get the
results you expect, but this is not guaranteed. The order of
evaluation for expressions involving user variables is undefined and
may change based on the elements contained within a given statement;
in addition, this order is not guaranteed to be the same between
releases of the MySQL Server. In SELECT #a, #a:=#a+1, ..., you might
think that MySQL will evaluate #a first and then do an assignment
second. However, changing the statement (for example, by adding a
GROUP BY, HAVING, or ORDER BY clause) may cause MySQL to select an
execution plan with a different order of evaluation.
I am not going to give you a query with the output format you desire, as implementing that pivot table is going to be a very ugly and poorly performing query, as well as something that is not scalable as the number of distinct games increases.
Instead, I will focus on how to query the data in the most straightforward manner and how to read it into a data structure that would be used by application logic to create the pivot view as desired.
First the query:
SELECT
userid,
data_type,
MAX(timespentaday) AS max_timespent
FROM social_count
GROUP BY userid, data_type
This would give results like
userid data_type max_timespent
------ --------- -------------
1 League of Legends 500
1 Hearthstone 1500
1 Dota 2 700
2 World of Warcraft 1200
2 Final Fantasy 500
Now when reading the results out of the database, you just read it into a structure that is useful. I will use PHP as example language, but this should be pretty easily portable to any langauge
// will hold distinct list of all available games
$games_array = array();
// will hold user data from DB
$user_data = array();
while ($row = /* your database row fetch mechanism here */) {
// update games array as necessary
if (!in_array($row['data_type'], $games_array)) {
// add this game to $games_array as it does not exist there yet
$games_array[] = $row['data_type'];
}
// update users array
$users[$row['userid']][$row['data_type']] = $row['max_timespent'];
}
// build pivot table
foreach($users as $id => $game_times) {
// echo table row start
// echo out user id in first element
// then iterate through available games
foreach($games_array as $game) {
if(!empty($game_times[$game])) {
// echo $game_times['game'] into table element
} else {
// echo 0 into table element
}
}
// echo table row end
}
You will not be able to build a query with a dynamic number of columns. You can do this query if you already know the game list, which I guess is not what you need.
BUT you can always post-process your results with any programming language, so you only have to retrieve the data.
The SQL query would look like this:
SELECT
userid AS User,
data_type AS Game,
max(timespentaday) AS TimeSpentADay
FROM
my_table
GROUP BY
userid
data_type
Then iterate over the results to fill any interface you want
OR
If and only if you can't afford any post-processing of any kind, you can retrieve the list of games first THEN you can build a query like the query below. Please bear in mind that this query is a lot less maintainable than the previous (beside being more difficult to build) and can and will cause you a lot of pain later in debugging.
SELECT
userid AS User,
max(CASE
WHEN data_type = 'Hearthstone' THEN timespentaday
ELSE NULL
END) AS Hearthstone,
max(CASE
WHEN data_type = 'League Of Legends' THEN timespentaday
ELSE NULL
END) AS `League Of Legends`,
...
FROM
my_table
GROUP BY
userid
The CASE contstruction is like an if in a procedural programming language, the following
CASE
WHEN data_type = 'League Of Legends' THEN timespentaday
ELSE NULL
END
Is evaluated to the value of timespentaday if the game is League Of Legends, and to NULL otherwise. The max aggregator simply ignore the NULL values.
Edit: added warning on the second query to explain the caveat of using a generated query thanks to Mike Brant's comment

How to do this query against MySQL database table?

I was given a task to show the CPU usage trend as part of a building process which also do regression test.
Each individual test case run has a record in the table RegrCaseResult. The RegrCaseResult table looks something like this:
id projectName ProjectType returnCode startTime endTime totalMetrics
1 'first' 'someType' 16 'someTime' 'someOtherTime' 222
The RegrCaseResult.totalMetrics is a special key which links to another table called ThreadMetrics through ThreadMetrics.id.
Here is how ThreadMetrics will look like:
id componentType componentName cpuTime linkId
1 'Job Totals' 'Job Totals' 'totalTime' 34223
2 'parser1' 'parser1' 'time1' null
3 'parser2' 'generator1' 'time2' null
4 'generator1' 'generator1' 'time3' null
------------------------------------------------------
5 'Job Totals' 'Jot Totals' 'totalTime' 9899
...
The rows with the compnentName 'Job Totals' is what the totalMetrics from RegrCaseResult table will link to and the 'totalTime' is what I am really want to get given a certain projectType. The 'Job Totals' is actually a summation of the other records - in the above example, the summation of time1 through time3. The linkId at the end of table ThreadMetrics can link back to RegrCaseResult.id.
The requirements also states I should have a way to enforce the condition which only includes those projects which have a consistent return code during certain period. That's where my initial question comes from as follows:
I created the following simple table to show what I am trying to achieve:
id projectName returnCode
1 'first' 16
2 'second' 16
3 'third' 8
4 'first' 16
5 'second' 8
6 'first' 16
Basically I want to get all the projects which have a consistent returnCode no matter what the returnCode values are. In the above sample, I should only get one project which is "first". I think this would be simple but I am bad when it comes to database. Any help would be great.
I tried my best to make it clear. Hope I have achieved my goal.
Here is an easy way:
select projectname
from table t
group by projectname
having min(returncode) = max(returncode);
If the min() and max() values are the same, then all the values are the same (unless you have NULL values).
EDIT:
To keep 'third' out, you need some other rule, such as having more than one return code. So, you can do this:
select projectname
from table t
group by projectname
having min(returncode) = max(returncode) and count(*) > 1;
select projectName from projects
group by projectName having count(distinct(returnCode)) = 1)
This would also return projects which has only one entry.
How do you want to handle them?
Working example: http://www.sqlfiddle.com/#!2/e7338/8
This should do it:
SELECT COUNT(ProjectName) AS numCount, ProjectName FROM (
SELECT ProjectName FROM Foo
GROUP BY ProjectName, ReturnCode
) AS Inside
GROUP BY Inside.ProjectName
HAVING numCount = 1
This groups all the ProjectNames by their names and return codes, then selects those that only have a single return code listed.
SQLFiddle Link: http://sqlfiddle.com/#!2/c52b6/11/0
You can try something like this with Not Exists:
Select Distinct ProjectName
From Table A
Where Not Exists
(
Select 1
From Table B
Where B.ProjectName = A.ProjectName
And B.ReturnCode <> A.ReturnCode
)
I'm not sure exactly what you're selecting, so you can change the Select statement to what you need.

Need Help With Simple MySQL SELECT

I've got a table of crime data. In a simplified version, it would look like this:
Table Headings:
crime_id, neighborhood, offense
Table Data:
- 1, Old Town, robbery
- 2, Bad Town, theft
- 3, Bad Town, theft
- 4, Uptown, stolen auto
If I SELECT * FROM mytable WHERE offense ='theft', then the results for Bad Town are returned. But, I'm making a ranking, so what I'm really interested in is:
Old Town: 0
Bad Town: theft
Bad Town: theft
Uptown: 0
How do I write a SELECT statement that returns cases where there are thefts, but also returns neighborhoods that don't have an entry for the specified offense?
UPDATE: This my actual SELECT. I'm having problems applying the solution that p.campbell and Gratzy were so kind to post to this SELECT. How do I apply the CASE statement with the COUNT(*)?
SELECT
cbn.neighborhoods AS neighborhoods,
COUNT(*) AS offenses,
TRUNCATE(((na.neighborhood_area_in_sq_meters /1000) * 0.000386102159),2) AS sq_miles,
( COUNT(*) / ((na.neighborhood_area_in_sq_meters /1000) * 0.000386102159) ) AS offenses_per_sq_mile
FROM
wp_crime_by_neighborhood cbn, wp_neighborhood_area na
WHERE
cbn.offense='theft'
AND
cbn.neighborhoods = na.neighborhoods
GROUP BY
cbn.neighborhoods
ORDER BY
offenses_per_sq_mile DESC
If you're looking to make a ranking, wouldn't it be better to get the number of thefts in Bad Town rather than a row for each? Something like this:
select distinct mt.neighborhood, ifnull(total, 0)
from mytable mt
left join (
select neighborhood, count(*) as total
from mytable
where offense = 'theft'
group by neighborhood
) as t on t.neighborhood = mt.neighborhood
Based on the data you gave, this query should return:
Old Town: 0
Bad Town: 2
Uptown: 0
That seems more useful to me for making a ranking. You can easily throw an order by on there.
I would think using a case statement should do it.
http://dev.mysql.com/doc/refman/5.0/en/case-statement.html
something like
Select neighborhood,
case offense when 'theft' then offense else '0' end case
from table
Try this:
SELECT cbn.neighborhoods AS neighborhoods,
CASE WHEN IFNULL(COUNT(*),0) > 0 THEN CONCAT(COUNT(*), ' ', offense)
ELSE '0'
END AS offenses
--- ... and the rest of your query
FROM wp_crime_by_neighborhood cbn
INNER JOIN wp_neighborhood_area na
ON cbn.neighborhoods = na.neighborhoods
WHERE cbn.offense='theft'
GROUP BY cbn.neighborhoods
--ORDER BY offenses_per_sq_mile DESC