MySQL automatically updating field when inserting data - php

I'm making a web application to make customers order items for anything. For that I've made a MySQL database which has the following tables:
customers
orders
orders-items
products
In the customers table is all the information about the person such as:
The customer ID, for the primary key and auto increment (id)
The first name (first_name)
The last name (last_name)
The email address (email_address)
Information about the customer (customer_info)
Example:
In the orders table is all the specific information about it such as:
The order ID, for the primary key and auto increment (id)
Which customer it ordered, linked with id field from the customers table (customer_id)
Order information (order_info)
The location where the order needs to go to (location)
The total price the customer has to pay (total_price)
When the order was created (created)
Example:
In the orders-items table are all the items which every customer ordered, this is being linked by the order-id from the previous table.
The ID, for primary key and auto increment, not used for any relation (id)
The order ID, used for which product is for which order. This is linked with the id field from the orders table (order_id)
The product ID, this is used for what product they ordered, this is linked with the id field from the products table. (product_id)
The amount of this product they ordered (quantity)
Example:
In the products table is all the information about the products:
The ID, for primary key and auto incrementing, This is linked with the product_id field from the order_items table (id)
The name of the product (name)
The description of the product (description)
The price of the product (price)
Example:
Question:
I've got this query:
SELECT `orders-items`.`order_id` , SUM(`orders-items`.`quantity`* `products`.`price`) total
FROM `orders-items`
INNER JOIN `Products` ON `orders-items`.`products_id` = `products`.`id`
And it shows me a list of all the total prices every order_id has to pay.
But how do I make this so that this value of the total_price every order_id has to pay is automatticly inserted into the orders table inside the total_price field at the right order_id when inserting a product into my orders-list table?
Or is it still better to not keep track of the total_prices the customers have to pay?

A couple things to consider.
Having a total_price for itself is redundant. You can learn this total by summing the prices of this order's items at any time. It might be interesting to have it for performance reasons, but is this really necessary for your scenario? It rarely is.
Having a price on each order_item in the other hand would be useful. And the why is because thoses products prices might change in the future and you don't want to lose information of for how much they were sold at the time of that particular sale.
In any case, you can update your total_price using triggers like this:
DELIMITER $$
CREATE TRIGGER order_items_insert AFTER INSERT ON `orders-items` FOR EACH ROW
BEGIN
UPDATE orders o INNER JOIN (SELECT i.order_id id, SUM(i.quantity * p.price) total_price FROM `orders-items` i INNER JOIN products p ON p.id = i.products_id AND i.order_id = new.order_id) t ON t.id = o.id SET o.total_price = t.total_price;
END$$
CREATE TRIGGER order_items_update AFTER UPDATE ON `orders-items` FOR EACH ROW
BEGIN
UPDATE orders o INNER JOIN (SELECT i.order_id id, SUM(i.quantity * p.price) total_price FROM `orders-items` i INNER JOIN products p ON p.id = i.products_id AND i.order_id = new.order_id) t ON t.id = o.id SET o.total_price = t.total_price;
END$$
CREATE TRIGGER order_items_delete AFTER DELETE ON `orders-items` FOR EACH ROW
BEGIN
UPDATE orders o INNER JOIN (SELECT i.order_id id, SUM(i.quantity * p.price) total_price FROM `orders-items` i INNER JOIN products p ON p.id = i.products_id AND i.order_id = old.order_id) t ON t.id = o.id SET o.total_price = t.total_price;
END$$
DELIMITER ;

One Option would be a trigger, which is executed on inserting a new customer.
Alternative you could going for a stored procedure. With this, you only need to call the procedure InsertCustomer, and the database handles the price update for you -> you will not have these dependencies in your Application.
Another way could be, to do 2 querys (insert and update) in a transaction. But for this i recommend Domain Driven Design. You would have a service, which has the method CreateCustomer(Customer $customer), which do a insert query and then the update query. On success it commits the transaction and returns true, if not success, it cancel the transaction and reuturns false. It should be your own convention to only manipulate data with help of the services (which knows the business logic).

Related

How to sum values across a set of categories, in SQL?

