Getting sum of 2 columns each with unique condition - sql

I've two tables like this.
create table teams (
"ID" Integer NOT NULL ,
"STADIUM_ID" Integer NOT NULL ,
"NAME" Varchar2 (50) NOT NULL ,
primary key ("ID")
) ;
create table matches (
"ID" Integer NOT NULL ,
"WINNER_ID" Integer NOT NULL ,
"OPPONENT_ID" Integer NOT NULL ,
"WINNERSCORE" Integer,
"OPPONENTSCORE" Integer,
primary key ("ID","WINNER_ID","OPPONENT_ID")
) ;
They have the following data :
select * from matches;
ID WINNER_ID OPPONENT_ID WINNERSCORE OPPONENTSCORE
---------- ---------- ----------- ----------- -------------
1 5 2 5 2
2 4 5 1 0
3 3 2 1 0
4 3 2 1 0
5 1 2 2 0
6 3 1 2 1
select * from teams;
ID STADIUM_ID NAME
---------- ---------- -----------
1 1 Team1
2 3 Team2
3 4 Team3
4 2 Team4
5 5 Team5
I need to get the sum of the goals for each team.
For this aim, tried the following query and got the results below :
select name,
(select sum(opponentscore)
from matches
where opponent_id = teams.id) +
(select sum(winnerscore) from matches where winner_id = teams.id) sum
from teams;
NAME SUM
-------------------------------------------------- ----------
Team1 3
Team2
Team3
Team4
Team5 5
Do you have any suggestion ?

All you need is to calculate seperately opponentscore and winnerscore by each individual team, and combine them with UNION ALL :
select name, sum(score) total_score
from
(
select name, sum(winnerscore) score
from teams t join matches m on ( t.id = m.winner_id )
group by name
union all
select name, sum(opponentscore) score
from teams t join matches m on ( t.id = m.opponent_id )
group by name
)
group by name
order by 1;
SQL Fiddle Demo

you should use join and group by
select name, sum(matches.opponentscore) + sum(matches.winnerscore) my_sum
from matches
inner join teams on teams.id = matches.winner_id
group by teams.name

You could join table teams twice with table matches:
SELECT name, SUM(wonMatches.WINNERSCORE + lostMatches.OPPONENTSCORE) as goals
FROM (teams INNER JOIN matches as wonMatches ON teams.ID = wonMatches.WINNER_ID)
INNER JOIN matches as lostMatches ON teams.ID = lostMatches.OPPONENT_ID
GROUP BY name

My solution is : change your database schema. Restart thinking your app's requirements. This schema does not answer the value your user are expecting from you.
From what I see, I would say that you're trying to build an app for fans that want to track their team / favorite player progress so they can brag.
That being said, I would have, at the end, those tables :
fan
team (id_team)
player (id_player, id_team)
tournament (id_tournament)
match (id_match, id_tournament, start_on, id_team_home, id_team_visitor)
goals (id_match, id_player, goaled_on)
So now, I believe that writing your query would be much more simple. You'll just have to join team, player, count over goals and group by team.

The problem is with NULLs - the subqueries return NULL when no result is found, and NULL + anything == NULL.
Most straightforward fix is:
select name,
nvl(
(select sum(opponentscore) from matches where opponent_id = teams.id),
0
)
+
nvl(
(select sum(winnerscore) from matches where winner_id = teams.id),
0
) sum
from teams;
For performance reasons tohugh, you might want to consider using a joined query with GROUP BY as suggested by others.

Related

Filter data based on multiple rows SQL

This is probably as simple SQL query. I'm finding it little tricky, as it's been a while I've written SQL.
ID NAME VALUE
--- ------ -------
1 Country Brazil
1 Country India
2 Country US
2 EmpLevel 1
3 EmpLevel 3
Pseudo Query:
Select *
from table_name
where (country = US or country = Brazil)
and (Employee_level = 1 or Employee_level = 3)
This query should return
ID NAME VALUE
--- ------ -------
2 Country US
2 EmpLevel 1
(As record with ID - 2 has Country as 'US' and EmpLevel '1')
I went through couple SO posts as well.
Multiple row SQL Where clause
SQL subselect filtering based on multiple sub-rows
Evaluation of multiples 'IN' Expressions in 'WHERE' clauses in mysql
I assume you're expected results for the country should be US instead of Brazil. Here's one option using a join with conditional aggregation:
select y.*
from yourtable y join (
select id
from yourtable
group by id
having max(case when name = 'Country' then value end) in ('US','Brazil') and
max(case when name = 'EmpLevel' then value end) in ('1','3')
) y2 on y.id = y2.id
SQL Fiddle Demo

SQL - Computing overlap between Interests

