MySql: Concatenate 2 columns if condition is met - mysql

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;

Related

sql query with grouped by column

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

Using MIN value after JOIN MAX(date) MYSQL

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

How to get multiple columns on subquery or group by

I have two tables on MySql, the first contains an ID and the name of some products. I have to get the cheapest combination of brand/market for each product. So, I've inserted some itens into both tables:
UPDATE: Inserted new product (bed) with no 'Product_Brand_Market' to test LEFT JOIN.
UPDATE: Changed some product prices for better testing.
CREATE TABLE Product(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL);
CREATE TABLE Product_Brand_Market(
product INT UNSIGNED,
market INT UNSIGNED, /*this will be a FOREIGN KEY*/
brand INT UNSIGNED, /*this will be a FOREIGN KEY*/
price DECIMAL(10,2) UNSIGNED NOT NULL,
PRIMARY KEY(product, market, brand),
CONSTRAINT FOREIGN KEY (product) REFERENCES Product(id));
INSERT INTO Product
(name) VALUES
('Chair'), /*will get id=1*/
('Table'), /*will get id=2*/
('Bed'); /*will get id=3*/
INSERT INTO Product_Brand_Market
(product, market, brand, price) VALUES
(1, 1, 1, 8.00), /*cheapest chair (brand=1, market=1)*/
(1, 1, 2, 8.50),
(1, 2, 1, 9.00),
(1, 2, 2, 9.50),
(2, 1, 1, 11.50),
(2, 1, 2, 11.00),
(2, 2, 1, 10.50),
(2, 2, 2, 10.00); /*cheapest table (brand=2, market=2)*/
/*no entries for bed, must return null*/
And tried the following code to get the desired values:
UPDATE: Changed INNER JOIN for LEFT JOIN.
SELECT p.id product, MIN(pbm.price) price, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON p.id = pbm.product
GROUP BY p.id;
The returned price is OK, but I'm getting the wrong keys:
| product | price | brand | market |
|---------|-------|-------|--------|
| 1 | 8 | 1 | 1 |
| 2 | 10 | 1 | 1 |
| 3 | null | null | null |
So the only way I could think to solve it is with subqueries, but I had to use two subqueries to get both brand and market:
SELECT
p.id product,
(
SELECT pbm.brand
FROM Product_Brand_Market pbm
WHERE p.id = pbm.product
ORDER BY pbm.price
LIMIT 1
) as brand,
(
SELECT pbm.market
FROM Product_Brand_Market pbm
WHERE p.id = pbm.product
ORDER BY pbm.price
LIMIT 1
) as market
FROM Product p;
It returns the desired table:
| product | brand | market |
|---------|-------|--------|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | null | null |
But I want to know if I really should use these two similar subqueries or there is a better way to do that on MySql, any ideas?
Use a correlated subquery with LIMIT 1 in the WHERE clause:
SELECT product, brand, market
FROM Product_Brand_Market pbm
WHERE (pbm.brand, pbm.market) = (
SELECT pbm1.brand, pbm1.market
FROM Product_Brand_Market pbm1
WHERE pbm1.product = pbm.product
ORDER BY pbm1.price ASC
LIMIT 1
)
This will return only one row per product, even if there are two or many of them with the same lowest price.
Demo: http://rextester.com/UIC44628
Update:
To get all products even if they have no entries in the Product_Brand_Market table, you will need a LEFT JOIN. Note that the condition should be moved to the ON clause.
SELECT p.id as product, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON pbm.product = p.id
AND (pbm.brand, pbm.market) = (
SELECT pbm1.brand, pbm1.market
FROM Product_Brand_Market pbm1
WHERE pbm1.product = pbm.product
ORDER BY pbm1.price ASC
LIMIT 1
);
Demo: http://rextester.com/MGXN36725
The follwing query might make a better use of your PK for the JOIN:
SELECT p.id as product, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON (pbm.product, pbm.market, pbm.brand) = (
SELECT pbm1.product, pbm1.market, pbm1.brand
FROM Product_Brand_Market pbm1
WHERE pbm1.product = p.id
ORDER BY pbm1.price ASC
LIMIT 1
);
An index on Product_Brand_Market(product, price) should also help to improve the performance of the subquery.

Additional rows needed in SQL view

I have the following SQL tables, and require a solution compatible with both MySQL and Postgresql
create table price_level (
id serial primary key,
name varchar(200)
);
create table product (
id serial primary key,
name varchar(200),
base numeric not null,
vat int not null
);
create table product_price (
id serial primary key,
base numeric,
vat numeric,
product_id int not null references product(id) on update cascade on delete cascade,
price_level_id int not null references price_level(id) on update cascade on delete cascade,
unique(product_id,price_level_id)
);
For the SQL structure above I've created a view:
create view view_product as
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
pp.price_level_id
from
product as p
left join
product_price as pp on pp.product_id=p.id
;
These are sample data:
Table price_level
id name
1 A
2 B
3 C
4 D
5 E
Table product
id name base vat
1 Test 100 20
Table product_price
id base vat product_id price_level_id
1 NULL NULL 1 1
2 200 NULL 1 2
3 NULL 10 1 3
Output of the view view_product is:
product_id base vat price_level_id
1 100 20 1
1 200 20 2
1 100 10 3
... and the question is: How do I get output like this?:
product_id base vat price_level_id
1 100 20 1
1 200 20 2
1 100 10 3
1 100 20 4
1 100 20 5
As you see in the example above I need to get D and E price_level as additional rows. How do I create such view/join? It should have good performance also because tables can get big with additional price levels.
Thanks for help.
I would use union to add those records from price_level table that do not have corresponding record in product_price table for a certain product:
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
pp.price_level_id
from
product as p
left join
product_price as pp on pp.product_id=p.id
union distinct
select
p.id as product_id,
p.base,
p.vat,
pl.price_level_id
from
price_level pl
join
product as p
where (p.id, pl.id) not in (select product_id, price_level_id from product_price)
I would use following approach, cross join tables price_level and product. Then just lookup if override exists in product_price table.
SELECT
product.id as product_id,
IFNULL(product_price.base, product.base) as `base`,
IFNULL(product_price.vat, product.vat) as `vat`,
price_level.id as price_level_id
FROM price_level
CROSS JOIN product
LEFT JOIN product_price ON
product_price.price_level_id = price_level.id AND
product_price.product_id = product.id
WHERE product.id = 1
ORDER BY product.id, price_level.id
just remember to use product.id and not product_id in WHERE conditions
Try with:
create view view_product as
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
coalesce(pp.price_level_id,pl.id) --modified row
from
product as p
left join
product_price as pp on pp.product_id=p.id
LEFT JOIN price_level pl on pp.price_level_id=pl.id -- modified row
;
(not tested, but for sure you have to catch the price levels from the properly table)

mysql many-to-one and group by giving unusual results

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
;