MYSQL: Multiple Table Join - Conditional on previous join - mysql

MEMBERS_TABLE
member_id
---------------------------------------------
1
ACCOUNTS_TABLE
account_id member_id
---------------------------------------------
1 1
INVESTMENTS_TABLE
investment_id account_id
---------------------------------------------
1 1
2 1
FUNDS_TABLE
fund_id investment_id
---------------------------------------------
1 1
2 2
This is my current query:
SELECT
m.member_id,
a.account_id,
i.investment_id,
f.fund_id,
COUNT(a.account_id) AS member_accounts_total,
COUNT(i.investment_id) AS member_investments_total,
COUNT(f.fund_id) AS member_funds_total
FROM members AS m
LEFT JOIN accounts AS a ON m.member_id = a.member_id
LEFT JOIN investments AS i ON a.account_id = i.account_id
LEFT JOIN funds AS f ON f.fund_id = i.fund_id
I would like to see the following results:
member_accounts_total: 1
member_investments_total: 2
member_funds_total: 2
Instead, I am getting these results:
member_accounts_total: 2
member_investments_total: 2
member_funds_total: 2
I really don't want to write multiple queries for this.

Just need to change
COUNT(a.account_id) AS member_accounts_total,
to
COUNT( distinct a.account_id) AS member_accounts_total,
The reason you're getting 2 is because the left join on accounts to investments results in 2 records. To get a distinct count of members you need to add well... distinct.
Note you may have problems with the other totals as well (Distinct may be needed there as well in the long run...) say if a member had multiple accounts. you may get odd counts as well (if each account had the same investment... would you want to see the count only once or twice?

Related

how to use count in MySQL in good properly way

I have 2 tables, this is my fiddle http://sqlfiddle.com/#!9/da5e4e/3
so basically i have 2 tables called personal and interview_score with personal.id = interview_score.personal_id.
assume this is my personal table
id name
1 John Doe
2 Nian
3 Rijali
and this is my interview_score table
id personal_id aspect_id
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
on this case, i just want to count how many personal_id in my interview_score table with this query
SELECT COUNT(i.id) as interviewed FROM personal p LEFT JOIN interview_score i ON i.personal_id = p.id GROUP BY i.personal_id;
but it returns just like this
interviewed
0
3
2
meanwhile, my expected result is just like this
interviewed
2
because on that table there are 2 personal_id based on that condition.
where my wrong at?
You can use join instead of left join also you need to group by p.id
SELECT COUNT(i.id) as interviewed FROM personal p JOIN interview_score i ON i.personal_id = p.id GROUP BY p.id;
from the above query you can get the count
To get count for a particular person
SELECT COUNT(i.id) as interviewed FROM personal p JOIN interview_score i ON i.personal_id = p.id and p.id=1 GROUP BY p.id;

Propel2; how to use querybuilder for subselect query

Problem:
I'm having trouble finding a solution building a query with QueryBuilder (perhaps getting it done with regular sql query first will help):
Trying to retrieve all customers for a user (has shop credits at one of the shops user is linked to), need the total credits (sum of credits at shops belonging to that user) as virtual column (to be able to order on), using paginate().
Database structure:
Table customers
id email other_fields
1 1#email.com f
2 2#email.com o
3 3#email.com o
Table users
id email other_fields
1 1#user.com b
2 2#user.com a
3 3#user.com r
Table shops
id name other_fields
1 Shop 1 m
2 Shop 1 o
3 Shop 1 o
Table user_shops
user_id shop_id
1 1
1 2
3 3
Table customer_shop_credits
customer_id shop_id credits
1 1 55
1 2 45
2 2 3
3 3 44
Expected result:
When retrieving customers for user 1, I'd expect to get back customer 1 with 100 credits and customer 2 with 3 credits
Closest I got:
$credits_query = CustomerShopCreditQuery::create()
->useShopQuery()
->useUserShopQuery()
->filterByUserId($user->getId())
->endUse()
->endUse()
;
$customers = CustomerQuery::create()
->addSelectQuery($credits_query, 'credits_alias', false)
->useCustomerShopCreditQuery()
->useShopQuery()
->useUserShopQuery()
->filterByUserId($user->getId())
->endUse()
->endUse()
->endUse()
->withColumn('sum(credits_alias.credits)', 'credits')
->groupById()
->orderBy($order_by_column, $direction)
->paginate($page, $page_size);
Which results in the following query:
SELECT customers.id, customers.email, sum(credits_alias.credits) AS credits
FROM customers
CROSS JOIN (
SELECT customer_shop_credits.id, customer_shop_credits.customer_id, customer_shop_credits.shop_id, customer_shop_credits.credits
FROM customer_shop_credits
INNER JOIN shops ON (customer_shop_credits.shop_id=shops.id)
INNER JOIN user_shops ON (shops.id=user_shops.shop_id)
WHERE user_shops.user_id=159
) AS credits_alias
INNER JOIN customer_shop_credits ON (customers.id=customer_shop_credits.customer_id)
INNER JOIN shops ON (customer_shop_credits.shop_id=shops.id)
INNER JOIN user_shops ON (shops.id=user_shops.shop_id)
WHERE user_shops.user_id=159
GROUP BY customers.id
ORDER BY customers.id DESC
LIMIT 25
But gives me results with wrong sum of credits.
Not to sure about the CROSS JOIN. When I edit this query and make it a JOIN and use ON (credits_alias.customer_id = customers.id) as a condition, the sum of credits is better, but seems to have the classic join problem of doubling the sum

Mysql (conditional?) query from two tables

Not sure if I have phrased the title properly, but here it goes. I have these two tables:
table:staff
id Name groupId Status
1 John Smith 1 1
2 John Doe 1 1
3 Jane Smith 2 1
4 Jerry Smith 1 1
table:jobqueue
id job_id staff_id jobStatus
1 1 1 1
2 2 1 1
3 5 2 1
4 7 3 0
Now, what I need to do is to find the staff with the least amount of job assigned to him which I am able to do by querying the jobqueue table.
SELECT min(cstaff),tmp.staff_id FROM (SELECT t.staff_id, count(staff_id) cstaff from jobqueue t join staff s on t.staff_id=s.id join group g on s.groupId=g.id where g.id=26 GROUP BY t.id ) tmp
This works fine, but the problem is if a staff is not assigned to any job at all, this query wont get them, because it only queries the jobqueue table, where that particular staff won't have any entry. I need to modify the query to include the staff table and if a staff is not assigned any job in the jobqueue then I need to get the staff details from the staff table. Basically, I need to find staff for a group who are not assigned any job and if all staffs are assigned job then find staff with the least amount of jobs assigned. Could use some help with this. Also, tagging as Yii as I would like to know if this is doable with Yii active-records. But I am okay with a plain sql query that will work with Yii sql commands.
not sure that it is optimal query, but it works:
select d.groupId, d.name, (select count(*) from jobqueue as e where e.staff_id=d.id) as jobassigned
from staff as d
where d.id in (
select
(
select a.id
from staff as a
left outer join
jobqueue as b
on (a.id = b.staff_id)
where a.groupId = c.groupId
group by a.id
order by count(distinct job_id) asc
limit 1
) as notassigneduserid
from (
select distinct groupId from staff
) as c)
maybe need some comments:
c query is needed to get all distinct groupId - if you have separate table for this, you can replace it
notassigneduserid statement for each groupId select user with minimal job count
d query is needed to fetch actual user names, groupId for all found "unassigned users" and present it
here is the results for data from question:
Group Staff Jobs assigned
1 Jerry Smith 0
2 Jane Smith 1
with
counts as (
select s.groupId
, s.id
, (select count(*) from jobqueue where staff_id = s.id) count
from staff s
group by s.id, s.groupId),
groups as (
select groupId, min(count) mincount
from counts
group by groupId)
select c.groupId, c.id, c.count
from counts c
join groups g on c.groupId = g.groupId
where c.count = g.mincount
This SQL will give you all the staff with the minimum number of jobs in each group. It might be that more than one staff has the same minimum number of jobs. The approach is to use common table expressions to build first a list of counts, and then to retrieve the minimum count for each group. Finally I join the counts and groups tables and retrieve the staff that have the minimum count for each group.
I tested this on SQL Server, but the syntax should work for MySQL as well. To your data I added:
id Name groupId Status
5 Bubba Jones 2 1
6 Bubba Smith 1 1
and
id job_id staff_id jobStatus
5 4 5 1
Results are
group name count
1 Bubba Smith 0
1 Jerry Smith 0
2 Bubba Jones 1
2 Jane Smith 1
BTW, I would not try to do this with active record, it is far too complex.
As Ilya Bursov said this answer wasn't respond exactly what was asked. So here is a more optimized solution:
SELECT *
FROM (
SELECT s.id as id_staff, s.Name, s.groupId, count(distinct t.id) as jobsXstaff
FROM staff s
LEFT JOIN jobqueue t ON s.id=t.staff_id
GROUP BY s.id, s.groupId
ORDER BY s.groupId, jobsXstaff
) tmp
GROUP BY groupId
Old answer below.
This works but without table group which I don't create. You can simply join table groups as you did:
SELECT min(cstaff),tmp.id
FROM (
SELECT s.id, count( staff_id ) cstaff
FROM jobqueue t
RIGHT JOIN staff s ON t.staff_id = s.id
GROUP BY t.id
) tmp
As you see you need to get all values from table staff (right join) and select the id staff from it's own table (s.id instead of t.staff_id). Also you have to get tmp.id instead of staff_id now.

Sum of All Related Rows with Matching ID MySQL

I have the following table schema:
tbl_portfolio
----------
id (auto number)
name
-
tbl_registration
----------------
id(auto number)
name
portfolio_id (FK to tbl_portfolio.id)
-
tbl_fund
---------
id (auto number)
registration_id (FK to tbl_registration.id)
-
tbl_transaction
---------------
id (auto number)
fund_id (FK to tbl_fund.id)
type
shares
price
I need to create a query that in psuedo-code would do the following:
SELECT port.*, SUM ALL transactions for each portfolio,
FROM tbl_portfolio port
INNER JOIN tbl_registration reg ON reg.portfolio_id = port.id
LEFT JOIN tbl_fund fund on fund.registration_id = reg.id
LEFT JOIN tbl_transaction trans ON trans.fund_id = fund.id
Now of course that query won't work...What I am needing essentially is to sum all the Price * Units for each fund, and then sum those together for each registration, and then sum all of that together for each portfolio.
Each portfolio can have multiple registrations, and each registration can have multiple funds, and each fund can have multiple transactions.
The last item that is throwing a stickler in this, there may be 10's or 100's of portfolios to count so I have no idea how to write the query, much less write it in an effective way that is not relying on subqueries that would cause it to have severely poor performance.
Thank you for the help!
Edit:
PinnyM's answer works and queries the data correctly - however I should expand on the full need.
Besides the tbl_transaction there is also a tbl_distri and tbl_div. Both have fund_id as FK to tbl_fund.id . I need to get the SUM's of tbl_distri.amount and tbl_div.units.
So the full psuedo query would be something to the effect of:
SELECT port.*, SUM ALL transactions for each portfolio, SUM(div.units), SUM(distri.amount)
FROM tbl_portfolio port
INNER JOIN tbl_registration reg ON reg.portfolio_id = port.id
LEFT JOIN tbl_fund fund on fund.registration_id = reg.id
LEFT JOIN tbl_transaction trans ON trans.fund_id = fund.id
LEFT JOIN tbl_distri distri on distri.fund_id = fund.id
LEFT JOIN tbl_div div on div.fund_id = fund.id
Have you tried using SUM()?
SELECT port.*, SUM(trans.shares * trans.price) AS transaction_totals
FROM tbl_portfolio port
INNER JOIN tbl_registration reg ON reg.portfolio_id = port.id
LEFT JOIN tbl_fund fund on fund.registration_id = reg.id
LEFT JOIN tbl_transaction trans ON trans.fund_id = fund.id
GROUP BY port.id
Judging from your question, you are looking for a rolled-up SUM
SELECT port.id AS port_id,
reg.id AS reg_id,
fund.id AS fund_id,
SUM ( trans.shares * trans.price) AS net_asset_value
FROM tbl_portfolio port
INNER JOIN tbl_registration reg ON reg.portfolio_id = port.id
LEFT JOIN tbl_fund fund on fund.registration_id = reg.id
LEFT JOIN tbl_transaction trans ON trans.fund_id = fund.id
GROUP BY port.id, reg.id, fund.id WITH ROLLUP
This will give you the sums id by id. You can use other JOIN operations with this as a subquery to fetch the textual names.
This will give results like this:
port_id reg_id fund_id net_asset_value
1 1 1 150.00
1 1 2 100.00
1 1 NULL 250.00 (rollup of previous two lines)
1 2 1 24.00
1 2 4 80.00
1 2 NULL 104.00 (rollup of previous two lines)
1 NULL NULL 354.00 (rollup at portfolio level)
3 1 1 40.00
3 1 2 50.00
3 1 NULL 90.00 (rollup of previous two lines)
3 2 1 14.00
3 2 4 60.00
3 2 NULL 74.00 (rollup of previous two lines)
3 NULL NULL 164.00 (rollup at portfolio level)
NULL NULL NULL 518.00 (grand total)
The NULLs make it into this resultset because that's what WITH ROLLUP does. This resultset only has the IDs in it; presumably the IDs are unique even if the names aren't. Non-unique names for portfolios, funds, etc, will mess up the GROUP BY pretty badly. Hence my earlier comment about retrieving the names.

Select query with MySQL database where distinct member id with least registration time are to be selected... How can I do it?

I am trying to work on a query where my database schema looks like:
MEMBER TABLE
member_id----------member_name--------member_nickname
1 prashant prash
2 christopher chris
3 nick nick
4 harvey harvey
----------------------------------------------------------
CHECKIN TABLE
checkin_id------------------member_id------------------reg_on
1 1 1287713712
2 1 1287723999
3 2 1287733712
4 1 1287735912
5 3 1287803712
6 2 1287833712
Now when I select distinct users, I want to join both tables to see the following result:
member_id----------------member_name---------------reg_on
1------------------------------prashant -----------------1287713712
2------------------------------christopher---------------1287733712
3------------------------------nick------------------------1287803712
Please Help!!!
Edit Don't know what I was thinking by overcomplicating my previous answer...
This is a simple JOIN with an aggregate:
SELECT
m.member_id,
m.member_name,
MIN(c.reg_on) AS reg_on
FROM
MEMBER m JOIN CHECKIN c ON m.member_id = c.member_id
GROUP BY m.member_id, m.member_name
If you select and group by the checkin table, that will give you the data you want, then join to the member table to fill out the rest of the query.
SELECT c.member_id, m.member_name, MIN(c.reg_on) AS reg_on
FROM checkin c
JOIN member m ON m.member_id = c.member_id
GROUP BY c.reg_on
ORDER BY c.member_id;
No subselect necessary.