I have two tables, 'authorization' and 'transaction'.
authorization table
Auth_ID | User_ID | Auth_Hours
-------------------------------
5 | 1 | 60
6 | 2 | 40
7 | 3 | 50
transaction table
Auth_ID | User_ID | Used_Hours
-------------------------------
5 | 1 | 5
6 | 2 | 2
5 | 1 | 20
6 | 2 | 17
5 | 1 | 11
6 | 2 | 9
I want my query to sum Used_Hours and group by Auth_ID and User_ID from the transaction table. Here's the catch - I also want the query result to show users who have not used any of their Auth_Hours with a 0. See below:
QUERY
SELECT a.Auth_ID, a.USER_ID, a.Auth_Hours, SUM(t.Used_Hours)
FROM AUTHORIZATION a
JOIN TRANSACTION t
on a.Auth_ID = t.Auth_ID
and a.User_ID = t.User_ID
GROUP BY a.Auth_ID, a.USER_ID, a.Auth_Hours
Actual Result
Auth_ID | User_ID | Auth_Hours | Total Hours Used
-------------------------------------------------
5 | 1 | 60 | 36
6 | 2 | 40 | 28
Wanted Result
Auth_ID | User_ID | Auth_Hours | Total Hours Used
-------------------------------------------------
5 | 1 | 60 | 36
6 | 2 | 40 | 28
7 | 3 | 50 | 0
I would imagine the query to be relatively simple.
The JOIN statement is a shortcut for INNER JOIN which returns records that have matching values in both tables. If you want to return all records from one table and the matching records from the other table, then you should use an outer join (LEFT [OUTER] JOIN or RIGHT [OUTER] JOIN). Then you can use the IFNULL() or COALESCE()functions to convert NULLs to zeros:
SELECT a.Auth_ID, a.USER_ID, a.Auth_Hours, IFNULL(SUM(t.Used_Hours), 0) AS 'Total Hours Used'
FROM authorization a
LEFT JOIN transaction t ON a.Auth_ID = t.Auth_ID AND a.User_ID = t.User_ID
GROUP BY a.Auth_ID, a.USER_ID, a.Auth_Hours
Notice that I used single quotes to assign a string with spaces as a name to the total field (as you used in your examples). This will work in all databases. In MySQL you can also use back ticks, but that only works in MySQL.
Here is a good illustration about the different types of joining tables.
this query work in any mysql Engine or Version. use this query:
SELECT a.Auth_ID, a.USER_ID, a.Auth_Hours, CASE COUNT(t.Used_Hours) WHEN 0 THEN 0 ELSE SUM(t.Used_Hours) END AS 'Total Hours Used'
FROM authorization a
LEFT JOIN TRANSACTION t ON a.Auth_ID = t.Auth_ID AND a.User_ID = t.User_ID
GROUP BY a.Auth_ID, a.USER_ID, a.Auth_Hours
Related
I am trying to get the sum of multiple rows from 2 different tables, but somehow the result returns multiple rows.
I need to get the SUM of quotation_item_amount (group by quotation_id) and invoice_item_amount (group by invoice_id) and if I query unpaid quotation, I need to get WHERE SUM(invoice) < SUM(quotation)
So here's my sample table
table client_project_id
+-------------------+-----------+----------------------+
| client_project_id | client_id | client_project_title |
+-------------------+-----------+----------------------+
| 23 | 5 | Project 1 |
| 17 | 9 | Project 2 |
| 54 | 7 | Project 3 |
+-------------------+-----------+----------------------+
table quotation
+--------------+-------------------+------------------+
| quotation_id | client_project_id | quotation_number |
+--------------+-------------------+------------------+
| 1 | 23 | Q/01/2020/001 |
| 2 | 17 | Q/01/2020/002 |
| 3 | 54 | Q/01/2020/003 |
+--------------+-------------------+------------------+
table quotation_item
+-------------------+--------------+-----------------------+
| quotation_item_id | quotation_id | quotation_item_amount |
+-------------------+--------------+-----------------------+
| 1 | 1 | 500 |
| 2 | 1 | 700 |
| 3 | 1 | 600 |
| 4 | 2 | 200 |
| 5 | 2 | 150 |
| 6 | 3 | 900 |
+-------------------+--------------+-----------------------+
table invoice
+--------------+-------------------+------------------+
| invoice_id | client_project_id | invoice_number |
+--------------+-------------------+------------------+
| 1 | 23 | I/01/2020/001 |
| 2 | 17 | I/01/2020/002 |
| 3 | 54 | I/01/2020/003 |
+--------------+-------------------+------------------+
table invoice_item
+-------------------+--------------+-----------------------+
| invoice_item_id | invoice_id | invoice_item_amount |
+-------------------+--------------+-----------------------+
| 1 | 1 | 500 |
| 2 | 1 | 700 |
| 3 | 1 | 600 |
| 4 | 2 | 200 |
| 5 | 2 | 150 |
| 6 | 3 | 900 |
+-------------------+--------------+-----------------------+
The result that I need to obtain is:
SUM of quotation_item_amount and SUM of invoice_item_amount PER client_project_id
To query WHERE SUM(invoice) < SUM(quotation)
Here is my latest try at the query
SELECT
SUM(quotation_item.quotation_item_amount) as quot_amt,
SUM(invoice_item.invoice_item_amount) as inv_amt,
data_client_project.client_project_id,
data_client.client_name
FROM data_client_project a
LEFT JOIN quotation b ON a.client_project_id = b.client_project_id
LEFT JOIN data_client d ON a.client_id = d.client_id
LEFT JOIN invoice i ON a.client_project_id = i.client_project_id
JOIN (
SELECT quotation_id,
SUM(c.quotation_item_amount) as quot_amt
FROM quotation_item c
GROUP BY c.quotation_id
) quotitem
ON b.quotation_id = quotitem.quotation_id
JOIN (
SELECT invoice_id,
SUM(e.invoice_item_price) as inv_amt
FROM invoice_item e
GROUP BY e.invoice_id
) invitem
ON i.invoice_id = invitem.invoice_id
However, this results in multiple duplicate rows of the quotation_item_amount and invoice_item_amount.
Have tried using UNION / UNION ALL and several other queries which just do not work.
Thank you for all your suggestions.
It looks like you are trying to aggregate along two different dimensions at the same time. The solution is to pre-aggregate along each dimension:
SELECT *
FROM data_client_project cp LEFT JOIN
(SELECT q.client_project_id,
SUM(qi.quotation_item_amount * qi.quotation_item_qty) as quot_amt
FROM quotation q JOIN
quotation_item qi
ON qi.quotation_id = q.quotation_id
GROUP BY q.client_project_id
) q
USING (client_project_id) LEFT JOIN
(SELECT i.client_project_id,
SUM(invoice_item_price) as inv_amt
FROM invoice i JOIN
invoice_item ii
ON i.invoice_id = ii.invoice_id
GROUP BY i.client_project_id
) i
USING (client_project_id);
Two notes about your style.
First, you are using arbitrary letters for table aliases. This makes the query quite hard to follow and becomes quite awkward if you add new tables, remove tables, or rearrange the names. Use abbreviations for the tables. Much easier to follow.
Second, I don't really recommend SELECT * for such queries. But, you can avoid duplicated column by replacing ON with USING.
I may be missing something, but your table descriptions do not include a example for data_client or data_client_project Given your example, I expect your row expansion is coming from the first 3 joins.
Make sure that the below is giving you the list of data you want first, then try joining in the calculation:
SELECT *
FROM data_client_project a
LEFT JOIN quotation b ON a.client_project_id = b.client_project_id
LEFT JOIN data_client d ON a.client_id = d.client_id
LEFT JOIN invoice i ON a.client_project_id = i.client_project_id;
#you may want to append the above with a limit 100 for testing.
if you have duplicated rows form the main query then add distinct for obatin a only distinct rows
and andd the where conditio for filtering the result by quotitem.quot_amt < invitem.inv_amt
SELECT distinct a.*, b.*, d.*, i.*
FROM data_client_project a
LEFT JOIN quotation b ON a.client_project_id = b.client_project_id
LEFT JOIN data_client d ON a.client_id = d.client_id
LEFT JOIN invoice i ON a.client_project_id = i.client_project_id
JOIN (
SELECT quotation_id,
SUM(c.quotation_item_amount * c.quotation_item_qty) as quot_amt
FROM quotation_item c
GROUP BY c.quotation_id
) quotitem ON b.quotation_id = quotitem.quotation_id
JOIN (
SELECT invoice_id,
SUM(e.invoice_item_price) as inv_amt
FROM invoice_item e
GROUP BY e.invoice_id
) invitem ON i.invoice_id = invitem.invoice_id
WHERE quotitem.quot_amt < invitem.inv_amt
I am having trouble trying to work out how to structure my query to allow me to 'reset' and only count records after there has been a reset.
Basic Structure
Log Table
ID | Date | Time | SectorID | personnumber
1 | 2020-02-10 | 13:23:00 | 23 | 66 (This is a row to be counted)
2 | 2020-02-10 | 13:28:00 | 38 | 66 (This is a row to be counted)
3 | 2020-02-10 | 13:30:00 | 5 | 66 (This is a 'reset' row) (SectorID 5 is a reset)
4 | 2020-02-10 | 13:38:00 | 12 | 66 (This is a row to be counted)
5 | 2020-02-10 | 13:42:00 | 56 | 66 (This is a row to be counted)
For the above, there are 2 records, then there was a reset (which is indicated by sector ID of 5), and then 2 more records (the other records can be any other number other than 5).
So I want the 'count' to return 2
The query below is what I have for counting all records without any reset function
SELECT
personnumber,
count(*) as occurrences
FROM log
WHERE personnumber IS NOT NULL
AND sectorid != 5
GROUP BY personnumber
HAVING count(*) > 1
ORDER BY occurrences DESC, personnumber
This would return
Personnumber | Occurrences
66 | 4
I hope this explains my problem sufficiently. Any help would be much appreciated.
Thanks
Jon
You could JOIN the same table by using sectorId and personnumeber in order to count all the records after this id:
SELECT
l.personnumber,
COUNT(*) as occurrences
FROM
`log` l
INNER JOIN
(
SELECT id, personnumber
FROM `log` ll
WHERE ll.sectorid = 5
) AS ll ON l.personnumber = ll.personnumber
WHERE
l.personnumber IS NOT NULL
AND l.id > ll.id
GROUP BY l.personnumber
HAVING COUNT(*) > 1
ORDER BY
occurrences DESC,
l.personnumber
Output:
+--------------+-------------+
| personnumber | occurrences |
+--------------+-------------+
| 66 | 2 |
+--------------+-------------+
1 row in set (0.01 sec)
If you need only the records after the last occurrence of sector 5 for particular personnumber, then you need to get the maximum id within the derived table:
SELECT
l.personnumber,
COUNT(*) as occurrences
FROM
`log` l
INNER JOIN
(
SELECT MAX(id) AS id, personnumber
FROM `log` ll
WHERE ll.sectorid = 5
GROUP BY personnumber
) AS ll ON l.personnumber = ll.personnumber
WHERE
l.personnumber IS NOT NULL
AND l.id > ll.id
GROUP BY l.personnumber
HAVING COUNT(*) > 1
ORDER BY
occurrences DESC,
l.personnumber
I have a table of movie ratings that contains millions of rows containing userid's, movieid's and ratings.
| userId | movieId | rating |
------------------------------
| 1 | 213 | 5 |
| 1 | 245 | 4 |
| 2 | 213 | 4 |
| 2 | 245 | 4 |
| 3 | 657 | 5 |
| 3 | 245 | 5 |
I'm trying to figure out a way of grouping together userId's that contain matching sets of movieId's. Ideally I want the query to only find matches if they have at least 5 movieId's in common and if the rating is above 4, but I've simplified it for this example.
In the instance above, userId 1 and 2 would be the only users that match as they both contain the same movieIds. I need a statement that would essentially replicate this. Thanks in advance for any help.
You can perform a self-join on matching movies, filter out records with uninteresting ratings, group by user-pairs and then filter the resulting groups for only those that have at least the requisite number of matching records:
SELECT a.userId, b.userId
FROM myTable a JOIN myTable b USING (movieId)
WHERE a.userId < b.userId
AND a.rating > 4
AND b.rating > 4
GROUP BY a.userId, b.userId
HAVING COUNT(*) >= 5
select movieId, rating
from tablename
group by movieId
having count(userId) > 1 and rating > 4;
this gives me movieId 245 and rating 5, which should be correct according to your provided example data, have more than 1 userId and a rating greater than 4.
I have some tables.
titles
id| title
1 | Cars
2 | Computers
3 | Phones
4 | Tvs
entry
id | title_id | user_id | entry | time
1 | 1 | 12 | entry-01 | 1
2 | 2 | 11 | entry-02 | 2
3 | 3 | 12 | entry-03 | 3
4 | 2 | 11 | entry-04 | 4
5 | 3 | 11 | entry-05 | 5
6 | 4 | 12 | entry-06 | 6
7 | 4 | 13 | entry-07 | 7
8 | 4 | 11 | entry-08 | 8
9 | 1 | 10 | entry-09 | 9
10 | 2 | 12 | entry-10 | 10
users
id | username
10 | user-1
11 | user-2
12 | user-3
13 | user-4
friends
id | user_id | friend_id
1 | 10 | 12
2 | 11 | 12
3 | 12 | 10
4 | 10 | 11
I need to filter titles based on friends' entries and sort the results by (entry.time) desc. And I also need to show friends name and count(entry) at the list.
Expected result filtered by user_id=10 is:
result
1 | Computers | user-3, user-2(2)
2 | Tvs | user-2, user-3
3 | Phones | user-2, user-3
4 | Cars | user-3
any ideas?
This problem is complex, but if you get into the habit of breaking problems down into smaller pieces you will catch on pretty quickly. Why not start by getting all friends of user id 10? We can do so like this:
SELECT CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL;
Notice the use of a case statement, because user_id 10 could be in either of the two columns. I use the GROUP BY in case the user/friend pair appears multiple times (like 10 and 12 for your example) and a check for not null to remove the rows that didn't match the case.
Now that you have those, you can join it with the entries and titles tables to get the information you're going to need. Just add in some aggregation to get the number of entries each user has for a title:
SELECT t.title, u.userName, COUNT(*) AS numEntries
FROM titles t
LEFT JOIN entry e ON e.title_id = t.id
JOIN users u ON u.id = e.user_id
JOIN(
SELECT
CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL) f ON f.userFriends = u.id
GROUP BY t.title, u.userName;
Matching your format is going to be very tricky. Typically, you can use GROUP_CONCAT() to get a comma separated list, but you will get something like user3, user2, user2 for your first list. To fix this, I recommend writing a CONCAT() in your select statement to modify the above query to get the number of entries to the side of each user. In addition, use another CASE statement so that this only happens when the COUNT(*) is greater than 1:
SELECT t.title,
CASE WHEN COUNT(*) > 1 THEN
CONCAT(u.userName, ' (', COUNT(*), ')')
ELSE
u.userName
END AS numEntries
FROM titles t
LEFT JOIN entry e ON e.title_id = t.id
JOIN users u ON u.id = e.user_id
JOIN(
SELECT
CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL) f ON f.userFriends = u.id
GROUP BY t.title, u.userName;
And now, I would preform a GROUP_CONCAT() on that query:
SELECT tmp.title, GROUP_CONCAT(tmp.userEntries) AS friendEntries
FROM(
SELECT t.title,
CASE WHEN COUNT(*) > 1 THEN
CONCAT(u.userName, ' (', COUNT(*), ')')
ELSE
u.userName
END AS userEntries
FROM titles t
LEFT JOIN entry e ON e.title_id = t.id
JOIN users u ON u.id = e.user_id
JOIN(
SELECT
CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL) f ON f.userFriends = u.id
GROUP BY t.title, u.userName) tmp
GROUP BY tmp.title;
I apologize for the lengthy response (though I wanted to be clear and cover it all). If you've made it to this point, you'll be happy to know that it works in SQL Fiddle.
Hi there coders around the world,
I'm working on a project where users can do certain things and gain points for it. To simplify this question let's say we got 2 tables user and points.
-- table user -- table points
+---------------+ +-----------------------------+
| id | name | | id | points | user_id |
+---------------+ +-----------------------------+
| 1 Tim | | 1 5 1 |
| 2 Tom | | 2 10 1 |
| 3 Marc | | 3 5 1 |
| 4 Tina | | 4 12 2 |
| 5 Lutz | | 5 2 2 |
+---------------+ | 6 7 1 |
| 7 40 3 |
| 8 100 1 |
+-----------------------------+
Now to get the complete highscore-list I use the following query
SELECT u.*, SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
ORDER BY sum_points DESC
resulting in a fine highscore-list with all users from first to last
+------------------------------+
| id | name | sum_points |
+------------------------------+
| 1 Tim 127 |
| 3 Marc 40 |
| 2 Tom 14 |
| 4 Tina 0 |
| 5 Lutz 0 |
+------------------------------+
Alright back to the question itself. On the profile of a single user I'd like to show his ranking within the highscore-list.
Can this be done using a single query just showing that for example Tom (id=2) is ranked in place 3?
Thanks alot :-)
The idea is to ask, "how many players rank above #this_user":
select count(*) + 1 from
(
/* list of all users */
SELECT SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
) x
/* just count the ones with higher sum_points */
where sum_points > (select sum(points) from points where user_id = #this_user)
Edited to make result 1-based instead of 0-based
SELECT q.*,
#r := #r + 1 AS rank
FROM (
SELECT #r := 0
) vars,
(
SELECT u.*,
SUM(p.points) AS sum_points
FROM
user u
LEFT JOIN
points p
ON p.user_id = u.id
GROUP BY
u.id
ORDER BY
sum_points DESC
) q