I want to get a list of category items and display the total amount linked to those categories. I have the "amount" field in the "transaction" table and I want to link it to category table.
This is how my tables are:
Category Master
Subcategory Master
Item Master
Transaction
So to get my "Amount" field to category, I would have to pass a certain common column between them. I have CategoryID in SubCategory master, subcatid in item master and similarly itemid in transaction.
Earlier when I grouped the amount using transaction date, the process went smoothly:
SELECT TransactionDate, SUM(Amount) FROM transaction GROUP BY MONTH(TransactionDate)
Now the problem I'm facing with grouping it using categoryname is that all of the amount seems to be =50 whereas it is still different in the database. I know that this is something really silly, but I am comparatively new to programming and not sure how to use logic appropriately.
SELECT categorymaster.CategoryName, transaction.Amount
FROM categorymaster
INNER JOIN subcategorymaster
INNER JOIN itemmaster
INNER JOIN transaction
GROUP BY categorymaster.CategoryName
This answer assumes your primary key in each table is named "ID". You didn't provide that info.
SELECT categorymaster.CategoryName, sum(transaction.Amount)
FROM categorymaster
INNER JOIN subcategorymaster
ON subcagetorymaster.CategoryId = categorymaster.ID
INNER JOIN itemmaster
ON itemmaster.SubCatId = subcategorymaster.ID
INNER JOIN transaction
ON transaction.ItemId = itemmaster.ID
GROUP BY categorymaster.CategoryName

Mysql query with count on another table

I have two tables, one that has transaction information such as id, status, price and date. I have another table that stores the number of items in that order as individual rows. So, say transaction 2 has 10 items, there will be 10 rows in the second table of different items. What im trying to do is run a query that lists transactions and the number of items sold in that transaction. I imagine this would require a count on the second table, but im not entirely sure of how to do it. This is the basic layout of the database
transaction id, date, price, discount, status
items: id, transaction_id, item_name, email, date_ordered, hash
Thanks in advance for all the help.
Group by the columns in the transaction table you want to select. Then add a count of the items
select t.id, count(i.id) as item_count
from transaction t
left join items i on i.transaction_id = t.id
group by t.id
You can do a left join on both tables like below
select t.*, tab.total_order
from transaction t
left join
(
select transaction_id, count(*) as total_order
from items
group by transaction_id
) tab on t.id = tab.transaction_id

MySQL sum up numbers in a relational database

