How to add column totals to view - mysql

I'm constructing a SQL query for a business report (using MySQL). What I would like to do is create a table that looks like the following:
Product | Quantity | Price | Total
widget1 | 3 | 1.00 | 3.00
widget1 | 1 | 1.00 | 1.00
widget1 | 2 | 1.00 | 2.00
widget1 | 3 | 1.00 | 3.00
Total | 9 | 1.00 | 9.00
I can write a query to output everything except the last line of the table. Is this possible? If so how would one implement it?
I have tried some of the answers below with the following query but it doesn't work. I must be missing something fundamental.
SELECT uc_order_products.nid AS nid,
uc_orders.order_id AS 'order_id',
first_name.value AS 'firstname',
last_name.value AS 'lastname',
uc_order_products.title AS 'program',
uc_order_products.qty AS 'quantity',
uc_order_products.price AS 'price',
(uc_order_products.qty * uc_order_products.price) AS 'total',
sum(uc_order_products.qty) AS 'total quantity',
sum(uc_order_products.qty * uc_order_products.price) AS 'total revenue'
FROM profile_values first_name
INNER JOIN profile_values last_name ON first_name.uid = last_name.uid
LEFT JOIN uc_orders uc_orders ON uc_orders.uid = first_name.uid
LEFT JOIN uc_order_products uc_order_products ON uc_orders.order_id = uc_order_products.order_id
WHERE uc_orders.order_status IN ('completed')
AND first_name.fid =5
AND last_name.fid =6
AND COALESCE(:nid,nid) = nid
GROUP BY uc_order_products.nid WITH ROLLUP
I suspect that I can't use group by with rollup within the query that creates reporting table. How would I wrap the query to produce the desired result?
Thanks

