Sum of columns from tables in join - mysql

I have 3 tables, Orders, Orders_products and Orders_total. Currently i have a query that gets the SUM of products for each months, but now we would like to also add the freight cost that is in a different table.
I tried with the following that returned the correct total_value, but the total_shipping is 5 times as big. This i think has to due with that orders, can have multiplie products, but i cant figure out what else to do.
SELECT Count(DISTINCT O.orders_id) AS Orders,
Sum(OP.final_price * OP.products_quantity) AS total_value,
Date_format(O.last_modified, '%m-%Y') AS date_interval,
Sum(OT.value) AS total_shipping
FROM
orders AS O
LEFT JOIN
orders_total AS OT
ON ( OT.orders_id = O.orders_id
AND OT.class = 'ot_shipping' ),
orders_products AS OP
WHERE
( O.orders_id = OP.orders_id )
AND ( O.orders_status = 3 )
GROUP BY date_interval
ORDER BY O.last_modified DESC
The returned value is:
+----+------------+---------------+----------------+
| ID | total_value| date_interval | total_shipping |
+----+------------+---------------+----------------+
| 17 | 55912.2160 | 01-2014 | 24954 |
Expected:
+----+------------+---------------+----------------+
| ID | total_value| date_interval | total_shipping |
+----+------------+---------------+----------------+
| 17 | 55912.2160 | 01-2014 | 4938 |
Here is the sqlfiddle http://sqlfiddle.com/#!2/dfe10/1/0
It contains one order, with 3 products in it. the expected total_value is 500 and the expected total_shipping is also 500, but returns 1500 (3 x products). Sadly i had to remove a lot of fields from my table due to a limit in sqlfiddle of max 8000 chars.

Try putting the shipping value into an inline view:
select count(*) as Orders,
sum(ord.order_total_value) as total_value,
ord.date_interval as date_interval,
sum(ship.order_shipping_value) as total_shipping
from
(
SELECT O.orders_id,
O.last_modified AS modified_date,
Sum(OP.final_price * OP.products_quantity) as order_total_value,
Date_format(O.last_modified, '%m-%Y') as date_interval
FROM orders AS O
INNER JOIN orders_products AS OP on O.orders_id = OP.orders_id
WHERE O.orders_status = 3
GROUP BY date_interval,O.orders_id
) ord LEFT OUTER JOIN
(
SELECT orders_id,sum(value) as order_shipping_value
FROM orders_total
WHERE class='ot_shipping'
GROUP BY orders_id
) ship ON ord.orders_id = ship.orders_id
GROUP BY ord.date_interval
ORDER BY modified_date DESC;

Related

Group_Concat with multiple joined tables

