I've been struggling with this query for two days now. I've got a user table with some values which has a relation with an order table (user can have multiple orders). This table has a relation with order_item (order can have multiple orderItems). Order_Item has a relation with invoice (order_item can have multiple invoices.
The branch and shop have a one-on-one relation with the user.
Here are the most important values of all the tables:
user:
-userId (int)
order
-orderId (int)
-userId (int)
-inserted (date)
order_item
-orderItemId (int)
-orderId (int)
invoice
-invoiceId (int)
-orderItemId (int)
-cost (double)
The foreign keys are self-explanatory here. User->Order->OrderItem->Invoice.
What I need is a query in which each row in the result represents a user with two columns representing the total sales (sum of costs) in 2014 and 2015.
So what it has to do is show each user in a row with some info from the user table (company name, e-mail etc etc) and two columns with the total costs of 2014 and one of 2015 based on the order.inserted date value.
An example would be:
Name: | E-Mail | 2014 Cost | 2015 Cost
Google | info#google.com | €50.000 | €45.000
Now I've gotten so far that I've got a result for the first sum (showing all users regardless of cost), only when I join a second time (to calculate the 2015 cost) my previous sum costs get completely screwed up.
I tried some select queries within joins but I couldnt get any query to work. It's not like I'm a complete beginner in SQL but this is too complex for me to figure out this exact moment.
This is the query I use to get the 2014 result (and as soon as I add a second join for 2015 it gets screwed up):
SELECT t.userId, SUM(i.cost),
t.companyName, t.email,
t.website, t.tel, t.priority,
b.name AS Branch, s.name AS `Shop Name`
FROM `user` AS t
LEFT JOIN branch AS b ON b.branchId = t.branchId
LEFT JOIN shop AS s ON s.shopId = t.shopId
LEFT JOIN `order` AS o ON (o.userId = t.userId AND YEAR(o.inserted) = 2014)
LEFT JOIN order_item AS oi ON oi.orderId = o.orderId
LEFT JOIN invoice AS i ON i.orderItemId = oi.orderItemId
GROUP BY t.userId
I really hope somebody can help me with this. (I'm using mySQL/innoDB in Navicat 8).
Ultimately, this is a form of pivot table you are trying to produce. Instead of joining and testing the year conditions in the joins' ON clauses, you may place the condition directly inside SUM() aggregates like:
-- If the year matches, add the cost value into the sum
-- Otherwise, add zero
SUM(CASE WHEN YEAR(o.inserted) = 2014 THEN i.cost ELSE 0 END) AS `2014 Cost`
This eliminates the need for those extra joins. When applying the GROUP BY, it should include all columns which could potentially be different per group. MySQL allows you to omit columns in SELECT from GROUP BY where most other RDBMS would result in a query compile error.
SELECT
t.userId,
-- Apply the aggregate SUM() conditionally for each year
SUM(CASE WHEN YEAR(o.inserted) = 2014 THEN i.cost ELSE 0 END) AS `2014 Cost`
SUM(CASE WHEN YEAR(o.inserted) = 2015 THEN i.cost ELSE 0 END) AS `2015 Cost`
t.companyName,
t.email,
t.website,
t.tel,
t.priority,
b.name AS Branch,
s.name AS `Shop Name`
FROM
`user` AS t
LEFT JOIN branch AS b ON b.branchId = t.branchId
LEFT JOIN shop AS s ON s.shopId = t.shopId
LEFT JOIN `order` AS o ON (o.userId = t.userId)
LEFT JOIN order_item AS oi ON oi.orderId = o.orderId
LEFT JOIN invoice AS i ON i.orderItemId = oi.orderItemId
GROUP BY
t.userId,
-- Adding remaining SELECT fields
-- though MySQL will allow these to be omitted
-- without breaking this particular query
t.companyName,
t.email,
t.website,
t.tel,
t.priority,
Branch,
`Shop Name`
Related
This is a slight variant of the question I asked here
SQL Query for getting maximum value from a column
I have a Person Table and an Activity Table with the following data
-- PERSON-----
------ACTIVITY------------
I have got this data in the database about users spending time on a particular activity.
I intend to get the data when every user has spent the maximum number of hours.
My Query is
Select p.Id as 'PersonId',
p.Name as 'Name',
act.HoursSpent as 'Hours Spent',
act.Date as 'Date'
From Person p
Left JOIN (Select MAX(HoursSpent), Date from Activity
Group By HoursSpent, Date) act
on act.personId = p.Id
but it is giving me all the rows for Person and not with the Maximum Numbers of Hours Spent.
This should be my result.
You have several issues with your query:
The subquery to get hours is aggregated by date, not person.
You don't have a way to bring in other columns from activity.
You can take this approach -- joins and group by, but it requires two joins:
select p.*, a.* -- the columns you want
from Person p left join
activity a
on a.personId = p.id left join
(select personid, max(HoursSpent) as max_hoursspent
from activity a
group by personid
) ma
on ma.personId = a.personId and
ma.max_hoursspent = a.hoursspent;
Note that this can return duplicates for a given person -- if there are ties for the maximum.
This is written more colloquially using row_number():
select p.*, a.* -- the columns you want
from Person p left join
(select a.*,
row_number() over (partition by a.personid order by a.hoursspent desc) as seqnum
from activity a
) a
on a.personId = p.id and a.seqnum = 1
ma.max_hoursspent = a.hoursspent;
I tried to write a query, but unfortunately I didn't succeed.
I want to know how many packages delivered over a given period by a person.
So I want to know how many packages were delivered by John (user_id = 1) between 01-02-18 and 28-02-18. John drives another car (another plate_id) every day.
(orders_drivers.user_id, plates.plate_name, orders.delivery_date, orders.package_amount)
I have 3 table:
orders with plate_id delivery_date package_amount
plates with plate_id plate_name
orders_drivers with plate_id plate_date user_id
I tried some solutions but didn't get the expected result. Thanks!
Try using JOINS as shown below:
SELECT SUM(o.package_amount)
FROM orders o INNER JOIN orders_drivers od
ON o.plate_id=od.plate_id
WHERE od.user_id=<the_user_id>;
See MySQL Join Made Easy for insight.
You can also use a subquery:
SELECT SUM(o.package_amount)
FROM orders o
WHERE EXISTS (SELECT 1
FROM orders_drivers od
WHERE user_id=<user_id> AND o.plate_id=od.plate_id);
SELECT sum(orders.package_amount) AS amount
FROM orders
LEFT JOIN plates ON orders.plate_id = orders_drivers.plate_id
LEFT JOIN orders_driver ON orders.plate_id = orders_drivers.plate_id
WHERE orders.delivery_date > date1 AND orders.delivery_date < date2 AND orders_driver.user_id = userid
GROUP BY orders_drivers.user_id
But seriously, you need to ask questions that makes more sense.
sum is a function to add all values that has been grouped by GROUP BY.
LEFT JOIN connects all tables by id = id. Any other join can do this in this case, as all ids are unique (at least I hope).
WHERE, where you give the dates and user.
And GROUP BY userid, so if there are more records of the same id, they are returned as one (and summed by their pack amount.)
With the AS, your result is returned under the name 'amount',
If you want the total of packageamount by user in a period, you can use this query:
UPDATE: add a where clause on user_id, to retrieve John related data
SELECT od.user_id
, p.plate_name
, SUM(o.package_amount) AS TotalPackageAmount
FROM orders_drivers od
JOIN plates p
ON o.plate_id = od.plate_id
JOIN orders o
ON o.plate_id = od.plate_id
WHERE o.delivery_date BETWEEN convert(datetime,01/02/2018,103) AND convert(datetime,28/02/2018,103)
AND od.user_id = 1
GROUP BY od.user_id
, p.plate_name
It groups rows on user_id and plate_name, filter a period of delivery_date(s) and then calculate the sum of packageamount for the group
I have a query to show customers and the total dollar value of all their orders. The query takes about 100 seconds to execute.
I'm querying on an ExpressionEngine CMS database. ExpressionEngine uses one table exp_channel_data, for all content. Therefore, I have to join on that table for both customer and order data. I have about 14,000 customers, 30,000 orders and 160,000 total records in that table.
Can I change this query to speed it up?
SELECT link.author_id AS customer_id,
customers.field_id_122 AS company,
Sum(orders.field_id_22) AS total_orders
FROM exp_channel_data customers
JOIN exp_channel_titles link
ON link.author_id = customers.field_id_117
AND customers.channel_id = 7
JOIN exp_channel_data orders
ON orders.entry_id = link.entry_id
AND orders.channel_id = 3
GROUP BY customer_id
Thanks, and please let me know if I should include other information.
UPDATE SOLUTION
My apologies. I noticed that entry_id for the exp_channel_data table customers corresponds to author_id for the exp_channel_titles table. So I don't have to use field_id_117 in the join. field_id_117 duplicates entry_id, but in a TEXT field. JOINING on that text field slowed things down. The query is now 3 seconds
However, the inner join solution posted by #DRapp is 1.5 seconds. Here is his sql with a minor edit:
SELECT
PQ.author_id CustomerID,
c.field_id_122 CompanyName,
PQ.totalOrders
FROM
( SELECT
t.author_id
SUM( o.field_id_22 ) as totalOrders
FROM
exp_channel_data o
JOIN
exp_channel_titles t ON t.author_id = o.entry_id AND o.channel_id = 3
GROUP BY
t.author_id ) PQ
JOIN
exp_channel_data c ON PQ.author_id = c.entry_id AND c.channel_id = 7
ORDER BY CustomerID
If this is the same table, then the same columns across the board for all alias instances.
I would ensure an index on (channel_id, entry_id, field_id_117 ) if possible. Another index on (author_id) for the prequery of order totals
Then, start first with what will become an inner query doing nothing but a per customer sum of order amounts.. Since the join is the "author_id" as the customer ID, just query/sum that first. Not completely understanding the (what I would consider) poor design of the structure, knowing what the "Channel_ID" really indicates, you don't want to duplicate summation values because of these other things in the mix.
select
o.author_id,
sum( o.field_id_22 ) as totalOrders
FROM
exp_channel_data customers o
where
o.channel_id = 3
group by
o.author_id
If that is correct on the per customer (via author_id column), then that can be wrapped as follows
select
PQ.author_id CustomerID,
c.field_id_122 CompanyName,
PQ.totalOrders
from
( select
o.author_id,
sum( o.field_id_22 ) as totalOrders
FROM
exp_channel_data customers o
where
o.channel_id = 3
group by
o.author_id ) PQ
JOIN exp_channel_data c
on PQ.author_id = c.field_id_117
AND c.channel_id = 7
Can you post the results of an EXPLAIN query?
I'm guessing that your tables are not indexed well for this operation. All of the columns that you join on should probably be indexed. As a first guess I'd look at indexing exp_channel_data.field_id_117
Try something like this. Possibly you have error in joins. also check whether joins on columns are correct in your databases. Cross join may takes time to fetch large data, by mistake if your joins are not proper on columns.
select
link.author_id as customer_id,
customers.field_id_122 as company,
sum(orders.field_id_22) as total_or_orders
from exp_channel_data customers
join exp_channel_titles link on (link.author_id = customers.field_id_117 and
link.author_id = customer.channel_id = 7)
join exp_channel_data orders on (orders.entry_id = link.entry_id and orders.entry_id = orders.channel_id = 3)
group by customer_id
I am trying to create a query that will take information out of four tables for a billing system that I am creating. I have the following tables:
Table Invoice
InvoiceID (PK)
ClientID
Date
Status
...
Table Client
ClientID (PK)
ClientName
...
Table InvoiceItem
ItemID (PK)
InvoiceID
Amount
...
Table Payments
PaymentID (PK)
InvoiceID
Amount
...
I need to create a query where I can access information from the Invoice table along with the client name, and the sum of all invoice items and payments associated with the invoice.
I have tried the following:
SELECT
Invoice.InvoiceID,
Invoice.`Date`,
Invoice.Terms,
Invoice.DateDue,
Invoice.Status,
Client.ClinicName,
SUM(InvoiceItem.Amount),
SUM(Payment.PaymentAmount)
FROM Invoice
JOIN (Client, InvoiceItem, Payment) ON
(Client.ClientID=Invoice.ClientID AND
InvoiceItem.InvoiceID=Invoice.InvoiceID AND
Payment.InvoiceID=Invoice.InvoiceID)
And while this kind-of works, it is multiplying the SUM() by the number of records used to get the sum (i.e. if there are two payments - 800,400 - It gives me (800+400)*2 -- 2400). I am guessing that there is something with how I am using the join, and I have honestly never had to use join for more than one table, and I would always use GROUP BY, but I can't seem to get that to work correctly.
To make matters worse, I have been lost to the world of vb.net/MSSQL client-side programming for the past several years, so my MySQL is rather rough.
Your problem is that you can't aggregate over two independent tables at once in a single query. However you can do it using subqueries.
SELECT Invoice.InvoiceID, Invoice.`Date`, Invoice.Terms, Invoice.DateDue, Invoice.Status, Client.ClinicName, InvoiceItemSum.SumOfAmount, PaymentSum.SumOfPaymentAmount
FROM Invoice
INNER JOIN Client ON Client.ClientID = Invoice.ClientID
INNER JOIN (
SELECT InvoiceID, SUM(Amount) AS SumOfAmount
FROM InvoiceItem
GROUP BY InvoiceID
) InvoiceItemSum ON InvoiceItemSum.InvoiceID = Invoice.InvoiceID
INNER JOIN (
SELECT InvoiceID, SUM(PaymentAmount) AS SumOfPaymentAmount
FROM Payment
GROUP BY InvoiceID
) PaymentSum ON PaymentSum.InvoiceID = Invoice.InvoiceID
Here try this one
SELECT a.InvoiceID,
a.`Date`,
a.Terms,
a.DateDue,
a.Status,
b.ClinicName,
SUM(c.Amount),
SUM(d.PaymentAmount)
FROM Invoice a
INNER JOIN Client b
on a.ClientID = b.ClientID
INNER JOIN InvoiceItem c
ON c.InvoiceID = a.InvoiceID
INNER JOIN JOIN Payment d
ON d.InvoiceID = a.InvoiceID
GROUP BY a.InvoiceID,
a.`Date`,
a.Terms,
a.DateDue,
a.Status,
b.ClinicName
can you elaborate more on this?
it is multiplying the SUM() by the number of records used to get the
sum (i.e. if there are two payments - 800,400 - It gives me
(800+400)*2 -- 2400)
Try this:
SELECT
Invoice.InvoiceID,
Invoice.`Date`,
Invoice.Terms,
Invoice.DateDue,
Invoice.Status,
Client.ClinicName,
SUM(InvoiceItem.Amount),
SUM(Payment.PaymentAmount)
FROM Invoice
JOIN Client ON Client.ClientID=Invoice.ClientID
JOIN InvoiceItem ON InvoiceItem.InvoiceID=Invoice.InvoiceID
JOIN Payment ON Payment.InvoiceID=Invoice.InvoiceID
group by 1,2,3,4,5,6;
I did two things to your query:
Created separated joins for each of the child tables
Added a group by, without which the sum won't work correctly (fyi, in all other databases, omitting the group by would actually result in a syntax error)
you can also achive it by "CROSS APPLY"
SELECT Invoice.InvoiceID, Invoice.`Date`, Invoice.Terms, Invoice.DateDue, Invoice.Status, Client.ClinicName, InvoiceItemSum.SumOfAmount, PaymentSum.SumOfPaymentAmount
FROM Invoice
INNER JOIN Client ON Client.ClientID = Invoice.ClientID
CROSS APPLY ( SELECT ISNULL(SUM(Amount),0) AS SumOfAmount
FROM InvoiceItem
WHERE InvoiceID = Invoice.InvoiceID
) InvoiceItemSum
CROSS APPLY ( SELECT ISNULL(SUM(PaymentAmount),0) AS SumOfPaymentAmount
FROM Payment
WHERE InvoiceID = Invoice.InvoiceID
) PaymentSum
I'm trying to do a left join over 3 tables with a where clause, and I can't get it to work.
My tables are:
sale:
sale_id (int)
terminal_id (int)
zread_id (int)
... more fields about total amounts, dates times etc
sale_payment:
payment_id (int)
sale_id (int)
payment_type (enum: 'P','W','A')
method_id (int)
payment_amount (Decimal)
sale_writeoff_method:
method_id (int)
description (varchar)
display_order (int)
Sales can be finalised (paid) in 3 different ways (hence the Enum) a physical payment - Cash, Cheque etc, a "Write Off" payment - where stock gets used up at cost price (i.e. Wastage, Giveaways etc) or Accounts - Customer credit etc
At the end of a sales period (end of day) the user does a Z-Read which collects all the transactions that occur and generates a report where the user then has to balance the cash in the drawer etc. When I'm creating the initial Z-Read object, I can collect the information I need by using the query:
SELECT m.method_id, m.description, SUM(s.sale_total) as z_total, COUNT(s.sale_id) as total_sales
FROM sale_writeoff_method m
LEFT JOIN sale_payment p ON m.method_id = p.method_id
LEFT JOIN sale s ON s.sale_id = p.sale_id
WHERE s.zread_id IS NULL
AND (p.payment_type = 'W' OR p.payment_type IS NULL)
AND (s.terminal_id = ? OR s.terminal_id IS NULL)
GROUP BY p.payment_type, m.method_id
ORDER BY m.display_order;
Once everything is balanced and finalised, all of the collected sales for all types in the sales table are flagged with the zread_id that results from inserting this object.
Now my issue is, when I need to re-create the z-read object in the future, for re-printing reports for example, I can't get all the method_id's and descriptions to show - the query I've been using is:
SELECT m.method_id, m.description, SUM(s.sale_total) as z_total, COUNT(s.sale_id) as total_sales
FROM sale_writeoff_method m
LEFT JOIN sale_payment p ON m.method_id = p.method_id
LEFT JOIN sale s ON s.sale_id = p.sale_id
WHERE s.zread_id = 1
AND (p.payment_type = 'W' OR p.payment_type IS NULL)
GROUP BY p.payment_type, m.method_id
ORDER BY m.display_order;
But it only displays methods that had sales attached for that Z-Read period. I can't use WHERE s.zread_id IS NULL because that will include all the sales that haven't been finalised yet.
Any suggestions would be greatly appreciated!
The problem is that a left join returns nulls for the joined column values where no matching row is found, but you are checking those column values in the where clause, but the where predicates are is executed after all the rows are joined, so they'll never match and your outer joins are sunk.
ie, this example query:
select *
from table1 t1
left join table2 t2 on t2.fk = t1.id
where t2.col1 = 'x'
will never return any rows that don't have corresponding rows in table2, because col1 will be null, and comparisons with null are always false, except for col1 is null.
To fix this, you need to move the tests into the ON clause, so the comparison happens while the join is being made, like this:
select *
from table1 t1
left join table2 t2 on t2.fk = t1.id and t2.col1 = 'x'
Now the left join will still return rows while matching on the key, and applying extra predicates to further refine the match.
In your case, you are doing an outer (ie left) join to sale_payment p, but testing p.payment_type = 'W' in the where clause, which won't work.
Here's the fixed query, with the tests on the left-joined tables made in the ON clause:
SELECT
m.method_id,
m.description,
SUM(s.sale_total) as z_total,
COUNT(s.sale_id) as total_sales
FROM sale_writeoff_method m
LEFT JOIN sale_payment p ON m.method_id = p.method_id AND p.payment_type = 'W'
LEFT JOIN sale s ON s.sale_id = p.sale_id AND s.terminal_id = ?
GROUP BY m.method_id, m.description
ORDER BY m.display_order;
Note that I also removed the group by p.payment_type, because you haven't selected that column, and I added group by m.description, because you have selected that.
You may need to fine tune the query, but hopefully this will be pretty close