I'm making a web application to make customers order items for anything. For that I've made a MySQL database which has the following tables:
customers
orders
order-items
products
In the customers table is all the information about the person such as:
The customer ID, for the primary key and auto increment (id)
The first name (first_name)
The last name (last_name)
The email address (email_adress)
Information about the customer (customer_info)
Example:
In the orders table is all the specific information about it such as:
The order ID, for the primary key and auto increment (id)
Which customer ordered it, linked with id field from the customers table (customer_id)
Order information (order_info)
The location where the order needs to go to (location)
When the order was created (created)
Example:
In the order-items table are all the items which every customer ordered, this is being linked by the order-id from the previous table.
The ID, for primary key and auto increment, not used for any relation (id)
The order ID, used for which product is for which order. This is linked with the id field from the orders table (order_id)
The product ID, this is used for what product they ordered, this is linked with the id field from the products table. (product_id)
The amount of this product they ordered (quantity)
Example:
In the products table is all the information about the products:
The ID, for primary key and auto incrementing, This is linked with the product_id field from the order_items table (id)
The name of the product (name)
The description of the product (description)
The price of the product (price)
Example:
The problem
Bob ordered product_id 2, times 3. Which is the sandwich with beef with the price of 2.50, which we have to multiply by 3 because it has been ordered 3 times. Which is 7.50
Bob also ordered product_id 3, times 5. Which is the sandwich with chicken with the price of 3.00, which we have to multiply by 5 because it has been ordered 5 times. Which comes out on 15.00
Now I need to sum these up. Which is 15.00 + 7.50 = 22.50
The question
How do I get the product_id linked with the actual price of the product_id? which I can then multiply by the quantity.
And then sum up all those values with the same order_id
For the first order we get product 2 (Quantity 3) and product 3 (Quantity 5), which should add 2.503 + 3.005 = 22.50
You asked, "
How do i get the product_id linked with the actual price of the product_id? Which i can then multiply by the quantity... And then sum up all those values with the same order_id."
Like this:
SELECT OI.Order_ID, Sum(OI.Quantity * P.Price) Total_Price
FROM `order-items` OI
INNER JOIN Products P
on OI.Products_Id = P.ID
GROUP BY OI.Order_ID
Expected output for sample data in question:
ORDER_ID Total_price
1 22.50
This is why I asked about sample Output. I'm not sure what columns you were trying to return so I returned just the sum total for each order.
Now what this says
Return a row for each order showing the order ID and the total_price which is the sum of (quantity ordered * price all lines for that order.
This is accomplished by looking at the order-items table (in back tic's because I'm not sure if mySQL like's dashes (-) in names.). Joining these tables based on the product_Id between the ordered items table and the price table. and then group the results by the ordered item allowing the sum to aggregate all the rows for an order times the price of the item on that line of the order together.
You can build an intermediate table that has the totals for each order, then JOIN that with the orders table to get the customerID of that order, which you join with customers to get the customer name
SELECT C.FirstName, C.LastName, Totals.Order_ID, Totals.NetCost
FROM (SELECT OI.order_id, SUM(OI.quantity * P.price) as NetCost
FROM orderitems as OI INNER JOIN products as P ON OI.products_id = P.id
GROUP BY OI.order_id
) as Totals
INNER JOIN orders as O on O.ID = Totals.order_id
INNER JOIN customers as C on C.ID = O.customer_id
And the problem #xQbert is talking about is that if your customer places an order on Monday, you raise your price on Tuesday, and run this report on Wed, the report will show a total different from what the customer saw when he approved the order. A better design would be to store orderprice in orderitem, or at least the ordertotal in orders so subsequent price changes don't affect historic orders. The former is better in case a customer wants a refund on one item.
EDIT: I mean the problem #xQbert mentions in his comment, his answer came in ahead of mine but I missed it on the refresh
EDIT 2: More explanation, as requested by #Bas
It's easiest to think of this query from the inside and work outward, so let me start with getting the prices of items.
I want to link an item in orderitem with it's price, so JOIN orderitem (I give it an alias of OI) to products (I give an alias of P) on the product ID. Product ID is called products_id in OrderItem, but just ID in products.
Once I have that, I can multiply the price from Products by the quantity in OrderItems to get a cost for just that item
Because I want the total of everything for each order, I use a GROUP BY clause on this sub query, with the thing I'm grouping by being order_id. With a GROUP BY, you usually take the field or fields you are grouping by, and some "aggregate" fields where you take the sum, or max, or average, or whatever of them, and it's the sum or whatever for all rows that are in the same group.
So at this point we have defined the query inside the parenthesis. I named that Totals, and it acts just like a real table, but it goes away when your query is over.
The next step is to combine this "totals" table with your orders table so we can look up the customer ID, and then combine it with Customers so we can look up the customer name. I used aliases for Customer and Orders (C and O respectively), but they are not necessary. In fact, this could be rewritten most if not all of the aliases. I think it makes it more readable, but if they confuse you, you could use the following instead, it's the same. I think Totals is necessary, but maybe not even that.
SELECT FirstName, LastName, Totals.Order_ID, Totals.NetCost
FROM (SELECT order_id, SUM(quantity * price) as NetCost
FROM orderitems INNER JOIN products ON products_id = products.id
GROUP BY order_id
) as Totals
INNER JOIN orders on orders.ID = Totals.order_id
INNER JOIN customers on customers.ID = orders.customer_id
Hopefully this is clear, but if not, leave more comments and I'll further explain tonight or tomorrow.

Updating table A with information from table B (which contains duplicate information)

This is a bit tricky. I've got someone with a very specific request.
This database has two tables: Table A (customer table) and table B (order table).
Table A has a unique customer ID (customerid) for each row. It also has a Date of Service (DATEOFS) column, which is primarily populated with old information from the previous database.
Table B has a unique Order number (ordernum) for each order. Each order is also linked to the customer (customerid) and has the date of the order (dofserv).
To show the Date of Service in the actual PHP script, I use the following query:
SELECT dofserv FROM orders WHERE customerid=" . $result['customerid'] . " ORDER BY dofserv DESC LIMIT 1
This returns me the most recent date of service for a single customer.
What I'm attempting to do is update the DATEOFS in Table A with the -most recent- DOFSERV from Table B.
As I understand it, the following query would work if there was only one matching row:
UPDATE clients a, orders b SET a.DATEOFS = b.DOFSERV WHERE a.customerid = b.customerid
However, since the orders table has multiple rows which match the customerid, I'm not sure how to update the clients.DATEOFS with only the most recent orders.DOFSERV.
Any suggestions? Would like to do this in pure MySQL to run from the command line; I could do it in PHP, but I'm not sure how long it would take to run.
One way to do it is like this:
UPDATE clients a
SET
a.DATEOFS=(SELECT MAX(b.DATEOFS) FROM orders b WHERE a.customerid=b.customerid);
But this query will insert NULL in a.DATEOFS if there is no matching order. If that's not what you want, you can modify it slightly to:
UPDATE clients a
JOIN orders o ON a.customer_id=o.customer_id
SET
a.DATEOFS=(SELECT MAX(b.DATEOFS) FROM orders b WHERE a.customerid=b.customerid);
This query will restrict the updates to only those rows in clients where there is at least one matching row in orders.
You could join the clients table on an aggregate query:
UPDATE clients a
JOIN (SELECT customer_id, MAX(dofserv) AS md
FROM orders
GROUP BY customer_id) b ON a.customerid = b.customerid
SET a.dateofs = b.md
This is one way that will only update a minimal number of rows:
update clients a
set dofserv = ( select max(dofserv)
from orders b
where b.customerid = a.customerid
)
where exists ( select *
from orders b
where b.customerid = a.customerid
and b.dofserv > a.dofserve
)

Using multiple inner joins

I have four tables:
users, orders, orders_product and products.
They are connected to each other by foreign key
user tables contains: id, name, email and username.
product table contains: id, product_name, product_description and product_price
orders table contains: id, u_id(foreign key).
orders_product table contains: id, product_id(foreign key), order_id(foreign key).
Now I was trying to fetch the name of a user with the total price of a particular order that he has placed.
The maximum I could went for was something like this:
SELECT prod.order_id,
SUM(product_price) AS Total
FROM products
INNER JOIN
(SELECT orders.id AS order_id,
orders_product.product_id
FROM orders
INNER JOIN orders_product ON orders.id = orders_product.order_id
WHERE order_id=1) AS prod ON products.id = prod.product_id;
It showed me total price of a particular order. Now I have two questions:
Is that query correct. It looks like a very long query. Can the same result be achieved with a smaller one?
How to fetch the name of a user with the total price of a particular order that he has placed.
Hi some addition to #Gordon Linoff
your query seems ok.
if you store your price data in order_products it will be good and some benefit, one of these benefit is aggregation will be simple. Second benefit if product price change it will not affect to order.
Your query is correct for one order, but it can be improved:
Don't use a subquery unless necessary. In MySQL this introduces additional overhead.
You are only looking at one order, which seems on the light site. You should remove the where clause.
You should be using a group by because you want aggregation.
You need to join in the user table to get the name.
I also added table aliases (abbreviations for table names). This makes the query a bit more readable:
SELECT u.name, SUM(p.product_price) as Total
FROM orders_product op INNER JOIN
orders o
ON o.id = op.order_id INNER JOIN
products p
ON p.id = op.product_id INNER JOIN
users u
on o.userid = u.id
WHERE op.order_id = 1
GROUP BY u.name;
Your SQL is wrong. Because You want to calculate specific to user. But your SQL is specific to Order. Your SQL will give result for One Order. Please make it User Specific by giving user name or what ever is unique.

Categories