I have two main tables that comprise bookings for events.
A Registrants table (Bookings) R and an Events table E.
There are also two connected tables, Field_Values V and Event_Categories C
This diagram shows the relationship
What I am trying to do is create an Invoice query that mirrors the user's shopping cart. Often a user will book multiple events in one transaction, so my invoice should have columns for the common items e.g. User Name, User Email, Booking Date, Transaction ID and aggregated columns for the invoice line item values e.g. Quantity "1,2" Description "Desc1, Desc2" Price "10.00, 20.00" where there are two line items in the shopping cart.
The Transaction ID (dcea4_eb_registrant.transaction_id) is unique per Invoice and repeated per line item in that sale.
I have the following query which produces rows for each line item
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
E.event_date AS SERVICEDATE,
Concat('Ad-Hoc Booking:',E.title) AS DESCRIPTION,
R.number_registrants AS QUANTITY,
FORMAT(R.amount / R.number_registrants,2) AS RATE,
R.amount AS AMOUNT,
C.category_id as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY E.event_date, E.title, R.id, R.first_name, R.last_name, R.email,R.register_date, R.amount, R.comment
ORDER BY R.register_date DESC, R.transaction_id
This produces output like this
I'm using the following query to try to group together the rows with a common transaction_ID (rows two and three in the last picture) - I add group_concat on the columns I want to aggregate and change the Group By to be the transaction_id
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
Group_ConCat( E.event_date) AS SERVICEDATE,
Group_ConCat( Concat('Ad-Hoc Booking:',E.title)) AS DESCRIPTION,
Group_ConCat( R.number_registrants) AS QUANTITY,
Group_ConCat( FORMAT(R.amount / R.number_registrants,2)) AS RATE2,
Group_ConCat( R.amount) AS AMOUNT,
Group_ConCat( C.category_id) as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY R.transaction_id
ORDER BY R.register_date DESC, R.transaction_id
But this produces this output
It seems to be multiplying the rows. The Quantity column in the first row should just be 1 and in the second row it should be 2,1 .
I've tried using Group_Concat with DISTINCT but this doesn't work because often the values being concatenated are the same (e.g. the price for two events being booked are both the same) and the query only returns one value e.g. 10 and not 10, 10. The latter being what I need.
I'm guessing the issue is around the way the tables are joined but I'm struggling to work out how to get what I need.
Pointers in the right direction most appreciated.
You seem determined to go in what seems to me to be the wrong direction, so here's a gentle nudge down that hill...
Consider the following...
CREATE TABLE users
(user_id SERIAL PRIMARY KEY
,username VARCHAR(12) UNIQUE
);
INSERT INTO users VALUES
(101,'John'),(102,'Paul'),(103,'George'),(104,'Ringo');
DROP TABLE IF EXISTS sales;
CREATE TABLE sales
(sale_id SERIAL PRIMARY KEY
,purchaser_id INT NOT NULL
,item_code CHAR(1) NOT NULL
,quantity INT NOT NULL
);
INSERT INTO sales VALUES
( 1,101,'A',1),
( 2,103,'A',2),
( 3,103,'A',3),
( 4,104,'A',1),
( 5,104,'A',2),
( 6,104,'A',3),
( 7,103,'B',2),
( 8,103,'B',2),
( 9,104,'B',3),
(10,103,'B',2),
(11,104,'B',2),
(12,104,'B',1);
SELECT u.*
, x.sale_ids
, x.item_codes
, x.quantities
FROM users u
LEFT
JOIN
( SELECT purchaser_id
, GROUP_CONCAT(sale_id ORDER BY sale_id) sale_ids
, GROUP_CONCAT(item_code ORDER BY sale_id) item_codes
, GROUP_CONCAT(quantity ORDER BY sale_id) quantities
FROM sales
GROUP
BY purchaser_id
) x
ON x.purchaser_id = u.user_id;
+---------+----------+---------------+-------------+-------------+
| user_id | username | sale_ids | item_codes | quantities |
+---------+----------+---------------+-------------+-------------+
| 101 | John | 1 | A | 1 |
| 102 | Paul | NULL | NULL | NULL |
| 103 | George | 2,3,7,8,10 | A,A,B,B,B | 2,3,2,2,2 |
| 104 | Ringo | 4,5,6,9,11,12 | A,A,A,B,B,B | 1,2,3,3,2,1 |
+---------+----------+---------------+-------------+-------------+

Mysql: how to update a table column based on another table with a many to many relationship

Using mysql 7.0
Lets say I have two tables products, orders
orders
id | date | latest_product_name
1 | ... | NULL
products
id| order_id | name | created_at
1 | 1 | Ball | 2020-07-10
2 | 1 | Car | 2020-07-11
How can I generate a query that updates order's latest_product_name column with Car in our case for order 1, because it was created at the latest date.
of course this would update every order with it's latest product
something like:
UPDATE orders SET latest_product_name = Latest.name
FROM (
SELECT p.name, DISTINCT(p.order_id) as order_id
FROM products as p
Order BY latest_file_date DESC
) as Latest
WHERE Latest.order_id = orders.id
Try the below way -
with cte as
(
SELECT name,order_id, row_number() over(partition by order_id order by created_at DESC) as rn
FROM products
)
UPDATE orders join cte
on cte.order_id = orders.id
SET latest_product_name = cte.name where rn=1
This query specifically retrieves the product that was added last
UPDATE orders SET latest_product_name =
(select prod_name
FROM products prods
where prods.order_id = orders.id
and prods.created_at = (select max(created_at)
from products prods_
where prods_.order_id = prods.order_id));
The following will achieve the same result, and is more compact, but perhaps not as logical
UPDATE orders SET latest_product_name =
(select prod_name
FROM products
order by created_at desc
limit 1
);
Se how both solutions work at this Fiddle

Query to group without lost IF function