I have had a little attempt at this, mainly because i hadn't heard of WITH ROLLUP (thanks biziclop) and I wanted to try it out.
CREATE TABLE test.MyTable(
product TEXT(10),
quantity NUMERIC,
price NUMERIC
);
INSERT INTO MyTable VALUES
("widget1", 3, 1),
("widget1", 1, 1),
("widget1", 2, 1),
("widget1", 3, 1),
;
SELECT
Product,
Quantity,
Price,
Total
FROM
(
SELECT
rownum,
COALESCE(Product, 'Total') AS Product,
Quantity,
Price,
(Quantity * Price) AS Total
FROM
(
SELECT
#rownum:=#rownum+1 rownum,
Product,
SUM(Quantity) AS Quantity,
Price AS Price
FROM
MyTable,
(SELECT #rownum:=0) r
GROUP BY
product, rownum
WITH ROLLUP
)
AS myalias
) AS myalias2
WHERE rownum IS NOT NULL
OR Product = 'Total'
Outputs:
I'm giving up now, but i am looking forward to seeing how a pro does it!

try this:
SELECT
product,
COUNT(product) AS quantity,
SUM(price) price
FROM product
GROUP BY product WITH ROLLUP

Related

grouping records by a field and submit a query on each group in sql

I have a table like this:
create table product_company (
id int PRIMARY KEY,
productName varchar(100),
companyName varchar(100),
price int
);
I want to know the name of the product which it has the second rank in price in each company.
for example if company1 has three product product1=30, product2=50 and product3=15(the assignment shows the price of each product in this company) so product1 has the second rank in price property in company1 and I want to write a query that returns something like below:
company1 product1
company2 ...
...
I mean for every company I want to know the product that has the second rank in price within that company.
I don't know how to use group by clause because group by is working fine by aggregate functions but I don't want the maximum in price.
I want to write this query with standard sql queries and clauses and without some special funcions that may not work in some DBMS
If you are running MySQL 8.0, you can use window function dense_rank():
select *
from (
select
pc.*,
dense_rank() over(partition by companyName order by price desc) rn
from product_company pc
) t
where rn = 2
In earlier versions, one solution is to filter with a correlated subquery. But you have to be careful to properly handle possible top ties. This should do it:
select pc.*
from product_company pc
where (
select count(distinct pc1.price)
from product_company pc1
where pc1.companyName = pc.companyName and pc1.price > pc.price
) = 1
An EXISTS with a COUNT can also be used for this
For example:
create table product_company (
id int PRIMARY KEY AUTO_INCREMENT,
productName varchar(100),
companyName varchar(100),
price decimal(16,2)
);
insert into product_company
(productName, companyName, price) values
('product 1', 'odd org', 9)
,('product 2', 'odd org', 15)
,('product 3', 'odd org', 11)
,('product 4', 'odd org', 17)
,('product 5', 'even inc.', 18)
,('product 6', 'even inc.', 12)
,('product 7', 'even inc.', 16)
,('product 8', 'even inc.', 14)
;
select *
from product_company t
where exists
(
select 1
from product_company t2
where t2.companyName = t.companyName
and t2.price >= t.price
having count(distinct t2.price) = 2
)
id | productName | companyName | price
-: | :---------- | :---------- | ----:
2 | product 2 | odd org | 15.00
7 | product 7 | even inc. | 16.00
db<>fiddle here
And if you want to have the top 2 per company?
Then change the HAVING clause
...
having count(distinct t2.price) <= 2
...

MySQL 8.0 GROUP BY / FULL_GROUP_BY

Since upgrading to MySQL 8 I have a lot of queries that dont comply to the new full-group-by setting of MySQL. Below is a simplified version of one of the queries. I have A LOT of them and before going through all the code, I want to fully understand the problem.
I have the following records in the database:
[prod_id] => 1
[prod_name] => Product 1
[prod_new] => 50.00
[prod_size] => L
[prod_id] => 2
[prod_name] => Product 1
[prod_new] => 45.00
[prod_size] => M
[prod_id] => 3
[prod_name] => Product 1
[prod_new] => 40.00
[prod_size] => S
[prod_id] => 4
[prod_name] => Product 4
[prod_new] => 100.00
[prod_size] => M
[prod_id] => 5
[prod_name] => Product 5
[prod_new] => 200.00
[prod_size] => M
When I ran the following query in MySQL 5.x, I got 3 results. Containing products 1, 4, 5. With the corresponding name, price and size.
SELECT prod_id, prod_name, prod_price, prod_size
FROM prod_product
GROUP BY prod_name
Since the upgrade I get the widely know error about nonaggregated columns. So I want to fix this, but in some cases this will get me unwanted results. Lets say, for some reason, I wanted the highest product-id.
SELECT MAX(prod_id), prod_name, ANY_VALUE(prod_price), ANY_VALUE(prod_size)
FROM prod_product
GROUP BY prod_name
This will give me product-id's 3, 4, 5. But with product-id 3 it gives me the price and size of product-id 1.
Obviously that is unwanted behaviour. I would assume that, since prod_id is the primary-key, the database knows which values to show with the corresponding id. When I say MAX(prod_id) this already pinpoints a single record in this group, why give me values of other records from this group?
I guess I am missing something important here. =)
Thanks!
I would assume that, since prod_id is the primary-key, the database knows which values to show with the corresponding id.
Why would it know that?
Consider the following queries:
SELECT prod_name, prod_id, MIN(prod_price), MAX(prod_price)
FROM prod_product
GROUP BY prod_name
Which value should it return for prod_id here? The product that corresponds to the minimum price? Or the product that corresponds to the maximum price?
Also, what if there are multiple products that tie for the minimum or maximum price? Which one should it return?
SELECT prod_name, prod_id, AVG(prod_price)
FROM prod_product
GROUP BY prod_name
Now which prod_id should it infer? The aggregate calculation AVG() is likely to return a value that doesn't correspond to any single product.
The same happens with the aggregate SUM().
The fact is, there is no implicit correlation between an aggregate function and a specific row in the group. You should not expect SQL to guess which row from the group you mean to reference when you use non-aggregated expressions.
If you want the first record in every group of rows having the same prod_name ordered by prod_id, you can use window function ROW_NUMBER(), which is available in MySQL 8 :
SELECT x.prod_id, x.prod_name, x.prod_new, x.prod_size
FROM (
SELECT
p.prod_id, p.prod_name, p.prod_new, p.prod_size,
ROW_NUMBER() OVER(PARTITION BY p.prod_name ORDER BY p.prod_id) rn
FROM prod_product p
) x WHERE x.rn = 1
The inner query assigns a number to each record within each group, and the outer query filters in the first record in each group.
Demo on DB Fiddle :
WITH prod_product AS (
SELECT 1 prod_id, 'Product 1' prod_name, 50 prod_new, 'L' prod_size
UNION ALL SELECT 2, 'Product 1', 45, 'M'
UNION ALL SELECT 3, 'Product 1', 40, 'S'
UNION ALL SELECT 4, 'Product 4', 100, 'M'
UNION ALL SELECT 5, 'Product 5', 200, 'M'
)
SELECT x.prod_id, x.prod_name, x.prod_new, x.prod_size
FROM (
SELECT
p.prod_id, p.prod_name, p.prod_new, p.prod_size,
ROW_NUMBER() OVER(PARTITION BY p.prod_name ORDER BY p.prod_id) rn
FROM prod_product p
) x WHERE x.rn = 1;
| prod_id | prod_name | prod_new | prod_size |
| ------- | --------- | -------- | --------- |
| 1 | Product 1 | 50 | L |
| 4 | Product 4 | 100 | M |
| 5 | Product 5 | 200 | M |

