Join Tables to find SUM for Points available and points completed - sql

I have three tables: Achievements, Characters, and Character_Achievements table that store's the ID's of completed achievements and user id. I am looking to get each category, total amount of points possible and also the amount completed.
I am able to get each category and the amount of points possible but I am unsuccessful at retrieving the completed count as well.
I currently use this to get each category and the amount of points possible
SELECT achievements.category, SUM(points) AS Total
FROM achievements
GROUP BY achievements.category ORDER BY achievements._id asc
I get these results.
Category Total
Operations 50
Events 25
I can also get the amount of points completed
SELECT achievements.category, SUM(points) AS Completed
FROM achievements
LEFT JOIN character_achievements
ON character_achievements.achievements_id = achievements._id
LEFT JOIN character
ON character_achievements.character_id = character._id
WHERE character._id = '1'
which returns this but only the categories that are completed. How do I combine these two queries together.
Category Completed
Operations 50
Events 25
I've tried UNION but it does not return the results I need.
Here are my example tables
Achievements Table
Category Title Points
Operations Epic Enemies 25
Operations Explosive Conflict 25
Events Bounty Contract 25
Character_Achievements Table
Character Character_id Achievements_id
Operations 1 1
Events 1 3
The results I'm looking for would like this.
Results
Category Completed Total
Operations 25 50
Events 25 25
I am able

If I'm understanding your question correctly, you can use SUM with CASE:
SELECT a.category,
SUM(CASE WHEN ca.achievements_id is not null then points end) AS Completed,
SUM(points) Total
FROM achievements a
LEFT JOIN character_achievements ca
ON ca.achievements_id = a._id
GROUP BY a.category
ORDER BY a._id asc

Related

COUNT with multiple LEFT joins [duplicate]

This question already has answers here:
Two SQL LEFT JOINS produce incorrect result
(3 answers)
Closed 12 months ago.
I am having some troubles with a count function. The problem is given by a left join that I am not sure I am doing correctly.
Variables are:
Customer_name (buyer)
Product_code (what the customer buys)
Store (where the customer buys)
The datasets are:
Customer_df (list of customers and product codes of their purchases)
Store1_df (list of product codes per week, for Store 1)
Store2_df (list of product codes per day, for Store 2)
Final output desired:
I would like to have a table with:
col1: Customer_name;
col2: Count of items purchased in store 1;
col3: Count of items purchased in store 2;
Filters: date range
My query looks like this:
SELECT
DISTINCT
C_customer_name,
C.product_code,
COUNT(S1.product_code) AS s1_sales,
COUNT(S2.product_code) AS s2_sales,
FROM customer_df C
LEFT JOIN store1_df S1 USING(product_code)
LEFT JOIN store2_df S2 USING(product_code)
GROUP BY
customer_name, product_code
HAVING
S1_sales > 0
OR S2_sales > 0
The output I expect is something like this:
Customer_name
Product_code
Store1_weekly_sales
Store2_weekly_sales
Luigi
120012
4
8
James
100022
6
10
But instead, I get:
Customer_name
Product_code
Store1_weekly_sales
Store2_weekly_sales
Luigi
120012
290
60
James
100022
290
60
It works when instead of COUNT(product_code) I do COUNT(DSITINCT product_code) but I would like to avoid that because I would like to be able to aggregate on different timespans (e.g. if I do count distinct and take into account more than 1 week of data I will not get the right numbers)
My hypothesis are:
I am joining the tables in the wrong way
There is a problem when joining two datasets with different time aggregations
What am I doing wrong?
The reason as Philipxy indicated is common. You are getting a Cartesian result from your data thus bloating your numbers. To simplify, lets consider just a single customer purchasing one item from two stores. The first store has 3 purchases, the second store has 5 purchases. Your total count is 3 * 5. This is because for each entry in the first is also joined by the same customer id in the second. So 1st purchase is joined to second store 1-5, then second purchase joined to second store 1-5 and you can see the bloat. So, by having each store pre-query the aggregates per customer will have AT MOST, one record per customer per store (and per product as per your desired outcome).
select
c.customer_name,
AllCustProducts.Product_Code,
coalesce( PQStore1.SalesEntries, 0 ) Store1SalesEntries,
coalesce( PQStore2.SalesEntries, 0 ) Store2SalesEntries
from
customer_df c
-- now, we need all possible UNIQUE instances of
-- a given customer and product to prevent duplicates
-- for subsequent queries of sales per customer and store
JOIN
( select distinct customerid, product_code
from store1_df
union
select distinct customerid, product_code
from store2_df ) AllCustProducts
on c.customerid = AllCustProducts.customerid
-- NOW, we can join to a pre-query of sales at store 1
-- by customer id and product code. You may also want to
-- get sum( SalesDollars ) if available, just add respectively
-- to each sub-query below.
LEFT JOIN
( select
s1.customerid,
s1.product_code,
count(*) as SalesEntries
from
store1_df s1
group by
s1.customerid,
s1.product_code ) PQStore1
on AllCustProducts.customerid = PQStore1.customerid
AND AllCustProducts.product_code = PQStore1.product_code
-- now, same pre-aggregation to store 2
LEFT JOIN
( select
s2.customerid,
s2.product_code,
count(*) as SalesEntries
from
store2_df s2
group by
s2.customerid,
s2.product_code ) PQStore2
on AllCustProducts.customerid = PQStore2.customerid
AND AllCustProducts.product_code = PQStore2.product_code
No need for a group by or having since all entries in their respective pre-aggregates will result in a maximum of 1 record per unique combination. Now, as for your needs to filter by date ranges. I would just add a WHERE clause within each of the AllCustProducts, PQStore1, and PQStore2.

