How to avoid calculating rows caused by the join inside SUM()? - mysql

Here is my database schema simplified:
// wallet
+----+--------+---------+
| id | credit | user_id |
+----+--------+---------+
| 1 | 1000 | 1 |
| 2 | 1500 | 2 |
+----+--------+---------+
// where_to_pay_ability
+----+-------------+-----------+
| id | business_id | wallet_id |
+----+-------------+-----------+
| 1 | 5 | 1 |
| 2 | 4 | 1 |
+----+-------------+-----------+
And this is the current query I have:
select sum(credit)
from wallet w
left join where_to_pay_ability wtpa on w.id = wtpa.wallet_id
where user_id = 1
It returs 2000. Becuase there are two rows inside where_to_pay_ability table. That's a wrong credit for me. I want to sum rows once inside wallet table. So, the expected result is 1000.
How can I do that?
It should be noted, I can do that left join with a sub-query that is GROUP BYed wallet_id (Or DISTINCT). But, I need to have those business_ids.
So, I need a condition inside the SUM() to avoid calculating rows caused by the join.

You would need to aggregate before joining:
select sum(credit)
from wallet w left join
(select wtpa.wallet_id, count(*) as cnt
from where_to_pay_ability wtpa
group by wtpa.wallet_id
) wtpa
on w.id = wtpa.wallet_id;
where user_id = 1;
In your particular example, though, you could use max() because there is only one row.

Related

How to join two table to display sales for each id

I was trying to display the total sale of each id by combining the two table by using the id. I have two table 1. user table, 2. sales table
//user table
--------------
| id | name |
---------------
| 1 | yuki |
| 2 | soman |
---------------
// sales table
--------------
| id | total|
---------------
| 1 | 300 |
| 2 | 23 |
| 1 | 500 |
---------------
With my query it only display 1 sale witch is sales for yuki.
SELECT i.name,SUM(ROUND(s.total),2)) AS sales
FROM user i
INNER JOIN sales s
ON i.id = s.id
--------------
| name | sales|
---------------
| yuki | 800 |
---------------
I want to display the output like this, what did I missed from my query?
--------------
| name | sales|
---------------
| yuki | 800 |
|soman | 23 |
---------------
Your query needs a group by clause:
SELECT u.name, SUM(ROUND(s.total),2)) AS sales
FROM user u
INNER JOIN sales s ON s.id = u.id
GROUP BY u.id, u.name
Such error is much easier to spot when sql mode ONLY_FULL_GROUP_BY is enabled.
As an alternative, you might want to consider a correlated subquery, which avoids outer aggregation (it actually behaves like a LEFT JOIN, which is - probably - closer to what you want):
SELECT u.*,
(SELECT SUM(ROUND(s.total),2)) FROM sales s WHERE s.id = u.id) AS sales
FROM user u
Side note: user is a language keyword, hence not a good choice for a column name. Consider using something else, such as users for example.

Mysql count of a subquery column with having

I have a table called giveaways and each giveaway can have contestants. I am trying to get the number of giveaways without winners. The contestants table has a field called winner that is 1 or 0.
My data looks like:
Giveaway Table
| id | name |
|----|------------|
| 1 | Giveaway 1 |
| 2 | Giveaway 2 |
Contestant Table
| id | giveaway_id|winner|
|----|------------|------|
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 3 | 2 | 0 |
This is my query :
SELECT (SELECT COUNT(id) FROM contestants c
WHERE c.giveaway_id = g.id AND winner = 1) as winners
FROM giveaways g
having winners = 0
Right now this will return multiple rows, I want to get the count of rows. I tried wrapping a count() around the winners subquery but that did not work.
In the example above this would be returned:
Results
| winners |
|---------|
| 0 |
| 0 |
I want just the count of rows which would be 2.
What's a better approach? Thx
I am trying to get the number of giveaways without winners.
Use NOT EXISTS with a correlated subquery :
SELECT
COUNT(*)
FROM
giveaways g
WHERE NOT EXISTS (
SELECT 1 FROM contestants WHERE giveaway_id = g.id AND winner = 1
)
The subquery ensures that there is no winning contestant for each giveaway to count.

MySQL Select from Multiple Tables and most recent record

I'm having issues with a select query and can't quite figure out how to fix. I have two tables:
TABLE_students
|--------|------------|--------|
| STU_ID | EMAIL | NAME |
|--------|------------|--------|
| 1 | a#e.com | Bob |
| 2 | b#e.com | Joe |
| 3 | c#e.com | Tim |
--------------------------------
TABLE_scores
|--------|------------|-------------|--------|
| SRE_ID | STU_ID | DATE | SCORE |
|--------|------------|-------------|--------|
| 91 | 2 | 2018-04-03 | 78 |
| 92 | 2 | 2018-04-06 | 89 |
| 93 | 3 | 2018-04-03 | 67 |
| 94 | 3 | 2018-04-06 | 72 |
| 95 | 3 | 2018-04-07 | 81 |
----------------------------------------------
I'm trying to select data from both tables but have a few requirements. I need to select the student even if they don't have a score in the scores table. I also only only want the latest scores record.
The query below only returns those students that have a score and it also duplicates returns a total of 5 rows (since there are five scores). What I want is for the query to return three rows (one for each student) and their latest score value (or NULL if they don't have a score):
SELECT students.NAME, scores.SCORE FROM TABLE_students as students, TABLE_scores AS scores WHERE students.STU_ID = scores.STU_ID;
I'm having difficulty figuring out how to pull all students regardless of whether they have a score and how to pull only the latest score if they do have one.
Thank you!
This is a variation of the greatest-n-per-group question, which is common on Stack Overflow.
I would do this with a couple of joins:
SELECT s.NAME, c1.DATE, c1.SCORE
FROM students AS s
LEFT JOIN scores AS c1 ON c1.STU_ID = s.STU_ID
LEFT JOIN scores AS c2 ON c2.STU_ID = s.STU_ID
AND (c2.DATE > c1.DATE OR c2.DATE = c1.DATE AND c2.SRE_ID > c1.SRE_ID)
WHERE c2.STU_ID IS NULL;
If c2.STU_ID is null, it means the LEFT JOIN matched no rows that have a greater date (or greater SRE_ID in case of a tie) than the row in c1. This means the row in c1 must be the most recent, because there is no other row that is more recent.
P.S.: Please learn the JOIN syntax, and avoid "comma-style" joins. JOIN has been standard since 1992.
P.P.S.: I removed the superfluous "TABLE_" prefix from your table names. You don't need to use the table name to remind yourself that it's a table! :-)
You could use correlated subquery:
SELECT *,
(SELECT score FROM TABLE_scores sc
WHERE sc.stu_id = s.stu_id ORDER BY DATE DESC LIMIT 1) AS score
FROM TABLE_students s

