MySQL JOIN and group by so there is one ID per row - mysql

Two tables: products, all IDs are unique, and stock, where there can be same ID several times. I need to compare the quantities where the quantity in product table doesn't match the total quantity in the stock table.
products
ID quantity
1 4
2 6
3 2
stock:
ID quantity
1 1
1 3
2 5
3 2
How can I get a result where there is a single ID per row? Expected result:
ID quantity as products quantity as stock
2 6 5

You can have a subquery that computes the total stocks, and LEFT JOIN it to your products table:
SELECT
products.ID,
products.quantity AS `quantity as products`,
total_stock.quantity AS `quantity as stock`
FROM
products
LEFT JOIN
-- We compute total quantities from `stock`
(SELECT
stock.ID, sum(stock.quantity) AS quantity
FROM
stock
GROUP BY
stock.ID
) AS total_stock
ON total_stock.ID = products.ID
WHERE
-- We want to find only discrepancies.
-- We use NOT <=> to safely check nulls.
NOT (total_stock.quantity <=> products.quantity)
ORDER BY
products.ID ;
I've assumed this schema (with a REFEFENCES constraint):
CREATE TABLE products
(
ID INTEGER PRIMARY KEY,
quantity INTEGER NOT NULL
) ;
CREATE TABLE stock
(
ID INTEGER NOT NULL REFERENCES products(ID),
quantity INTEGER NOT NULL
) ;
You can find your example data (together with a few extra data to care for nulls) and this solution at dbfiddle here
You can also change the WHERE clause to:
-- We use coalesce to convert nulls to 0 (we assume *don't know* means *don't have*)
coalesce(total_stock.quantity,0) <> coalesce(products.quantity, 0)
depending on your use-case.
dbfiddle here
References:
MySQL NULL-safe equal
MySQL COALESCE

Related

How to add sorting position to MySQL records?