MAX SUM, when multiplying two values from seperate tables

I have been able to get the total value of the two values but I'm not sure how to get the max. So when I enter this code, I have 4 row with the correct values but I just want to display the row with the maximum value out of those 4 rows.
SELECT AC.ACTID, SUM(AC.HOURS_WORKED * AL.HOURLYRATE) TOTAL
FROM ACTION AC
INNER JOIN ALLOCATION AL
ON AC.ACTID = AL.ACTID
INNER JOIN EMPLOYEE E
ON E.EMPID = AL.EMPID
GROUP BY AC.ACTID
I have to also put in EMPID but I'm not worried about that because that part is fine. Also this is SQL code.
You are showing actions with their cumulated costs.
According to your query the action table contains hours_worked and this value applies to every single employee involved. E.g. with hours_worked = 5 and three employees on that action, there were 15 hours worked.
Then there is the allocation table allowing many employees to work on one action on one hand and one employee to participate on many actions on the other (m:n relation). The employees are thus grouped per action. Say, in the example of three employees, one is allocated with an hourlyrate of 100 and the other two are allocated with an hourlyrate of 200. Then you have a total of 1 * 5 * 100 + 2 * 5 * 200 = 2500.
You are selecting many actions and you only want to show the top one(s) according to the calculated totals. If you have four actions for instance with the totals 1000, 2000, 2500, and again 2500, you want to show the two actions with 2500.
In Oracle (and standard SQL for that matter), you use FETCH FIRST ROW WITH TIES for that:
SELECT
ac.actid,
SUM(ac.hours_worked * al.hourlyrate) AS total
FROM action ac
INNER JOIN allocation al ON ac.actid = al.actid
INNER JOIN employee e ON e.empid = al.empid
GROUP BY ac.actid
ORDER BY total DESC
FETCH FIRST ROW WITH TIES;
As there are multiple employees involved per action, you'll have to create a string with their list, if you want to show them with the action. Use LISTAGG for this.

Group by demands us to include all selected rows, when we need the results grouped by just one row

