I have 3 table. manufacturers, products and prices
I want to get the last price of product and select min price of them.
Table manufacturers:
# manufacturers
id name
1 Manufacturer 1
2 Manufacturer 2
Table products:
# products
id name
1 Product 1
2 Product 2
Table prices:
# prices
id price manufacturerId createdAt
1 10 1 '2019-09-09 00:00:00'
2 20 1 '2019-09-10 00:00:00'
3 11 2 '2019-09-09 00:00:00'
4 21 2 '2019-09-10 00:00:00'
Full code:
DROP DATABASE if exists ssg ;
CREATE DATABASE ssg;
USE ssg;
# Create database manufacturers
CREATE TABLE manufacturers (id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(256) NOT NULL);
# Insert value
INSERT INTO manufacturers (name) VALUES ('Manufacturer 1');
INSERT INTO manufacturers (name) VALUES ('Manufacturer 2');
# Create database products
CREATE TABLE products (id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(256) NOT NULL);
# Insert value
INSERT INTO products (name) VALUES ('Product 1');
# Create database prices
CREATE TABLE prices (id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
productId INT(11) UNSIGNED NOT NULL,
price BIGINT UNSIGNED NOT NULL,
manufacturerId INT(11) UNSIGNED NOT NULL,
createdAt DATETIME NOT NULL);
# Insert value
INSERT INTO prices (productId, price, manufacturerId, createdAt) VALUES (1, 10, 1, '2019-09-09 00:00:00');
INSERT INTO prices (productId, price, manufacturerId, createdAt) VALUES (1, 20, 1, '2019-09-10 00:00:00');
INSERT INTO prices (productId, price, manufacturerId, createdAt)VALUES (1, 11, 2, '2019-09-09 00:00:00');
INSERT INTO prices (productId, price, manufacturerId, createdAt)VALUES (1, 21, 2, '2019-09-10 00:00:00');
# Query
SELECT products.id, products.name, lastValue.price as latestPrice, lastValue.manufacturerId
FROM products
LEFT JOIN(
SELECT productId, COUNT(DISTINCT manufacturerId) AS total
FROM prices
GROUP BY prices.productId) counts ON counts.productId = products.id
LEFT JOIN (
SELECT prices.*
FROM (
SELECT productId, MAX(createdAt) createdAt
FROM prices
GROUP BY productId) latest
JOIN prices ON latest.productId = prices.productId
AND prices.createdAt = latest.createdAt
) lastValue
ON lastValue.productId = products.id
and I got:
id name latestPrice manufacturerId
1 Product 1 20 1
1 Product 1 21 2
So how can I receive products with only with the MIN of latestPrice.
I have to post it in http://sqlfiddle.com/#!9/418cb7/1 . Please "Build Schema" then "Run SQL"
Sorry for my bad english.
In MySQL 8.0, you can do this with window functions only:
select id, name, price, manufacturerId
from (
select
t.*,
rank() over(order by price) rn2
from (
select
p.id,
p.name,
i.price,
i.manufacturerId,
rank() over(partition by p.id order by i.createdAt desc) rn1
from products p
inner join prices i on i.productId = p.id
) t
where rn1 = 1
) t
where rn2 = 1
This phrases as:
first rank the prices of each product by descending date, and filter on the latest price per product
then rank the all the latest prices by ascending price, and filter on the lowest of them
Demo on DB Fiddle:
id | name | price | manufacturerId
-: | :-------- | ----: | -------------:
1 | Product 1 | 20 | 1
Related
I have three tables:
Table Clothes
id
clothname
supplier
instock
1
Shirt
Venom
Yes
2
Tshirt
Traders
No
Table Toys
id
toyname
instock
1
Car
Yes
2
Ball
Yes
2
Yoyo
N/A
Table Tools
id
toolname
instock
1
Drill
Yes
2
Hammer
No
I would like single SQL query to count percentage of all products from 3 tables, where Instock = Yes
From table Clothes there are two conditions, instock = Yes and supplier = Venom.
Desired output would be:
Percentage of products in stock: XY% percent
| Status | Percentage from total Products |
+----------+---------------------------------+
| Instock | 57.14 |
What would be the SQL query for this?
Your data
CREATE TABLE Clothes (
id INTEGER NOT NULL,
clothname VARCHAR(70) NOT NULL,
supplier VARCHAR(80) NOT NULL,
instock VARCHAR(30) NOT NULL
);
INSERT INTO Clothes(id, clothname, supplier, instock)
VALUES
(1, 'Shirt', 'Venom', 'Yes'),
(2, 'Tshirt', 'Traders', 'No');
CREATE TABLE Toys (
id INTEGER NOT NULL,
toyname VARCHAR(50) NOT NULL,
instock VARCHAR(30) NOT NULL
);
INSERT INTO Toys(id, toyname, instock)
VALUES
(1, 'Car', 'Yes'),
(2, 'Ball', 'Yes'),
(2, 'Yoyo', 'N/A');
CREATE TABLE Tools (
id INTEGER NOT NULL,
toyname VARCHAR(50) NOT NULL,
instock VARCHAR(30) NOT NULL
);
INSERT INTO Tools(id, toyname, instock)
VALUES
(1, 'Drill', 'Yes'),
(2, 'Hammer', 'No');
Use union all and cte as follows
WITH T AS
(
SELECT id,
clothname,
instock
FROM Clothes
WHERE supplier='Venom'
UNION ALL
SELECT *
FROM Toys
UNION ALL
SELECT *
FROM Tools)
SELECT Instock AS status,
Cast(Count(*) AS FLOAT) / (SELECT Count(*)
FROM T) AS 'Percentage from total Products'
FROM T
WHERE Instock = 'Yes'
GROUP BY Instock
desired output
status
Percentage from total Products
Yes
0.666666666666667
I have two tables as transactions and listings
Table T as fields of
order_date timestamp
order_id BIGINT
listing_id INT
price INT
Table L with fields of
listing_id INT
price INT
category varchar
If i want to get the sell ratio for each category if sell ratio is defined as the number of sold listings divided by the total number of listings * 100, how can I compose this? would a case statement or cte work better?
listings table is for all listings available and transactions represents all sold
Thanks
Is this what you want?
select
l.category,
count(*) no_listing_transactions
100.0 * count(*) / sum(count(*)) over() per100
from t
inner join l on l.listing_id = t.listing_id
group by l.category
This gives you the count of transactions per category, and the percent that this count represents over the total number of transactions.
Note that this makes uses of window functions, which require MySQL 8.0. In earlier versions, one solution would be to would use a correlated subquery (assuming that there are no "orphan" transactions):
select
l.category,
count(*) no_listing_transactions
100.0 * count(*) / (select count(*) from t) per100
from t
inner join l on l.listing_id = t.listing_id
group by l.category
Try this one
Schema (MySQL v5.7)
Query #1
Create Table `gilbertdim_333952_L` (
listing_id int NOT NULL AUTO_INCREMENT,
price float,
category varchar(10),
PRIMARY KEY (listing_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
There are no results to be displayed.
Query #2
INSERT INTO gilbertdim_333952_L (price, category) VALUES
(100, 'FOOD'),
(50, 'DRINKS');
There are no results to be displayed.
Query #3
Create Table `gilbertdim_333952_T` (
order_id int NOT NULL AUTO_INCREMENT,
order_date timestamp NULL DEFAULT CURRENT_TIMESTAMP,
listing_id int,
price float,
PRIMARY KEY (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
There are no results to be displayed.
Query #4
INSERT INTO gilbertdim_333952_T (listing_id, price) VALUES
(1, 100),(1, 100),(1, 100),
(2, 50),(2, 50);
There are no results to be displayed.
Query #5
SELECT l.*, (COUNT(1) / (SELECT COUNT(1) FROM gilbertdim_333952_T) * 100) as sales
FROM gilbertdim_333952_L l
LEFT JOIN gilbertdim_333952_T t ON l.listing_id = t.listing_id
GROUP BY l.listing_id;
| listing_id | price | category | sales |
| ---------- | ----- | -------- | ----- |
| 1 | 100 | FOOD | 60 |
| 2 | 50 | DRINKS | 40 |
View on DB Fiddle
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
...
I have the following data set:
CREATE TABLE division (
id INT AUTO_INCREMENT PRIMARY KEY,
division VARCHAR(30) NOT NULL
) ENGINE=INNODB;
INSERT INTO division (division) VALUES ("Division1"), ("Division2"), ("Division3"), ("Division4");
CREATE TABLE product (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product VARCHAR(30) NOT NULL,
divisionID INT
) ENGINE=INNODB;
INSERT INTO product (product, divisionID) VALUES ("Product1", 3), ("Product1", 1), ("Product2", 2), ("Product3", 4);
I have the following query:
SELECT Concat(product,' ',division) as 'product'
FROM products p
LEFT JOIN division d ON d.id = p.divisionID
ORDER BY product;
Above query pulls the following records:
Product
------------------
Product1 DIvision1
Product1 Division3
Product2 Division2
Product3 Division4
Q: How can I modify the query so that only the duplicate Products would be concatenated with their Division, and unique products would be left as is, like below example?
Product
------------------
Product1 Division1
Product1 Division3
Product2
Product3
Here's a very literal solution...
SELECT CONCAT_WS(' ',p.product,CASE WHEN total > 1 THEN d.division ELSE NULL END) name
FROM product p
LEFT
JOIN division d
ON d.id = p.divisionid
JOIN
( SELECT product
, COUNT(*) total
FROM product
GROUP
BY product
) x
ON x.product = p.product
ORDER
BY p.product,d.division;
im having difficulty with the following fairly simple setup:
CREATE TABLE IF NOT EXISTS invoices (
id int(11) NOT NULL auto_increment,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS invoices_items (
id int(11) NOT NULL auto_increment,
invoice_id int(11) NOT NULL,
description text NOT NULL,
amount decimal(10,2) NOT NULL default '0.00',
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS invoices_payments (
id int(11) NOT NULL auto_increment,
invoice_id int(11) NOT NULL,
amount decimal(10,2) NOT NULL default '0.00',
PRIMARY KEY (id)
);
some data:
INSERT INTO invoices (id) VALUES (1);
INSERT INTO invoices_items (id, invoice_id, description, amount) VALUES
(1, 1, 'Item 1', '750.00'),
(2, 1, 'Item 2', '750.00'),
(3, 1, 'Item 3', '50.00'),
(4, 1, 'Item 4', '150.00');
INSERT INTO invoices_payments (id, invoice_id, amount) VALUES
(1, 1, '50.00'),
(2, 1, '1650.00');
and the sql yielding unusual results:
select invoices.id,
ifnull(sum(invoices_payments.amount),0) as payments_total,
ifnull(count(invoices_items.id),0) as item_count
from invoices
left join invoices_items on invoices_items.invoice_id=invoices.id
left join invoices_payments on invoices_payments.invoice_id=invoices.id
group by invoices.id
results in the (erroneous) output
id payments_total item_count
1 6800.00 8
now, as evidenced by there being infact only four 'invoice_item' rows, i dont understand why mysql is not grouping properly.
EDIT
i know i can do something like this:
select x.*, ifnull(sum(invoices_payments.amount),0) as payments_total from (
select invoices.id,
ifnull(count(invoices_items.id),0) as item_count
from invoices
left join invoices_items on invoices_items.invoice_id=invoices.id
group by invoices.id
) as x left join invoices_payments on invoices_payments.invoice_id=x.id
group by x.id
but i want to know if im doing something wrong in the first query - i cant immediately see why the first query is giving incorrect results! :(
Your join logic is incorrect. In your join, you specify invoices_items.invoice_id = invoices.id. You also specify invoices_payments.invoice_id = invoices.id. Because of transitivity, you end up with:
invoices_items.invoice_id = invoices.id
invoices_payments.invoice_id = invoices.id
invoice_items.invoice_id = invoices_payments.invoice_id
The sum of the 2 invoice payments is $1700. For every invoice payment, there are 4 invoice_items that satisfy the above relations. $1700 * 4 = $6800.
For every invoice item, there will be two invoice payments that satisfy the above relations. 4 invoice items * 2 = 8 count.
There are two tables with a many:one relationship with invoices. Your count is the cartesian product.
The payments should be applied to the invoice, not the invoice items. Get the invoice total first, then join the payments to it.
This may be similar to what you are looking for:
SELECT
invoice_total.invoice_id,
invoice_total.amount as invoice_amount,
payments_total.amount as total_paid
FROM
(
SELECT
invoice_id,
SUM(amount) as amount
FROM
invoices_items
GROUP BY
invoice_id
) invoice_total
INNER JOIN
(
SELECT
invoice_id,
SUM(amount) as amount
FROM
invoices_payments
GROUP BY
invoice_id
) payments_total
ON invoice_total.invoice_id = payments_total.invoice_id;
edit:
ah, sorry - see your point now. The reason you're getting unexpected results is that this query:
SELECT *
FROM invoices
LEFT JOIN invoices_items ON invoices_items.invoice_id = invoices.id
LEFT JOIN invoices_payments ON invoices_payments.invoice_id = invoices.id;
results in this:
id id invoice_id description amount id invoice_id amount
1 1 1 Item 1 750.00 1 1 50.00
1 1 1 Item 1 750.00 2 1 1650.00
1 2 1 Item 2 750.00 1 1 50.00
1 2 1 Item 2 750.00 2 1 1650.00
1 3 1 Item 3 50.00 1 1 50.00
1 3 1 Item 3 50.00 2 1 1650.00
1 4 1 Item 4 150.00 1 1 50.00
1 4 1 Item 4 150.00 2 1 1650.00
As you can see you get every invoices_items record once each for every invoices_payments record. You're going to have to grab (i.e. group) them separately.
Note that the GROUP BY clause in your initial query is redundant.
Here's what you need:
SELECT
invoices.id,
payments_total.payments_total,
IFNULL(COUNT(invoices_items.id),0) AS item_count
FROM invoices
LEFT JOIN invoices_items ON invoices.id = invoices_items.invoice_id
LEFT JOIN (
SELECT invoice_id,
IFNULL(SUM(invoices_payments.amount),0) AS payments_total
FROM invoices_payments
GROUP BY invoice_id
) AS payments_total ON invoices.id = payments_total.invoice_id
;