I have a MySQL 8 InnoDB Table that stores prices from price comparison sites for specific products of a particular day. Usually prices are sorted ASC so I thought it is not neccessary to save the SERP (position) to the record.
The functionality to save the position has been added now and I am looking for a way to add this information for the old records. It can be identified by sorting after price.
Shema:
create table shops_prices
(
DATE date not null,
SKU char(10) not null,
MERCHANT_ID tinyint unsigned not null,
SHOP_ID mediumint unsigned not null,
PRICE decimal(10, 2) null,
SERP tinyint unsigned null,
primary key (DATE, SKU, MERCHANT_ID, SHOP_ID)
)
comment 'Prices from shops within price comparison merchants like e.g. Idealo
';
Get results for one product for one day:
SELECT *
FROM tbl
WHERE date = currdate() and SKU = 123
ORDER BY price
Desired result example:
date SKU price SERP
2021-09-02 123 12.99 1
2021-09-02 123 13.99 2
2021-09-02 123 14.99 3
2021-09-03 123 12.99 1
2021-09-03 123 13.99 2
2021-09-03 124 18.99 1
How could I update the existing records to add the position to all datasets? (1,2,3,..)
ROW_NUMBER() is used for given a serial based on sku and date where smaller price comes first. If WHERE condition needed for particular searching then enable it otherwise disable WHERE clause. use shop_id and merchant_id at partition column along with other two if needed. Use CTE for ranking the position then Update with main table.
-- MySQL (v5.8)
WITH t_cte AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY sku, date ORDER BY price) row_num
FROM shops_prices
WHERE sku = 123 AND date <= '2021-09-05'
)
UPDATE shops_prices s
INNER JOIN t_cte t
ON s.Date = t.Date
AND s.SKU = t.SKU
AND s.MERCHANT_ID = t.MERCHANT_ID
AND s.SHOP_ID = t.SHOP_ID
AND s.price = t.price
SET s.SERP = t.row_num
Please check from url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=51c3eecd19a5b6367722c6905235a5ed
Prior to mysql 8, you would use variables for this:
set #last_date=date(0);
set #last_sku=''; # may need to set collation for this string
set #last_serp=0;
update shops_prices
set serp=#last_serp:=if(
#last_date=date && #last_sku=sku,
#last_serp+1,
if((#last_date:=date) && (#last_sku:=sku),1,1)
)
order by date,sku;

Constraints on group by

I have a products table that contains all the products that can be sold by a seller. It has two columns as primary key; product_code and amount(product weight basically).
As product weight varies their price varies. 6 types of product weight are available: 0.5, 1, 1.5, 2, 2.5, 3. Product code for a product having different weights would be the same.
Sample data:
product_code | product_quantity | amount | quantity
1234 | 6 | 0.5kg | 0
1234 | 6 | 1.0kg | 7
1234 | 6 | 1.5kg | 8
I want to display all products, but only once per product_code.
SELECT * FROM products where group by product_code
Given the above three rows, this query gives me the first row of the three.
However, I want to return rows with quantity greater than zero if they exist, otherwise any row will do.
What modification do I need to make in my query to achieve this?
Here's my table structure:
If you want one row per product_code with the highest quantity you can use a WHERE condition with a correlated subquery, that is sorted by quantity and limited to one row.
SELECT p1.*
FROM products p1
WHERE (p1.product_code, p1.amount) = (
SELECT p2.product_code, p2.amount
FROM products p2
WHERE p2.product_code = p1.product_code
ORDER BY p2.quantity DESC
LIMIT 1
)
You need to use the primary key for the condition. But because we have WHERE p2.product_code = p1.product_code in the subquery, we can also rewrite it to:
SELECT p1.*
FROM products p1
WHERE p1.amount = (
SELECT p2.amount
FROM products p2
WHERE p2.product_code = p1.product_code
ORDER BY p2.quantity DESC
LIMIT 1
)
Now MySQL should be able to cache the subquery result for each product_code. With an index on (product_code, quantity, amount) or just (product_code, quantity) the subquery should be executed pretty fast.
To keep the result deterministic you can extend the ORDER BY clause of the subquery. If two rows with the same product_code have the same quantity and you want to pick the one with the greater amount (which cann't be equal because it's part of the primary key) then use
ORDER BY p2.quantity DESC, p2.amount DESC
Bearing in mind that you're using a non-standard feature, ie grouping by a subset of the non-aggregated columns, in which case (not guaranteed) the first row is returned for each group...
Do the group by over an ordered rowset that encounters non-zero quantities before zero quantities:
SELECT * FROM (
SELECT * FROM products
ORDER BY quantity desc
) x
GROUP BY product_code
Note: The implementation you're using behaves this way, but no guarantees are given that future versions will continue to work reliably the way you want them to.
Also, as per the documentation:
As of MySQL 5.7.5, the default SQL mode includes ONLY_FULL_GROUP_BY.
This means that your query won't run on 5.7.5+ unless you actively disable ONLY_FULL_GROUP_BY after installing.
The reliable, and portable, way is to use a subquery that finds all the desired quantities and joins to that. But you have the added complication that the quantity is not unique to a product, so you have to break the tie using amount:
SELECT p.*
FROM products p
JOIN (SELECT p2.product_code, max_quantity, max(amount) max_amount
FROM products p2
JOIN (SELECT product_code, MAX(quantity) max_quantity
FROM products
GROUP BY product_code) q
ON q.product_code = p2.product_code
AND max_quantity = p2.quantity
GROUP BY product_code, max_quantity) qa
ON p.product_code = qa.product_code
AND quantity = max_quantity
AND amount = max_amount
The first subquery finds the max quantity (which is the data you want), then the next subquery finds the max amount of all rows with that max quantity (there could be more than one row sharing the max amount). Since the combination of product code, amount and quantity is unique, joining back to the table using these values with give you the rows you want.
Disclaimer: Code may not compile or work as it was thumbed in on my phone (but there's a reasonable chance it will work)

MYSQL reference to another table fields

I am trying to get for a specific period of time the quantity of products sold. For this example : the products table has a quantity field which increments whenever a certain product is sold. The orders table has a start date and end date. Is there a way i can hold a reference of the products table (like an object) in my orders table so i can see the quantity sold for each product every start - end date ? A quick example : I am having 2 products:
1 bike 25000 3 and 2 sportbike 30000 5 sold for my date. So an order would be something like : 1 05.07.2015 05.07.2015 and those products.
CREATE TABLE products (
product_no integer PRIMARY KEY,
name varchar(20),
price float,
quantity integer,
);
CREATE TABLE sells (
sell_id integer PRIMARY KEY,
start_date date,
end_date date
);
//New idea :
CREATE TABLE products (
product_no integer PRIMARY KEY,
name varchar(20),
price float
);
CREATE TABLE sells (
sell_id integer PRIMARY KEY,
date date,
quantity integer,
product_id integer FOREIGN KEY
);
Unless an order is always for a single product, I think you will need a third table. To me a minimal *) data model for this situation would look like this:
CREATE TABLE products (
product_no integer PRIMARY KEY,
name varchar(20) not null,
price float,
quantity_on_stock integer not null
);
CREATE TABLE orders (
order_id integer PRIMARY KEY,
order_date date not null
);
CREATE TABLE orderlines (
order_id integer not null REFERENCES orders.order_id,
product_no integer REFERENCES products.product_no,
price integer,
quantity integer not null,
PRIMARY KEY(order_id, product_no)
);
Then, a query to get sales in a certain period could look like this:
select
p.product_no,
p.name,
sum(ol.quantity)
from
products p
inner join orderlines ol on ol.product_no = p.product_no
inner join orderlines o on o.order_id = ol.order_id
where
ol.order_date between :start_date and :end_date
*) I say minimal, but it's actually less than minimal. You'd probably want to store invoices too, or at least some kind of indication that says whether an order is actually payed for and delivered, since sometimes orders are cancelled, or are just waiting open.
You definitely want a sales table showing what was sold and when, rather than a table showing the running inventory.
It is much easier in SQL to aggregate detail to create running totals than it is to reconstruct detail from running totals.
So, if you happen to have a sales table containing, among other things, columns for product_id, quantity, and ts (the timestamp of the sale), then you can get a summary of sales by product_id and sales date like this.
SELECT product_id,
DATE(ts) AS salesdate,
SUM(quantity) AS quantity
FROM sales
WHERE ts >= :start_date
AND ts < :end_date + INTERVAL 1 DAY
GROUP BY DATE(ts), product_id
One of the things that's cool about this is you can add a column for transaction_type to this table. If a customer returns a product you tag it return and use a negative quantity. If your shop receives a shipment of products you tag it restock and use a negative quantity. Then, if you want to show net sales -- sales less returned products -- it's a small change to the query.
SELECT product_id,
DATE(ts) AS salesdate,
SUM(quantity) AS quantity
FROM sales
WHERE ts >= :start_date
AND ts < :end_date + INTERVAL 1 DAY
AND transaction_type IN ('sale', 'return')
GROUP BY DATE(ts), product_id
If you want to know how much stock you had on on hand for each product for a particular end_date, you do this (starting from the beginning of time).
SELECT product_id,
SUM(quantity) AS quantity
FROM sales
GROUP BY product_id
In actual inventory / sale systems, it's common to take inventory once a year, close out the previous year's sales table, and start a new one with a starting value for each product. it's also common to organize sales by sales_order and sales_detail, so you can track the sale of multiple items in each transaction.

