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
Related
So I have the following table, I managed to join users and membership tables just fine with a left join however I've been unsuccessful at summing up the individual customers' total.
Here's my code, the one-to-one associations seem to be doing fine however the summing up of the total seems to not display, what am I doing wrong? is there a different way of summing up a one-to-many association?
SELECT name, membership.userId as customerId, SUM(sales.total) as Total
FROM users
LEFT OUTER JOIN membership ON membership.userId = users.id
LEFT OUTER JOIN sales ON buyerId = users.id
Tables
Users table:
id name type
1 John Customer
2 Adam Customer
3 Robert Customer
Membership table:
id userId
1 1
2 2
3 3
Sales table:
buyerId total
1 12
1 20
1 5
2 5
2 10
3 5
3 5
Desired output:
Sales Report:
Name CustomerId Total
John 1 37
Adam 2 15
Robert 3 10
SELECT name, membership.userId as customerId, SUM(sales.total) as Total
FROM users
LEFT OUTER JOIN membership ON membership.userId = users.id
LEFT OUTER JOIN sales ON buyerId = users.id
GROUP BY name, customerId
You need to group by user.
I'm struggling to get my head around MySQL joins. I have three tables
-- events
id name
1 Event 1
2 Event 2
-- registrations
id event name
1 1 Alice
2 1 Bob
3 2 Alice
4 2 Charlie
-- scores
id event name score
1 1 Alice 10
2 1 Charlie 20
3 2 Alice 15
4 2 Bob 30
For each event I'm trying to work out
How many people registered (rows in registration table) but did NOT get a score (exclude rows in scores table)
How many people got a score (rows in scores table) but did NOT register (exclude rows in registration table)
How many people BOTH registered and got a score
I've tried different variations of
SELECT *
FROM registrations r
LEFT JOIN scores s
ON r.event = s.event
WHERE s.event IS NULL
AND r.event = 1
but I'm not sure what I should be joining on: event or name but neither are null and I never seem to get the correct numbers I'm looking for. The result at the end should be like
name reg_only score_only reg&score total
event Event 1 1 1 1 3
You can use below 3 queries in same sequence as you mentioned in your query-
SELECT e.id, e.name, COUNT(r.id) AS registered_user
FROM `events` AS e
INNER JOIN registrations AS r ON e.id=r.event
LEFT JOIN scores AS s ON e.id = s.event
WHERE s.event IS NULL;
SELECT e.id, e.name, COUNT(r.id) AS scored_user
FROM `events`scores AS e
INNER JOIN scores AS s ON e.id=s.event
LEFT JOIN registrations AS r ON e.id = r.event
WHERE r.event IS NULL;
SELECT e.id, e.name, COUNT(*) AS both_user
FROM `events` AS e
INNER JOIN registrations AS r ON e.id=r.event
INNER JOIN scores AS s ON e.id = s.event;
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?
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.
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.