I have a schema (millions of records with proper indexes in place) that looks like this:
groups | interests
------ | ---------
user_id | user_id
group_id | interest_id
A user can like 0..many interests and belong to 0..many groups.
Problem: Given a group ID, I want to get all the interests for all the users that do not belong to that group, and, that share at least one interest with anyone that belongs to the same provided group.
Since the above might be confusing, here's a straightforward example (SQLFiddle):
| 1 | 2 | 3 | 4 | 5 | (User IDs)
|-------------------|
| A | | A | | |
| B | B | B | | B |
| | C | | | |
| | | D | D | |
In the above example users are labeled with numbers while interests have characters.
If we assume that users 1 and 2 belong to group -1, then users 3 and 5 would be interesting:
user_id interest_id
------- -----------
3 A
3 B
3 D
5 B
I already wrote a dumb and very inefficient query that correctly returns the above:
SELECT * FROM "interests" WHERE "user_id" IN (
SELECT "user_id" FROM "interests" WHERE "interest_id" IN (
SELECT "interest_id" FROM "interests" WHERE "user_id" IN (
SELECT "user_id" FROM "groups" WHERE "group_id" = -1
)
) AND "user_id" NOT IN (
SELECT "user_id" FROM "groups" WHERE "group_id" = -1
)
);
But all my attempts to translate that into a proper joined query revealed themselves fruitless: either the query returns way more rows than it should or it just takes 10x as long as the sub-query, like:
SELECT "iii"."user_id" FROM "interests" AS "iii"
WHERE EXISTS
(
SELECT "ii"."user_id", "ii"."interest_id" FROM "groups" AS "gg"
INNER JOIN "interests" AS "ii" ON "gg"."user_id" = "ii"."user_id"
WHERE EXISTS
(
SELECT "i"."interest_id" FROM "groups" AS "g"
INNER JOIN "interests" AS "i" ON "g"."user_id" = "i"."user_id"
WHERE "group_id" = -1 AND "i"."interest_id" = "ii"."interest_id"
) AND "group_id" != -1 AND "ii"."user_id" = "iii"."user_id"
);
I've been struggling trying to optimize this query for the past two nights...
Any help or insight that gets me in the right direction would be greatly appreciated. :)
PS: Ideally, one query that returns an aggregated count of common interests would be even nicer:
user_id totalInterests commonInterests
------- -------------- ---------------
3 3 1/2 (either is fine, but 2 is better)
5 1 1
However, I'm not sure how much slower it would be compared to doing it in code.
Using the following to set up test tables
--drop table Interests ----------------------------
CREATE TABLE Interests
(
InterestId char(1) not null
,UserId int not null
)
INSERT Interests values
('A',1)
,('A',3)
,('B',1)
,('B',2)
,('B',3)
,('B',5)
,('C',2)
,('D',3)
,('D',4)
-- drop table Groups ---------------------
CREATE TABLE Groups
(
GroupId int not null
,UserId int not null
)
INSERT Groups values
(-1, 1)
,(-1, 2)
SELECT * from Groups
SELECT * from Groups
The following query would appear to do what you want:
DECLARE #GroupId int
SET #GroupId = -1
;WITH cteGroupInterests (InterestId)
as (-- List of the interests referenced by the target group
select distinct InterestId
from Groups gr
inner join Interests nt
on nt.UserId = gr.UserId
where gr.GroupId = #GroupId)
-- Aggregate interests for each user
SELECT
UserId
,count(OwnInterstId) OwnInterests
,count(SharedInterestId) SharedInterests
from (-- Subquery lists all interests for each user
select
nt.UserId
,nt.InterestId OwnInterstId
,cte.InterestId SharedInterestId
from Interests nt
left outer join cteGroupInterests cte
on cte.InterestId = nt.InterestId
where not exists (-- Correlated subquery: is "this" user in the target group?)
select 1
from Groups gr
where gr.GroupId = #GroupId
and gr.UserId = nt.UserId)) xx
group by UserId
having count(SharedInterestId) > 0
It appears to work, but I'd want to do more elaborate tests, and I've no idea how well it'd work against millions of rows. Key points are:
cte creates a temp table referenced by the later query; building an actual temp table might be a performance boost
Correlated subqueries can be tricky, but indexes and not exists should make this pretty quick
I was lazy and left out all the underscores, sorry
This is a bit confounding. I think the best approach is exists and not exists:
select i.*
from interest i
where not exists (select 1
from groups g
where i.user_id = g.user_id and
g.group_id = $group_id
) and
exists (select 1
from groups g join
interest i2
on g.user_id = i2.user_id
where g.user_id <> i.user_user_id and
i.interest_id = i2.interest_id
);
The first subquery is saying that the user is not in the group. The second is saying that the interest is shared with someone who is in the group.