Following this programming exercise: SQL with Street Fighter, which statement is:
It's time to assess which of the world's greatest fighters are through
to the 6 coveted places in the semi-finals of the Street Fighter World
Fighting Championship. Every fight of the year has been recorded and
each fighter's wins and losses need to be added up.
Each row of the table fighters records, alongside the fighter's name,
whether they won (1) or lost (0), as well as the type of move that
ended the bout.
id
name
won
lost
move_id
winning_moves
id
move
However, due to new health and safety regulations, all ki blasts have
been outlawed as a potential fire hazard. Any bout that ended with
Hadoken, Shouoken or Kikoken should not be counted in the total wins
and losses.
So, your job:
Return name, won, and lost columns displaying the name, total number of wins and total number of losses. Group by the fighter's
name.
Do not count any wins or losses where the winning move was Hadoken, Shouoken or Kikoken.
Order from most-wins to least
Return the top 6. Don't worry about ties.
How could we group the fighters by their names?
We have tried:
select name, won, lost from fighters inner join winning_moves on fighters.id=winning_moves.id
group by name order by won desc limit 6;
However it displays:
There was an error with the SQL query:
PG::GroupingError: ERROR: column "fighters.won" must appear in the
GROUP BY clause or be used in an aggregate function LINE 3: select
name, won, lost from fighters inner join winning_move...
In addition we have also tried to include all selected rows:
select name, won, lost from fighters inner join winning_moves on fighters.id=winning_moves.id
group by name,won,lost order by won desc limit 6;
But the results differ from the expected.
Expected:
name won lost
Sakura 44 15
Cammy 44 17
Rose 42 19
Karin 42 13
Dhalsim 40 15
Ryu 39 16
Actual:
name won lost
Vega 2 1
Guile 2 1
Ryu 2 1
Rose 1 0
Vega 1 0
Zangief 1 0
Besides we have read:
https://www.w3schools.com/sql/sql_join.asp
MySql Inner Join with WHERE clause
How to limit rows in PostgreSQL SELECT
https://www.w3schools.com/sql/sql_groupby.asp
GROUP BY clause or be used in an aggregate function
PostgreSQL column must appear in the GROUP BY clause or be used in an aggregate function when using case statement
must appear in the GROUP BY clause or be used in an aggregate function
I guess you need to have sum() to aggregate the ids wins n loss. In addition to that you dont need join as you dont wanna show the move in the first query
select name, sum(won) as wins,
sum(lost)
from fighters
group by name order by sum(won)
desc limit 6;

How to make one column fixed?

There is one scheme and different items inside it, so the scenario is that if user send SchemeID to the procedure then it should return the SchemeName(once) and all items inside a scheme i.e. DescriptionOfitem, Quantity, Rate, Amount... in this format
SchemeName DescriptionOfItems Quantity Unit Rate Amount
Scheme01 Bulbs 2 M2 200 400
Titles 10 M3 300 3000
SolarPanels 2 M2 1000 2000
Bricks 50 M9 50 2500
Total 7900
My try, it works but it also repeats the SchemeName for each row and can't find total
Select
Schemes.SchemeName,
ContractorsWorkDetails.ContractorsWorkDetailsItemDescription,
ContractorsWorkDetails.ContractorsWorkDetailsUnit,
ContractorsWorkDetails.ContractorsWorkDetailsItemQuantity,
ontractorsWorkDetails.ContractorsWorkDetailsItemRate,
ContractorsWorkDetails.ContractorsWorkDetailsAmount
From ContractorsWorkDetails
Inner Join Schemes
ON Schemes.pk_Schemes_SchemeID= ContractorsWorkDetails.fk_Schemes_ContractorsWorkDetails_SchemeID
Where ContractorsWorkDetails.fk_Schemes_ContractorsWorkDetails_SchemeID= 2
Update:
I tested the query as suggested below but it gives this kinda result
You can get the total using grouping sets. I would advise you to keep the schema name on each row. If you want it filtered out on certain rows, then do that at the application layer.
Now, having said that, I think this will do what you want in SQL:
Select (case when GROUPING(cwd.ContractorsWorkDetailsItemDescription) = 0
then 'Total'
when row_number() over (partition by s.SchemeName
order by cwd.ContractorsWorkDetailsItemDescription
) = 1
then s.SchemeName else ''
end) as SchemeName,
cwd.ContractorsWorkDetailsItemDescription,
cwd.ContractorsWorkDetailsUnit,
cwd.ContractorsWorkDetailsItemQuantity,
cwd.ContractorsWorkDetailsItemRate,
SUM(cwd.ContractorsWorkDetailsAmount) as ContractorsWorkDetailsAmount
From ContractorsWorkDetails cwd Inner Join
Schemes s
ON s.pk_Schemes_SchemeID = cwd.fk_Schemes_ContractorsWorkDetails_SchemeID
Where cwd.fk_Schemes_ContractorsWorkDetails_SchemeID = 2
group by GROUPING SETS ((s.SchemeName,
cwd.ContractorsWorkDetailsItemDescription,
cwd.ContractorsWorkDetailsUnit,
cwd.ContractorsWorkDetailsItemQuantity,
cwd.ContractorsWorkDetailsItemRate
), s.SchemeName)
Order By GROUPING(cwd.ContractorsWorkDetailsItemDescription),
s.SchemeName, cwd.ContractorsWorkDetailsItemDescription;
The reason you don't want to do this in SQL is because the result set no longer has a relational structure: the ordering of the rows is important.