Mysql join where value or null

I've got a table PROD
ID NAME
1 Apple
2 Banana
And the relative table PRICES , with global prices (ID_USER is Null)
or per-user prices (ID_USER)
PROD_ID USER_ID PRICE
1 null 10
1 5 8
Now, i need a query that finds all products and the relative prices,
the catch is that i'm trying to retrieve the user price only if there is one, else retrieve the global price
SELECT PROD.* , PRICES.* FROM PROD
LEFT JOIN PRICES ON PROD.ID_PROD = PRICES.ID_PROD
WHERE PRICES.USER_ID IS NULL OR PRICES.USER_ID = 5
This query returns 2 rows (the prod joined with the 2 prices)
Is there a way to retrieve the exact price for the product in just one query ?
thanks !!
EDIT: In the join i need the per-user price row (the last one) only if the row exists , else i need to retrieve the row with the global price, is that possible ?
SELECT
prod.*,
COALESCE(p_user, p_default) As Price
FROM
Prod INNER JOIN (SELECT
PROD_ID,
MAX(CASE WHEN USER_ID=5 THEN Price END) p_user,
MAX(CASE WHEN USER_ID IS NULL THEN Price END) p_default
FROM
Prices
GROUP
BY PROD_ID) m_uid
ON Prod.ID = m_uid.Prod_ID
Please see fiddle here.

Different Items in single row to get the total amount of that order

I Have an order table in which i insert all the items that my customer placed in his order in single row.like
Table_ORDER ( Od_id is primary key auto incrementd)
Od_Id Cust_Name Quantity of 101(int) Quantity of 102 Quantity of 103
----- ------- -------------------- --------------- --------------
1 John 5 4 7
2 Kim 4 3 2
Another Table of Price is like
Table_ Price
Prod_ID Price (money)
------- ------
101 5.2
102 2.5
103 3.5
Now I want to get the total amount of specific order placed by customer. Problem is that if i use differnt rows for different item that Order id will be changed and if i use single row then How I calculate the total price as i can put jst single Prod_ID colum.
Kindly Guide me and send some solutions
Regards
I do see that the table design violates most of the design values starting with no foreign key between tables.
But the worst case solution for your problem is here:
select ( q101*price101.price+q102*price102.price) as 'Total Price' from
(select p.id, q101, price from `order`, price p where p.id=101) as price101,
(select p.id, q102, price from `order`, price p where p.id=102) as price102,
(select p.id, q103, price from `order`, price p where p.id=103) as price103
I am just trying to build tables to connect the two of your tables and then query based on that.
But it gets tedious as the number of products grow. I would really suggest to think of a design alternative.
NOTE: I have chosen column names like this: Quantity of 101 = q101
"Problem is that if i use differnt rows for different item that Order id will be changed". This is easily fixed if you change the design of your database by moving the ordered products and quantity to a separate table related to the main order table by a foreign key.
Here's a very simple example:
Three tables, defined as below:
Table Orders
------------
OrderID (Identity column)
CustomerName
Table OrderDetails
------------------
OrderID (this is the foreign key from the table Order)
ProductID (this is the foreign key from the Products table)
Quantity
Table Products
--------------
ProductID
Price
Now you can get the total for a given order by doing a query like this:
SELECT SUM(ISNULL(od.Quantity, 0) * p.Price)
FROM Orders o
JOIN OrderDetail od
ON o.OrderID = od.OrderID
JOIN Products p
ON od.ProductID = p.ProductID
WHERE OrderID = 1
For customer 1 (John), the result is 60.50. For customer 2 (Kim), the result is 35.30 (based on the data in your question).
This design has the benefit of allowing you to add products to your Products table without having to change the schema (columns) of your Orders table.
Again, this is a very simple example just to illustrate the concept.