complex sql query (GROUP BY)

I need some help building a query.
Here is what I need :
I have a table called data:
ID| PRODUCT | VALUE |COUNTRY| DEVICE | SYSTEM
-----+---------+-------+-------+---------+--------
48 | p1 | 0.4 | US | dev1 | system1
47 | p2 | 0.67 | IT | dev2 | system2
46 | p3 | 1.2 | GB | dev3 | system3
45 | p1 | 0.9 | ES | dev4 | system4
44 | p1 | 0.6 | ES | dev4 | system1
I need to show which products have produced the most revenue and which country, device and system contributed the most.
**for example : the result i would get from the table would be:
PRODUCT | TOTAL COST |COUNTRY| DEVICE | SYSTEM
-------+------------+-------+---------+--------
p1 | 1.9 | ES | dev4 | system1
p2 | 0.67 | IT | dev2 | system2
p3 | 1.2 | GB | dev3 | system3
Top country is ES because ES contributed with 0.9 + 0.6 = 1.5 > 0.4 (contribution of US).
same logic for top device and top system.**
I guess for total revenue and product something like this will do :
SELECT SUM(value) as total_revenue,product FROM data GROUP BY product
But how can I add country,device and system?
Is this even feasible in a single query, if not what is the best way (performance wise) to do it?
Many thanks for your help.
EDIT
I edited the sample table to explain better.
Do it in separate queries:
SELECT product,
SUM(value) AS amount
FROM data
GROUP BY country -- change to device, system, etc. as required
ORDER BY amount DESC
LIMIT 1
You are correct... it is not just a simple query... but 3 queries wrapped into one result.
I've posted my sample out on SQL Fiddle here...
First query -- the inner most. You need to get all revenue based on a per product/country and sort that by the product and DESCENDING on the total revenue to have highest revenue in first position per product.
Next query (where I've implemented use of MySQL #variable use). Since the first result order already has it in order of product and revenue rank, I set the rank to 1 every time a product changes from whatever the "#LastProd" is... This would create ES = Rank #1 for product 1, then US = Rank #2 for product 1, then continue on the other "products".
The final outermost query re-joins back to the raw Data table but gets a list of all the devices and systems that comprised the product sale in question, but ONLY where the product rank was #1.
select
pqRank.product,
pqRank.country,
pqRank.revenue,
group_concat( distinct d2.device ) as PartDevices,
group_concat( distinct d2.system ) as PartSystems
from
( select
pq.product,
pq.country,
pq.revenue,
#RevenueRank := if( #LastProd = pq.product, #RevenueRank +1, 1 ) as ProdRank,
#LastProd := pq.product
from
( select
d.product,
d.country,
sum( d.value ) as Revenue
from
data d
group by
d.product,
d.country
order by
d.product,
Revenue desc ) pq,
( select #RevenueRank := 0,
#LastProd := ' ') as sqlvars
) pqRank
JOIN data d2
on pqRank.product = d2.product
and pqRank.country = d2.country
where
pqRank.ProdRank = 1
group by
pqRank.product,
pqRank.country
You could do sth like that
CREATE TABLE data
(
id int auto_increment primary key,
product varchar(20),
country varchar(4),
device varchar(20),
system varchar(20),
value decimal(5,2)
);
INSERT INTO data (product, country, device, system, value)
VALUES
('p1', 'US', 'dev1', 'system1', 0.4),
('p2', 'IT', 'dev2', 'system2', 0.67),
('p1', 'IT', 'dev1', 'system2', 0.23);
select 'p' as grouping_type, product, sum(value) as sumval
from data
group by product
union all
select 'c' as grouping_type, country, sum(value) as sumval
from data
group by country
union all
select 'd' as grouping_type, device, sum(value) as sumval
from data
group by device
union all
select 's' as grouping_type, system, sum(value) as sumval
from data
group by system
order by grouping_type, sumval
It's ugly, I wouldn't use it, but it should work.

How to find if a list/set is contained within another list

I have a list of product IDs and I want to find out which orders contain all those products. Orders table is structured like this:
order_id | product_id
----------------------
1 | 222
1 | 555
2 | 333
Obviously I can do it with some looping in PHP but I was wondering if there is an elegant way to do it purely in mysql.
My ideal fantasy query would be something like:
SELECT order_id
FROM orders
WHERE (222,555) IN GROUP_CONCAT(product_id)
GROUP BY order_id
Is there any hope or should I go read Tolkien? :) Also, out of curiosity, if not possible in mysql, is there any other database that has this functionality?
You were close
SELECT order_id
FROM orders
WHERE product_id in (222,555)
GROUP BY order_id
HAVING COUNT(DISTINCT product_id) = 2
Regarding your "out of curiosity" question in relational algebra this is achieved simply with division. AFAIK no RDBMS has implemented any extension that makes this as simple in SQL.
I have a preference for doing set comparisons only in the having clause:
select order_id
from orders
group by order_id
having sum(case when product_id = 222 then 1 else 0 end) > 0 and
sum(case when product_id = 555 then 1 else 0 end) > 0
What this is saying is: get me all orders where the order has at least one product 222 and at least one product 555.
I prefer this for two reasons. The first is generalizability. You can arrange more complicated conditions, such as 222 or 555 (just by changing the "and" to and "or"). Or, 333 and 555 or 222 without 555.
Second, when you create the query, you only have to put the condition in one place, in the having clause.
Assuming your database is properly normalized, i.e. there's no duplicate Product on a given Order
Mysqlism:
select order_id
from orders
group by order_id
having sum(product_id in (222,555)) = 2
Standard SQL:
select order_id
from orders
group by order_id
having sum(case when product_id in (222,555) then 1 end) = 2
If it has duplicates:
CREATE TABLE tbl
(`order_id` int, `product_id` int)
;
INSERT INTO tbl
(`order_id`, `product_id`)
VALUES
(1, 222),
(1, 555),
(2, 333),
(1, 555)
;
Do this then:
select order_id
from tbl
group by order_id
having count(distinct case when product_id in (222,555) then product_id end) = 2
Live test: http://www.sqlfiddle.com/#!2/fa1ad/5
CREATE TABLE orders
( order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
);
INSERT INTO orders(order_id,product_id) VALUES
(1, 222 ) , (1, 555 ) , (2, 333 )
, (3, 222 ) , (3, 555 ) , (3, 333 ); -- order#3 has all the products
CREATE TABLE products AS (SELECT DISTINCT product_id FROM orders);
SELECT *
FROM orders o1
--
-- There should not exist a product
-- that is not part of our order.
--
WHERE NOT EXISTS (
SELECT *
FROM products pr
WHERE 1=1
-- extra clause: only want producs from a literal list
AND pr.product_id IN (222,555,333)
-- ... that is not part of our order...
AND NOT EXISTS ( SELECT *
FROM orders o2
WHERE o2.product_id = pr.product_id
AND o2.order_id = o1.order_id
)
);
Result:
order_id | product_id
----------+------------
3 | 222
3 | 555
3 | 333
(3 rows)

Correlated subquery mysql

so i have a table with products
Product ID | Product Name
===========+===============
1 | Tissues
2 | Glass
I have a table with sales
Sale ID | Product ID | Quantity | Price
===========+============+==========+=============
1 | 1 | 1 | 55
2 | 2 | 1 | 60
and i have a table of purchases
Batch ID | Total Value | Quantity | Product ID
=========+=============+==========+==================
1 | 100 | 100 | 1
2 | 10 | 50 | 2
3 | 1 | 1 | 2
So im trying to calculate the profit based on average cost using the query
SELECT tblsale.product_id,
tblproduct.product_name,
SUM(tblsale.`quantity`) qty,
SUM(tblsale.`Price`*tblsale.`quantity`) sales,
(SELECT sum(total_value) / sum(quantity) VWAP
FROM tblpurchases
WHERE product_id = tblsale.product_id) average_price,
(average_price * qty) cost,
(sales-cost) profit
FROM tblsale, tblproduct
WHERE tblproduct.product_id = tblsale.`product_id`
GROUP by tblsale.`product_id`
But i can't seem to get it to work i get a 'average price' is an unknown column, how would I structure the query correctly
SQL doesn't support referencing a column alias in the same SELECT clause - that's why your average_price column is returning the 1054 error. You either have to do whatever operation you need in a subselect, derived table/inline view, or reuse the underlying logic where necessary. Here's an example of the reuse of logic:
SELECT prod.product_id,
prod.product_name,
SUM(s.quantity) qty,
SUM(s.Price * s.quantity) sales,
SUM(pur.total_value) / SUM(pur.quantity) average_price,
SUM(pur.total_value) / SUM(pur.quantity) * SUM(s.quantity) cost,
SUM(s.Price * s.quantity) - (SUM(pur.total_value) / SUM(pur.quantity) * SUM(s.quantity)) profit
FROM tblproduct prod
LEFT JOIN tblsale s ON prod.product_id = s.product_id
LEFT JOIN tblpurchases pur ON pur.product_id = prod.product_id
GROUP BY s.product_id
My query is using ANSI-92 JOIN syntax, which I recommend over the ANSI-89 syntax your query uses. See this question for more details.
How did you get to this query? It is completely off.. When writing a query, start small and then build it up. The query you have now is a complete mess and nowhere near to valid, there are random parenthesis' through out it.
To make a start, use indentation to make your query readable
SELECT p.product_id, p.product_name
, SUM(s.quantity) number_of_sales
, SUM(s.price) total_profit
, SUM(pu.quantity) purchase_quantity
, SUM(pu.value) purchase_value
, (SUM(pu.quantity) - SUM(s.quantity)) number_in_stock
, (SUM(s.price) - SUM(pu.value)) profit
, (SUM(pu.value) / SUM(pu.quantity)) avarage_purchase_price
FROM product p
LEFT JOIN sales s ON s.product_id = p.product_id
LEFT JOIN purchase pu ON pu.product_id = p.product_id
GROUP BY s.product_id, pu.product_id
"But i can't seem to get it to work i get a 'average price' is an unknown column, how would I structure the query correctly"
What is 'average price'? How would you like average price to be calculated? And the same for 'average cost'