Access SQL: How to specify which record to return based on the "more important" condition?

I have 2 tables (MS ACCESS):
Table "Orders"
OrderID Product Product_Group Client Client_Group Revenue
1 Cars Vehicles Men People 10 000
2 Houses NC_Assets Women People 15 000
3 Houses NC_Assets Partnersh Companies 12 000
4 Cars Vehicles Corps Companies 3 000
Table "Gouping"
Product Product_Group Client Client_Group Tax rate
Cars Companies Taxable 30%
Vehicles Companies Taxable 15%
Houses People Taxable 13%
Houses Women Taxable 15%
I want to join these tables to see which orders will fall into which taxable group. As you can see some products/clients are mapped differently than their groups -> if that is the case, the query should return only one record for this pair and exclude any pairing containing their groups. In pseudo-code:
If there's product-client grouping, return this record Else
If there's product-client grouping ---//----- else
If there's product group - client ----///-----else
If there's product group-client group ---///----
End if * 4
In that order.
Now my query (pseudo):
SELECT [Orders].*, [Grouping].* FROM [Orders] LEFT JOIN [Grouping] ON
(([Orders].Product = [Grouping].Product OR [Orders].Product_Group = [Grouping].Product_Group) AND
([Orders].Client = [Grouping].Client OR [Orders].Client_Group = [Grouping].Client_Group))
Returns both Cars-Companies and Vehicles-Companies. I'm out of ideas how to set it up to get only the most granular records from each combination. UNION? NOT EXISTS?
Any help appreciated.
I want to join these tables to see how many orders qualify as good,
mediocre etc.
Sounds like you want counts of the particular conditions...Assuming you have a SUM and CASE (I haven't written queries for MS Access in about 10 years...), here's some pseudo-code that should get you started:
SELECT SUM(CASE WHEN {mediocre-conditions} THEN 1 ELSE 0 END) AS MediocreCount,
SUM(CASE WHEN {good-conditions} THEN 1 ELSE 0 END) AS GoodCount,
SUM(CASE WHEN {great-conditions} THEN 1 ELSE 0 END) AS GreatCount
FROM [Orders] LEFT JOIN [Grouping] ON (([Orders].Product = [Grouping].Product OR [Orders].Product_Group = [Grouping].Product_Group) AND ([Orders].Client = [Grouping].Client OR [Orders].Client_Group = [Grouping].Client_Group))
[update] I don't like giving bad answers, so did a quick look...based on this link: Does MS Access support "CASE WHEN" clause if connect with ODBC?, it appears you may be able to do:
SELECT SUM(IIF({mediocre-conditions},1,0)) AS MediocreCount,
SUM(IIF({good-conditions},1,0)) AS GoodCount,
SUM(IIF({great-conditions},1,0)) AS GreatCount