How to list distinct column after a join and also count?

I have three tables:
team(ID, name)
goal(ID, team_ID, goalType_ID, date)
goalType(ID, name)
As you can see, team_ID is the ID of teams table, and goalType_ID is the ID of goalType table.
For all teams, I want to list the number of different types of goals that ever happened, 0 should appear if none.
We don't need to care about the goals table since we don't need the name of the type of goal so I've gotten to the follow code that only uses the first two tables:
SELECT team.ID, team.name, goal.goaType_ID
FROM team LEFT JOIN goal ON team.ID=goal.team_ID
What this results in is a three-column table of information I want, but I would like to count the number of DISTINCT goalTypes, and GROUP BY team.ID or team.name and keep it three columns and also if the result is null, show 0 (team might not have scored any goals).
The resulting table looks something like this:
team.ID team.name goalsType.ID
1 Team_1 1
2 Team_2 2
2 Team_2 2
2 Team_2 2
3 Team_3 4
4 Team_4 null
5 Team_5 null
6 Team_6 1
6 Team_6 2
6 Team_6 4
6 Team_6 3
7 Team_7 5
7 Team_7 4
8 Team_8 null
I have tried a combination of GROUP BY, DISTINCT, and COUNT, but still can't get a result I want.
Maybe I'm going about this all wrong? Any help would be appreciated, Thanks.
EDIT:
Based on Gordon Linoff's answer, I tried doing:
SELECT DISTINCT team.name, COUNT(goal.goalType_ID)
FROM team LEFT JOIN goal ON team.ID=goal.team_ID
GROUP BY team.ID, team.name
and it will give me:
Name #0
Team_1 1
Team_2 3
Team_3 1
Team_4 0
Team_5 0
Team_6 4
Team_7 1
Team_8 0
If I try to use "DISTINCT team.ID, DISTINCT team.name", it will error out.
Is this what you want?
SELECT team.ID, team.name, count(distinct goal.goalType_ID) as NumGoalTypes
FROM team LEFT JOIN
goal
ON team.ID = goal.team_ID
GROUP BY team.ID, team.name;
Try this http://sqlfiddle.com/#!3/8ec680/13
;WITH cte
AS (SELECT Row_number() OVER(partition BY tname
ORDER BY goalid), * from temp)--temp= Your join statement
SELECT CASE
WHEN a.goalid IS NULL THEN 0
ELSE a.row_n
END [count],
a.tid,
a.tname,
a.goalid
FROM cte a
JOIN (SELECT Max(row_n) row_n,
tname
FROM cte
GROUP BY tname) b
ON a.row_n = b.row_n
AND a.tname = b.tname

Different value counts on same column

I am new to Oracle. I have an Oracle table with three columns: serialno, item_category and item_status. In the third column the rows have values of serviceable, under_repair or condemned.
I want to run the query using count to show how many are serviceable, how many are under repair, how many are condemned against each item category.
I would like to run something like:
select item_category
, count(......) "total"
, count (.....) "serviceable"
, count(.....)"under_repair"
, count(....) "condemned"
from my_table
group by item_category ......
I am unable to run the inner query inside the count.
Here's what I'd like the result set to look like:
item_category total serviceable under repair condemned
============= ===== ============ ============ ===========
chair 18 10 5 3
table 12 6 3 3
You can either use CASE or DECODE statement inside the COUNT function.
SELECT item_category,
COUNT (*) total,
COUNT (DECODE (item_status, 'serviceable', 1)) AS serviceable,
COUNT (DECODE (item_status, 'under_repair', 1)) AS under_repair,
COUNT (DECODE (item_status, 'condemned', 1)) AS condemned
FROM mytable
GROUP BY item_category;
Output:
ITEM_CATEGORY TOTAL SERVICEABLE UNDER_REPAIR CONDEMNED
----------------------------------------------------------------
chair 5 1 2 2
table 5 3 1 1
This is a very basic "group by" query. If you search for that you will find plenty of documentation on how it is used.
For your specific case, you want:
select item_category, item_status, count(*)
from <your table>
group by item_category, item_status;
You'll get something like this:
item_category item_status count(*)
======================================
Chair under_repair 7
Chair condemned 16
Table under_repair 3
Change the column ordering as needed for your purpose
I have a tendency of writing this stuff up so when I forget how to do it, I have an easy to find example.
The PIVOT clause was new in 11g. Since that was 5+ years ago, I'm hoping you are using it.
Sample Data
create table t
(
serialno number(2,0),
item_category varchar2(30),
item_status varchar2(20)
);
insert into t ( serialno, item_category, item_status )
select
rownum serialno,
( case
when rownum <= 12 then 'table'
else 'chair'
end ) item_category,
( case
--table status
when rownum <= 12
and rownum <= 6
then 'servicable'
when rownum <= 12
and rownum between 7 and 9
then 'under_repair'
when rownum <= 12
and rownum > 9
then 'condemned'
--chair status
when rownum > 12
and rownum < 13 + 10
then 'servicable'
when rownum > 12
and rownum between 23 and 27
then 'under_repair'
when rownum > 12
and rownum > 27
then 'condemned'
end ) item_status
from
dual connect by level <= 30;
commit;
and the PIVOT query:
select *
from
(
select
item_status stat,
item_category,
item_status
from t
)
pivot
(
count( item_status )
for stat in ( 'servicable' as "servicable", 'under_repair' as "under_repair", 'condemned' as "condemned" )
);
ITEM_CATEGORY servicable under_repair condemned
------------- ---------- ------------ ----------
chair 10 5 3
table 6 3 3
I still prefer #Ramblin' Man's way of doing it (except using CASE in place of DECODE) though.
Edit
Just realized I left out the TOTAL column. I'm not sure there's a way to get that column using the PIVOT clause, perhaps someone else knows how. May also be the reason I don't use it that often.

