I'm trying to write a query to calculate the total charged on a per client basis, where a client has a registered property in one table, where they have not been invoiced in another, but I am not sure how to write the 'Not In' portion of the query so that it does not have to reference a specific ID.
The query listed below is part of a larger query to calculate overall totals, but I am struggling with this particular piece.
I have tried to modify the 'Not In' portion of the query to remove the reference to a specific ID and do a Group By the client_id, but this did not return any results. I also tried a GROUP_CONCAT, but I'm not sure I implemented that properly.
I'm not sure if a) what I'm trying to do is possible, or b) if there's a much better way to go about doing what I'm trying to accomplish, but I feel like if I could write this in another subquery, I'd be in much better shape, but I can't wrap my head around how to write it or even another way to approach the problem.
SELECT
clients.client_id,
clients.first_name,
clients.last_name,
SUM(total_charged) AS total_charged
FROM
clients
LEFT JOIN
(SELECT
clients.client_id,
route_property_reg.route_property_reg_id,
route_property_reg.rate_charged AS total_charged
FROM
clients
LEFT JOIN properties ON clients.client_id = properties.client_id
LEFT JOIN route_property_reg ON properties.property_id = route_property_reg.property_id
WHERE
route_property_reg.route_property_reg_id NOT IN (
SELECT DISTINCT
invoice_lines.route_property_reg_id
FROM
clients
LEFT JOIN invoices ON clients.client_id = invoices.client_id
LEFT JOIN invoice_lines ON invoices.invoice_id = invoice_lines.invoice_id
WHERE
clients.client_id = 1)
GROUP BY clients.client_id)
AS charged ON clients.client_id = charged.client_id
GROUP BY clients.client_id
SQL Fiddle
An alternative Fiddle with another attempt but similar results: SQL Fiddle
Expected/Anticipated Output would be:
| client_id | first_name | last_name | total_charged |
|-----------|------------|-----------|---------------|
| 1 | John | Doe | 200 | - (route-properties # 1 and 4)
| 2 | Jane | Doe | 200 | - (route-property #5)
| 3 | John | Smith | (null) |
| 4 | Jane | Smith | (null) |
After a lot of guess and check, the following works for me:
SELECT
clients.client_id,
clients.first_name,
COALESCE(SUM(total_charged), 0) AS total_charged
FROM
clients
LEFT JOIN (SELECT
c.client_id,
a.route_property_reg_id,
COALESCE(SUM(ROUND((a.rate_charged * (1 + (tax_types.rate / 100))), 2)), 0) AS total_charged
FROM
route_property_reg a
LEFT JOIN properties ON a.property_id = properties.property_id
LEFT JOIN clients c ON properties.client_id = c.client_id
LEFT JOIN tax_types ON a.tax_rate_charged = tax_types.tax_type_id
WHERE
NOT EXISTS( SELECT
NULL
FROM
invoice_lines b
LEFT JOIN invoices ON b.invoice_id = invoices.invoice_id
LEFT JOIN clients d ON invoices.client_id = d.client_id
WHERE
b.route_property_reg_id = a.route_property_reg_id
AND d.client_id = c.client_id)
GROUP BY client_id) AS charged ON clients.client_id = charged.client_id
GROUP BY clients.client_id
Related
I am trying to select the MAX of a COUNT grouping them by state (So one max for each distinct value in state). The count function works as intended.
SELECT c.id, c.name, t.name as type, COUNT(*) as count, c.state
FROM bookings_facilities f
JOIN bookings b
ON b.id = f.booking_id
JOIN clients c
ON c.id = b.client_id
JOIN client_types t
ON c.type = t.id
WHERE t.name = "School"
GROUP BY c.id
Here is the results,
I use the SQL statement below to try and choose the MAX of count grouping them by state.
SELECT *, MAX(z.count)
FROM (SELECT c.id, c.name, t.name as type, COUNT(*) as count, c.state
FROM bookings_facilities f
JOIN bookings b
ON b.id = f.booking_id
JOIN clients c
ON c.id = b.client_id
JOIN client_types t
ON c.type = t.id
WHERE t.name = "School"
GROUP BY c.id) z
GROUP BY z.state
Here is the results,
The 3 states, which appear only once in result 1 seems to be fine, but for the state, Selangor, which appears twice in the first result, had some problems.
The SQL query selected the right MAX(Count) which is 6, but instead of returning id as 1027 it returned id as 1002 which only has count as 1 in the first result.
I have tried it with different sets of data but I can't seem to get the details of the actual MAX(Count) row.
Here is the database design for reference
SQL Fiddle
Expected outcome is this, (Just the second row output needs to be changed).
Current Output Link
Since you can use MySQL 8.0, we can solve your problem using Window Functions. Over a partition of state, we will determine Row_Number() with the row having highest count as row number 1 and so on. Now, we simply need to consider only those rows where row number is 1, for a particular state.
Additionally, in your attempt, GROUP BY was not valid SQL. Older versions of MySQL were lenient and allowed it; but newer versions don't, unless you turn off the strict only_full_group_by mode. However, you should not disable it, and instead fix the query. The basic gist is that when using a Group By, your Select clause should only contain aggregated column(s)/expression(s) and/or the column(s)/expression(s) defined in the Group By clause. Do read: Error related to only_full_group_by when executing a query in MySql
SELECT dt2.id,
dt2.NAME,
dt2.state,
dt2.type,
dt2.count
FROM (SELECT dt1.id,
dt1.NAME,
dt1.state,
dt1.type,
dt1.count,
Row_number()
OVER (
partition BY dt1.state
ORDER BY dt1.count DESC) AS row_num
FROM (SELECT c.id,
c.NAME,
c.state,
t.NAME AS type,
Count(*) AS count
FROM bookings_facilities AS f
JOIN bookings AS b
ON b.id = f.booking_id
JOIN clients AS c
ON c.id = b.client_id
JOIN client_types AS t
ON c.type = t.id
WHERE t.NAME = 'School'
GROUP BY c.id,
c.NAME,
c.state,
type) AS dt1) AS dt2
WHERE dt2.row_num = 1
Result:
| id | NAME | state | type | count |
| ---- | ---------------------------------------- | --------- | ------ | ----- |
| 1006 | Holy Child Montessory School Of Fairview | Manila | School | 1 |
| 1027 | Sri Kuala Lumpur | Selangor | School | 6 |
| 1010 | Singapore American School | Singapore | School | 1 |
| 1015 | Keika Junior & Senior High School | Tokyo | School | 1 |
View on DB Fiddle
I've looked at a bunch of questions and solutions regarding many to many queries. I just can't seem to wrap my head around it. Maybe I'm not completely understanding the keywords in MySQL. But...
I have 3 tables. The first table is a list of peoples contact information. The second table is a list of mailing list categories. The third table is an associative table that holds the id's from the first and second table. How would I write a MySQL query to get all the contacts from the contact table that match the VIP list id (which I already have)?
Table 1 (contacts)
id | name | email
-----------------------------
1 | John | john#gmail.com
-----------------------------
2 | Jane | jane#gmail.com
-----------------------------
Table 2 (list_type)
id | list_name |
-----------------
1 | VIP's |
-----------------
2 | Generic |
-----------------
Table 3 (list_contact_joiner)
contact_id | list_type_id |
----------------------------
1 | 2 |
----------------------------
2 | 1 |
----------------------------
This is what I tried but get a syntax error
$listID = 1;
SELECT list_contact_joiner.contact_id
FROM list_contact_joiner
WHERE list_id = $listID AS lcj
INNER JOIN contact_lists AS cl
ON cl.id = lcj.contact_id
SELECT c.*
FROM contacts c
JOIN list_contact_joiner j on j.contact_id = c.id
JOIN list_type t on j.list_type_id = t.id
WHERE t.list_name = 'VIP''s'
If you already have the id of VIP's then you need to join only 2 tables
SELECT c.*
FROM contacts c
JOIN list_contact_joiner j on j.contact_id = c.id
WHERE j.list_type_id = 1
Yes the join statement is not correct. It should be something as
select
c.name,
c.email,
lt.list_type
from list_contact_joiner lcj
join contacts c on c.id = lcj.contact_id
join list_type lt on lt.id = lcj.list_type_id
where
lt.id = ?
If you are looking for data with $listID = 1; then the place holder is
lt.id = ?
Your syntax error is because all the table specification (FROM and JOIN) has to occur before the WHERE and you got your alias syntax a little messed up - this should work better:
$listID = 1;
SELECT lcj.contact_id
FROM list_contact_joiner AS lcj
INNER JOIN contact_lists AS cl
ON cl.id = lcj.contact_id
WHERE lcj.list_id = $listID
But if all you are trying to do is get the contact_id then you don't need to do any join at all...
SELECT contact_id
FROM list_contact_joiner
WHERE list_id = $listID
If you need the rest of the contact information then you might try this:
SELECT contact.*
FROM list_contact_joiner
JOIN contacts ON contacts.id = list_contact_joiner.contact_id
WHERE list_id = $listID
Note that you still don't need to get the list_type table at all.
Im trying to join 5 tables that look somewhat like this
post table
ID | product | user-us | make-id | dealer-id | pending | .... 30 other columns ... |
make table
ID | make |
state table
prefix | state | city | zip
members table
ID | name | email | password | zip | dealer-id
dealer table
ID | dealer name | city | state | zip | address | phone
MySql query looks like this
SELECT *
FROM `post` AS p
JOIN (`make` AS m, `state` AS s, `members` AS mb, `dealer` AS d)
ON (p.Make = m.id AND p.state = s.id AND p.id = mb.id AND mb.dealer-id = d.id)
WHERE p.pending != '1'
The problem is this query only returns rows that member.dealer-id = dealer.id, And if i use a LEFT JOIN, it returns all the correct rows, BUT all columns in tables make, state, members and dealer will be NULL. which it shouldn't be because i need the info in those tables.
Is there away i can only join the dealer table if member.dealer-id is > 0? i could add a row in the dealer table with id 0, but there has to be a better way.
Once you code your joins in the normal way, use LEFT JOIN only on the dealer table:
SELECT *
FROM post AS p
JOIN make AS m ON p.Make = m.id
JOIN state AS s ON p.state = s.id
JOIN`members AS mb ON p.id = mb.id
LEFT JOIN dealer AS d ON mb.dealer_id = d.id
WHERE p.pending != '1'
This will automatically only join to dealer if the member.dealer-id is greater than zero.
btw, I have never seen a query join coded like yours before. If I had, I would have assumed it would not execute due to a syntax error - it looks that strange.
I have a person table and a task table. Though a task can be done by multiple people, so in between there is a 'roster' table:
person
task_roster
task
I'm looking for a query that returns each person and their current active task. If they have no active task, it should simply return NULL. I've been messing around with the following:
SELECT *
FROM person
LEFT OUTER JOIN task_roster ON (task_roster.person = person.id)
LEFT OUTER JOIN task on (task.id = task_roster.task AND task.status = 1)
ORDER BY person.name
The problem I'm running in too is that this query returns old roster entries, so I get the same person back multiple times, for each previous task they performed. I've also tried adding a Group By on person.name, though then I don't get any of the active duties, just NULL values.
The restult I'm looking for would be like this:
+----------------+-----------+----------------+
| person.name | task.id | task.name |
+----------------+-----------+----------------+
| David Harris | 5 | Maintenance |
| Karen Simmons | 2 | Transport |
| Linda Foster | 5 | Maintenance |
| Michael King | NULL | NULL |
+----------------+-----------+----------------+
David and Linda are both working on the same task. The roster table is nothing more then a link between person.id and task.id
I wonder if this would work:
SELECT person.*,task.status
FROM person
LEFT OUTER JOIN task_roster ON (task_roster.person = person.id)
LEFT OUTER JOIN task on task.id = task_roster.task
WHERE task.status=1
ORDER BY person.name
The reason why you are getting also the old task is because of this join statement:
...
task_roster ON (task_roster.person = person.id)
LEFT OUTER JOIN task
...
basically it displays all the records from table task_roster. There may be other solutions on this but my solution below uses a subquery that gets only person with specific active task. The result of the subquery is then joined back on table person using LEFT JOIN so every record on table person will be shown on the list whether it has a matching record on the subquery or none.
SELECT a.name AS PersonName,
b.task AS TaskID,
b.name AS TaskName
FROM person a
LEFT JOIN
(
SELECT a.person, a.task, b.name
FROM task_roster a
INNER JOIN task b
ON a.task = b.id
WHERE b.status = 1
) b ON a.id = b.person
I have 4 different queries and each of them return individual unique set of Results.
I need to combine the Query Results with using a single query.
my sample queries are:
1. select cls.* from (calls as cls inner join calls_users as clsusr on cls.id=clsusr.call_id) inner join users as usr on usr.id=cls.assigned_user_id where cls.assigned_user_id='seed_max_id'
2. select mtn.* from (meetings as mtn inner join meetings_users as mtnusr on mtn.id=mtnusr.meeting_id) inner join users as usr on usr.id=mtn.assigned_user_id where mtn.assigned_user_id='seed_max_id'
3. select tsk.* from tasks as tsk inner join users as usr on usr.id=tsk.assigned_user_id where tsk.assigned_user_id='seed_max_id'
4. select nts.* from (notes as nts inner join accounts as acnts on acnts.id=nts.parent_id) inner join users as usr on usr.id=acnts.assigned_user_id where acnts.assigned_user_id='seed_max_id'
I tried the following way, but it didn't work
Combine: SELECT tbl1.*, tbl2.*
from (select cls.* from (calls as cls inner join calls_users as clsusr on cls.id=clsusr.call_id) inner join users as usr on usr.id=cls.assigned_user_id where cls.assigned_user_id='seed_max_id') as tbl1
left outer join
(select mtn.* from (meetings as mtn inner join meetings_users as mtnusr on mtn.id=mtnusr.meeting_id) inner join users as usr on usr.id=mtn.assigned_user_id where mtn.assigned_user_id='seed_max_id') as tbl2
using(assigned_user_id)
i also tried right outer join and other inner joins
I am really stuck, if any one know the solution then please help.
I need the similar result like How can I join two tables with different number of rows in MySQL?.
Data Sample:
From Query 1:
+-------------------------------------------+------------------+-
| Call Name | Call Description |
+-------------------------------------------+------------------+-
| Discuss Review Process | NULL |
| Get More information on the proposed deal | NULL |
| Left a message | NULL |
| Discuss Review Process | NULL |
+-------------------------------------------+------------------+
From Query 2:
+-----------------------+-----------------------------------------------------------
| Meeting Name | Meeting Description
+-----------------------+-----------------------------------------------------------
| Review needs | Meeting to discuss project plan and hash out the details o
| Initial discussion | Meeting to discuss project plan and hash out the details o
| Demo | Meeting to discuss project plan and hash out the details o
| Discuss pricing | Meeting to discuss project plan and hash out the details o
| Review needs | Meeting to discuss project plan and hash out the details o
+-----------------------+-----------------------------------------------------------
i need to combine the columns like the following:
+-------------------------------------------+------------------+-------------------+-------------------+
| Call Name | Call Description |Meeting Name |Meeting Description|
+-------------------------------------------+------------------+-------------------+-------------------+
| Discuss Review Process | NULL |Review needs |Meeting to discuss |
| Get More information on the proposed deal | NULL |Initial discussion |Meeting to discuss |
| Left a message | NULL |Demo |Meeting to discuss |
| NULL | NULL |Discuss pricing |Meeting to discuss |
| NULL | NULL |Review needs |Meeting to discuss |
+-------------------------------------------+------------------+-------------------+-------------------+
The best you can do is a UNION or UNION ALL but this requires them to have the same type and number of columns. For example:
SELECT 'Customer' AS type, id, name FROM customer
UNION ALL
SELECT 'Supplier', id, name FROM supplier
UNION ALL
SELECT 'Employee', id, full_name FROM employee
The column names don't have to match. The aliases from the first part will be used for the rest.
I'll also add that instead of:
select cls.* from (calls as cls inner join calls_users as clsusr on cls.id=clsusr.call_id) inner join users as usr on usr.id=cls.assigned_user_id where cls.assigned_user_id='seed_max_id'
you should remove the unnecessary subquery and just do:
SELECT c.*
FROM calls c
JOIN calls_users cu ONc.id = cu.call_id
WHERE c.assigned_user_id = 'seed_max_id'
There's no need for the extra complexity and the above is eminently more readable.
I assume you want your example to return a single row combining the corresponding entries from all these tables. Try this and tell us if it worked:
select * from users as usr
left outer join (calls as cls
inner join calls_users as clsusr
on cls.id = clsusr.call_id)
on usr.id = cls.assigned_user_id
left outer join (meetings as mtn
inner join meetings_users as mtnusr
on mtn.id = mtnusr.meeting_id)
on usr.id = mtn.assigned_user_id
left outer join tasks as tsk
on usr.id = tsk.assigned_user_id
left outer join (notes as nts
inner join accounts as acnts
on acnts.id=nts.parent_id)
on usr.id = acnts.assigned_user_id
where user.id = 'seed_max_id'