Querying MySQL tables for a item a user hasnt 'scored' yet

Tables
__________________ ________________________________
|______name________| |____________scores______________|
|___id___|__name___| |_id_|_user-id_|_name-id_|_score_|
| 1 | bob | | 1 | 3 | 1 | 5 |
| 2 | susan | | 2 | 1 | 3 | 4 |
| 3 | geoff | | 3 | 3 | 2 | 3 |
| 4 | larry | | 4 | 2 | 4 | 5 |
| 5 | peter | | 5 | 1 | 1 | 0 |
-------------------- ----------------------------------
Im looking to write a query that returns a RANDOM name from the 'name' table, that the user hasnt scored so far.
So given user '1' for example, it could return 'susan, larry or peter' as user '1' hasnt given them a score yet.
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id
WHERE votes.user_id = 1
AND (votes.score IS NULL);
So far I have this, but it doesnt seem to be working as I would like
(atm it doesnt return a random, but all, but this is wrong)
Any help would be appreciated.
If you are filtering on some field of outer joined table type of join is automatically changed to inner. In your case it's condition
votes.user_id = 1
So you need to move that condition from WHERE to ON
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id and votes.user_id = 1
WHERE (votes.score IS NULL);
Consider moving the condition from WHERE to JOIN ON clause since you are performing an OUTER JOIN else the effect would be same as INNER JOIN
LEFT JOIN votes
ON names.id = votes.name_id
AND votes.user_id = 1
WHERE votes.score IS NULL
ORDER BY RAND();
You could apply :
SELECT name FROM name join scores on name.id=scores.user_id WHERE scores.score=0
You can perform this as a sub-query
SELECT *
FROM names
WHERE id NOT IN (SELECT name_id FROM votes WHERE user_id=1)
ORDER BY RAND()
LIMIT 1

SQL: Keep subquery order in outer query

I am having issues trying to combine DISTINCT & ORDER BY. I have a Users table with the following attributes id, name & I have a Purchases table with the following attributes id,user_id,date_purchased,returned
I want to retrieve all unique Users that have a returned Purchase sorted by date_purchased.
Here is some sample data
Users
id | name
---+-----------
1 | Bob
2 | John
3 | Bill
4 | Frank
5 | Fred
6 | Al
Purchases
id | user_id | startdate | returned
-----+------------------+------------+---------------
100 | 1 | 2015-02-06 | true
101 | 1 | 2015-01-06 | true
102 | 1 | 2015-02-05 | false
103 | 2 | 2015-02-05 | false
104 | 2 | 2015-02-05 | false
105 | 3 | 2015-01-05 | true
106 | 3 | 2015-02-04 | true
107 | 4 | 2015-01-07 | true
108 | 5 | 2015-02-05 | false
109 | 6 | 2015-02-07 | false
110 | 6 | 2015-01-05 | true
The result should be the following user id's 1,3,4,6
Here is the query I wrote
SELECT DISTINCT (id) FROM (
SELECT users.id as id, purchases.startdate FROM
users INNER JOIN purchases on users.id=purchases.id
WHERE returned=true
ORDER BY startdate )
This query correctly returns the results; however it is in the incorrect order. Reading other answers I found that you can't maintain the subquery ordering. I tried to move the ordering to the outer query; however, startdate would also need to be present in the select query & that is not what I want
Just remove the subquery and use GROUP BY:
SELECT u.id as id
FROM users u INNER JOIN
purchases p
on u.id = p.id
WHERE returned = true
GROUP BY u.id
ORDER BY MIN(startdate);
You can only rely on the result set being in a particular order when you use ORDER BY for the outermost SELECT. There is no guarantee of ordering in any other case.
As a note: ordering usually does work with subquery (sadly, because many people look at the results from some queries and generalize to all of them). The problem in this case is the distinct. It rearranges the data (i.e. sorts it) to remove duplicates.
Gordon's script gives you the data you want, but to answer your question of how to maintain a subquery's order, you can pull the column you want to order by out of the subquery and then order by it.
SELECT DISTINCT (id), innerTable.startdate FROM (
SELECT users.id as id, purchases.startdate FROM
users INNER JOIN purchases on users.id=purchases.id
WHERE returned=true) as innerTable
ORDER BY innerTable.startdate