Selecting and sorting data from a single table

Correction to my question....
I'm trying to select and sort in a query from a single table. The primary key for the table is a combination of a serialized number and a time/date stamp.
The table's name in the database is "A12", the columns are defined as:
Serial2D (PK, char(25), not null)
Completed (PK, datetime, not null)
Result (smallint, null)
MachineID (FK, smallint, null)
PT_1 (float, null)
PT_2 (float, null)
PT_3 (float, null)
PT_4 (float, null)
Since the primary key for the table is a combination of the "Serial2D" and "Completed", there can be multiple "Serial2D" entries with different values in the "Completed" and "Result" columns. (I did not make this database... I have to work with what I got)
I want to write a query that will utilize the value of the "Result" column ( always a "0" or "1") and retrive only unique rows for each "Serial2D" value. If the "Result" column has a "1" for that row, I want to choose it over any entries with that Serial that has a "0" in the Result column. There should be only one entry in the table that has a Result column entry of "1" for any Serial2D value.
Ex. table
Serial2d Completed Result PT_1 PT_2 PT_3 PT_4
------- ------- ------ ---- ---- ---- ----
A1 1:00AM 0 32.5 20 26 29
A1 1:02AM 0 32.5 10 29 40
A1 1:03AM 1 10 5 4 3
B1 1:04AM 0 29 4 1 9
B1 1:05AM 0 40 3 4 9
C1 1:06AM 1 9 7 6 4
I would like to be able to retrieve would be:
Serial2d Completed Result PT_1 PT_2 PT_3 PT_4
------- ------- ------ ---- ---- ---- ----
A1 1:03AM 1 10 5 4 3
B1 1:05AM 0 40 3 4 9
C1 1:06AM 1 9 7 6 4
I'm new to SQL and I'm still learning ALL the syntax. I'm finding it difficult to search for the correct operators to use since I'm not sure what I need, so please forgive my ignorance. A post with my answer could be staring me right in the face and i wouldn't know it, please just point me to it.
I appreciate the answers to my previous post, but the answers weren't sufficient for me due to MY lack of information and ineptness with SQL. I know this is probably insanely easy for some, but try to remember when you first started SQL... that's where I'm at.
Since you are using SQL Server, you can use Windowing Functions to get this data.
Using a sub-query:
select *
from
(
select *,
row_number() over(partition by serial2d
order by result desc, completed desc) rn
from a12
) x
where rn = 1
See SQL Fiddle with Demo
Or you can use CTE for this query:
;with cte as
(
select *,
row_number() over(partition by serial2d
order by result desc, completed desc) rn
from a12
)
select *
from cte c
where rn = 1;
See SQL Fiddle With Demo
You can group by Serial to get the MAX of each Time.
SELECT Serial, MAX([Time]) AS [Time]
FROM myTable
GROUP BY Serial
HAVING MAX(Result) => 0
SELECT
t.Serial,
max_Result,
MAX([time]) AS max_time
FROM
myTable t inner join
(SELECT
Serial,
MAX([Result]) AS max_Result
FROM
myTable
GROUP BY
Serial) m on
t.serial = m.serial and
t.result = m.max_result
group by
t.serial,
max_Result
This can be solved using a correlated sub-query:
SELECT
T.serial,
T.[time],
0 AS result
FROM tablename T
WHERE
T.result = 1
OR
NOT EXISTS(
SELECT 1
FROM tablename
WHERE
serial = T.serial
AND (
[time] > T.[time]
OR
result = 1
)
)