I created a query to search for all my stock products that are in orders placed, and I created an alias "total_vendido" that adds the products when they are kits or units, so far this is ok. But now I need to group the sizes and add this "total_vendido" alias by size.
Query:
SELECT `gp`.`id`, `gp`.`data`, `gp`.`status`, `gp`.`situacao`, `gp`.`nome`,
`gp`.`razao_social`, `gp`.`email`, `gp`.`telefone`,
`itens`.*,
IF(itens.tipo = 'K',
SUM(itens.qtde_prod) * itens.qtde_lote,
SUM(itens.qtde_prod)
) AS total_vendido,
`estoq`.`titulo`
FROM `ga845_pedidos_view` `gp`
JOIN `ga845_pedido_itens` `itens` ON `itens`.`pedido_id` = `gp`.`id`
JOIN `ga845_produtos` `prod` ON `prod`.`id` = `itens`.`produtos_id`
JOIN `ga845_produtos_estoque` `estoq` ON `estoq`.`id` = `prod`.`estoques_id`
WHERE `gp`.`situacao` IN('Pedido Realizado', 'Pagamento Aprovado',
'Pedido em Separação', 'Pedido Separado')
AND date(gp.data) >= '2020-07-25'
AND date(gp.data) <= '2020-07-25'
AND `estoq`.`id` IN('24')
GROUP BY `itens`.`tamanho_prod`, `estoq`.`id`
ORDER BY `estoq`.`id` ASC, `itens`.`tamanho_prod` ASC
Current result (only important columns)
tamanho_prod | tipo | total_vendido
G | K | 5
G | U | 1
M | K | 1
P | U | 8
Expected result (only important columns)
tamanho_prod | total_vendido
G | 6
M | 1
P | 8
Code related to Expected result (only important columns)
SELECT
, `itens`.`tamanho_prod`
, SUM( IF(itens.tipo = 'K',
itens.qtde_prod * itens.qtde_lote,
itens.qtde_prod
) AS total_vendido
FROM `ga845_pedidos_view` `gp`
JOIN `ga845_pedido_itens` `itens` ON `itens`.`pedido_id` = `gp`.`id`
JOIN `ga845_produtos` `prod` ON `prod`.`id` = `itens`.`produtos_id`
JOIN `ga845_produtos_estoque` `estoq` ON `estoq`.`id` = `prod`.`estoques_id`
WHERE `gp`.`situacao` IN('Pedido Realizado', 'Pagamento Aprovado',
'Pedido em Separação', 'Pedido Separado')
AND date(gp.data) >= '2020-07-25'
AND date(gp.data) <= '2020-07-25'
AND `estoq`.`id` IN('24')
GROUP BY `itens`.`tamanho_prod`
ORDER BY `itens`.`tamanho_prod` ASC
if you want an aggregated result just for itens.tamanho_prod .. then you should use group by only for this column ... and move the SUM() outside the if condition

How to return results that relate across 3 tables efficiently MySQL

Howdie do,
I have the following 3 tables: order, manifest and tracking_updates. Now, each order has foreign key called manifest_id that references the manifest table. Several orders can be in a manifest. The tracking_updates table has a foreign key called order_id that references the order table.
Now, the manifest table contains a column named upload_date. That column, upload_date is the column I need to use in order to determine if an order was uploaded in the last 30 days.
The tracking_update table can contain many updates for each order and so, I must return the most recent tracking update status for each order that matches the criteria below:
1. orders < 30 days, any delivery status
2. orders > 30 days, not delivered
Please see tables below
**Order**
ID | manifest_id
1 | 123
2 | 123
3 | 456
**Manifest**:
ID | upload_date
123 | 2015-12-15 09:31:12
456 | 2015-10-13 09:31:12
**Tracking Update**:
order_id | status_type | last_updated
1 | M | 2015-12-15 00:00:00
1 | I | 2015-12-16 07:20:00
1 | D | 2015-12-17 15:20:00
2 | M | 2015-12-15 00:00:00
2 | D | 2015-12-16 15:20:00
3 | M | 2015-10-13 00:00:00
3 | I | 2015-10-14 12:00:00
3 | E | 2015-10-15 13:50:00
This is what the result set would look like for the orders above
**Result Set**
order_id | manifest_id | latest_tracking_update_status
1 | 123 | D
2 | 123 | D
3 | 456 | E
As you can see, order 1, 2 are assigned to manifest 123 and the manifest was uploaded within the last 30 days and their latest tracking update shows a 'D' for delivered. So those two orders should be included in the result set.
The order 3 is older then 30 days, but hasn't been delivered based off the latest tracking_update status_type, so it should show up in the result set.
Now, the tracking_update table as well over 1 million updates across all orders. So I'm really going for efficiency here
Currently, I have the following queries.
Query #1 returns orders that have been uploaded within the last 30 days and their corresponding latest tracking update
SELECT
fgw247.order.id as order_id,
(SELECT
status_type
FROM
tracking_update as tu
WHERE
tu.order_id = order_id
ORDER BY
tu.ship_update_date DESC
LIMIT
1
) as latestTrackingUpdate
FROM
fgw247.order, manifest
WHERE
fgw247.order.manifest_id = manifest.id
AND
upload_date >= '2015-12-12 00:00:00'
Query #2 returns the order_id and latest tracking update for every order in the tracking_update table:
SELECT tracking_update.order_id,
substring_index(group_concat(tracking_update.status_type order by tracking_update.last_updated), ',', -1)
FROM
tracking_update
WHERE
tracking_update.order_id is not NULL
GROUP BY tracking_update.order_id
I'm just not sure how to combine these queries to get my orders that match the criteria:
orders < 30 days, any delivery status
orders > 30 days, not delivered
Any ideas would be GREATLY appreciated.
* UPDATE *
This is the current query thanks to answer selected:
select
o.id, t.maxudate, tu.status_type, m.upload_date
from
(select order_id, max(last_updated) as maxudate from tracking_update group by order_id) t
inner join
tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.last_updated
right join
fgw247.order o on t.order_id=o.id
left join
manifest m on o.manifest_id=m.id
where
(tu.status_type != 'D' and tu.status_type != 'XD' and m.upload_date <='2015-12-12 00:00:00') or m.upload_date >= '2015-12-12 00:00:00'
LIMIT 10
UPDATE
This is the current query that joins the three tables rather efficiently
SELECT
o.*, tu.*
FROM
fgw247.`order` o
JOIN
manifest m
ON
o.`manifest_id` = m.`id`
JOIN
`tracking_update` tu
ON
tu.`order_id` = o.`id` and tu.`ship_update_date` = (select max(last_updated) as last_updated from tracking_update where order_id = o.`id` group by order_id)
WHERE
m.`upload_date` >= '2015-12-14 11:50:12'
OR
(o.`delivery_date` IS NULL AND m.`upload_date` < '2015-12-14 11:50:12')
LIMIT 100
Have a subquery that returns the latest update date from the tracking table for each order. Join this subquery on the tracking, orders, and manifests tables to get the details and filter based on the upload date in the where clause:
select o.order_id, t.maxudate, tu.status_type, m.upload_date
from (select order_id, max(update_date) as maxudate from tracking_update group by order_id) t
inner join tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.update_date
right join orders o on t.order_id=o.order_id
left join manifests m on o.manifest_id=m.manifest_id
where (tu.status_type<>'D' and curdate()-m.upload_date>30) or curdate()-m.upload_date<=30
It may be more efficient to use a union query instead of the or criteria in the where clause.
You can perform a JOIN with the 2nd query result like
SELECT
fgw247.order.id as order_id,
xx.some_column,
(SELECT
status_type
FROM
tracking_update as tu
WHERE tu.order_id = order_id
ORDER BY
tu.ship_update_date DESC
LIMIT
1
) as latestTrackingUpdate
FROM
fgw247.order JOIN manifest
ON fgw247.order.manifest_id = manifest.id
JOIN (
SELECT tracking_update.order_id,
substring_index(group_concat(tracking_update.status_type order by tracking_update.last_updated), ',', -1) AS some_column
FROM
tracking_update
WHERE
tracking_update.order_id is not NULL
GROUP BY tracking_update.order_id ) xx ON xx.order_id = fgw247.order.id
WHERE upload_date >= '2015-12-12 00:00:00'

Mysql subquery issues

Here is my mysql schema and query.
http://sqlfiddle.com/#!2/73b0d/2
I want sum(each day's memo.discount), date, sum(each day's sale sum(item.sell_price)) in each row. But can't seem to find out the way. How can I do this?
Expected outcome.
total_discount | added_on | total_sale
300 | 2014-06-25 00:00:00 | 1580
230 | 2014-06-26 00:00:00 | 980
Thanks in advance.
SELECT
SUM(m.discount) AS total_discount,
m.added_on,
sub0.total_sold AS total_sale
FROM memo m
LEFT OUTER JOIN
(
SELECT DATE(memo.added_on) AS group_added_on, SUM(item.sell_price) AS total_sold
FROM memo
JOIN memo_item ON memo_item.memo_id = memo.id
JOIN item ON item.id = memo_item.item_id
WHERE memo.showroom_id = 2
GROUP BY group_added_on
) sub0
ON group_added_on = DATE(m.added_on)
WHERE m.showroom_id = 2 and m.added_on BETWEEN '2014-06-25' AND '2014-06-26'
GROUP BY m.added_on
This has a sub query that gets the sum of the selling prices for each day for the showroom_id you are interested in, then joins that back against the memo table.
How about this?
select
sum(m.discount) as total_discount,
m.added_on,
sum(item.sell_price) as total_sale
from memo m
join memo_item on memo_item.memo_id = m.id
join item on item.id = memo_item.item_id
where m.showroom_id = 2 and m.added_on between '2014-06-25' and '2014-06-26'
group by m.added_on
Edit: to use discount only from "memo" table... :
select
sum(discount) as total_discount,
added_on,
sum(subtotal_sale) as total_sale
from
(
select
m.discount,
m.added_on,
sum(item.sell_price) as subtotal_sale
from memo m
join memo_item on memo_item.memo_id = m.id
join item on item.id = memo_item.item_id
where m.showroom_id = 2 and m.added_on between '2014-06-25' and '2014-06-26'
group by m.id
) h
group by added_on