I have a table for recording daily price from different suppliers. My goal is to find the best (low price) supplier.
The table structure is
Table Name: lab1
Columns: ID, Product_ID, Price_date, Price, Supplier
-----------------------------------------------------------------------------------
ID Product_ID Price_date Price Supplier
--------------------------------------------------------------------------------------
1 8 26-10-2014 1300 SP1
2 8 05-10-2014 1600 SP2
3 8 15-10-2014 1300 SP1
4 8 14-12-2014 1200 SP3
------------------------------------------------------------------------------------------
Create Table Structure
CREATE TABLE clickpic_pricecompare.lab1 (
ID int(11) NOT NULL AUTO_INCREMENT,
Product_ID int(11) DEFAULT NULL,
Price_Date date DEFAULT NULL,
Price decimal(19, 2) DEFAULT NULL,
Supplier varchar(255) DEFAULT NULL,
PRIMARY KEY (ID)
)
ENGINE = MYISAM
COMMENT = 'testing-purpose';
INSERT INTO lab1(ID, Product_ID, Price_Date, Price, Supplier) VALUES
(1, 8, '2014-10-26', 1300.00, 'SP1');
INSERT INTO lab1(ID, Product_ID, Price_Date, Price, Supplier) VALUES
(2, 8, '2014-10-05', 1600.00, 'SP2');
INSERT INTO lab1(ID, Product_ID, Price_Date, Price, Supplier) VALUES
(3, 8, '2014-10-15', 1300.00, 'SP1');
INSERT INTO lab1(ID, Product_ID, Price_Date, Price, Supplier) VALUES
(4, 8, '2014-10-14', 1200.00, 'SP3');
I NEED THE RESULT LOOKS LIKE BELOW
--------------------------------------------------------------------------------------
ID Product_ID Month Price Supplier
--------------------------------------------------------------------------------------
4 8 October 1200 SP3
-------------------------------------------------------------------------------------------
Please help...
You can use self join with the product id and minimum amount of price to get the lowest price row per product id
select l.ID,
l.Product_ID,
monthname(l.Price_Date) `Month`,
l.Price,
l.Supplier
from lab1 l
join (select Product_ID,min(Price) Price
from lab1
group by Product_ID) l1
using(Product_ID,Price)
DEMO
select temp2.id,
temp2.Product_ID,
DATENAME(month, temp2.Price_Date) AS MONTH,
temp1.Min_Price,
temp2.Supplier
from
(
select Product_ID, min(Price) as Min_Price
from lab1
group by Product_ID
) as temp1
inner join
lab1 temp2
on temp1.Product_ID = temp1.Product_ID
and temp1.Min_Price = temp2.Min_Price
I think you are looking for:
select l.ID, l.Product_ID, monthname(l.Price_Date) as Month, l.Price, l.Supplier
from lab1 l join
(select Product_ID, year(l.Price_date) as yr, month(l.Price_Date) as mon, min(Price) as Price
from lab1
group by Product_ID, year(l.Price_date), month(l.Price_Date)
) lmin
on l.Product_id = lmin.Product_id and
year(l.Price_Date) = lmin.yr and
month(l.Price_Date) = lmin.mon;
If you want data only for October, then add a where clause:
where l.Price_Date >= '2014-10-01' and l.Price_Date < '2014-11-01'
Related
I have an items table in my database that i want my query to process the values and give me the data of the max price, min price, most recurrent max price in that specific item category and no of items (and ignore the ones that are null), so here is my items table:
id
category
min_price
max_price
1
kids
10
100
2
adult
20
200
3
both
null
null
4
adult
20
100
5
adult
50
100
6
adult
50
200
7
kids
20
100
8
both
20
100
9
kids
null
null
10
adult
10
500
11
misc
null
null
I want the query to return this result:
category
min_price
max_price
price_mode
no_items
kids
10
100
100
3
adult
20
500
200
5
both
20
100
100
2
misc
null
null
null
1
so just to further explain the adult lowest price in 20 and highest is 500 and the 100 and 200 max_price has 2 occurrences both i want to take the highest as the price_mode which is 200 in this case and the no_items is just the count of how many times adult is shown in the table.
am struggling to get the mode honestly and grouping it correctly to get the output I want.
Below is the commands to create table and feed it with data. Tried to put it in SqlFiddle but that's not working for me i don't know why.
CREATE TABLE IF NOT EXISTS `items` (
`id` int(6) unsigned NOT NULL,
`category` TEXT NOT NULL,
`min_price` FLOAT DEFAULT NULL,
`max_price` FLOAT DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `items` (`id`, `category`, `min_price`, `max_price`) VALUES
('kids', 10, 100),
('adult', 20, 200),
('both', null, null),
('adult', 20, 100),
('adult', 50, 100),
('adult', 50, 200),
('kids', 20, 100),
('both', 20, 100),
('kids', null, null),
('adult', 10, 500),
('misc', null, null);
Your create table + insert data syntax doesn't work in fiddle because your data VALUES are for just 3 columns whereby you define 4 columns in the INSERT:
INSERT INTO `items` (`id`, `category`, `min_price`, `max_price`) VALUES
('kids' , 10 , 100),
/*where's the value for `id`?*/
...
If you remove id from the INSERT syntax, it won't work as well because you've set it as PRIMARY KEY so it can't be empty. What you can do in addition to removing id from INSERT is to define AUTO_INCREMENT on the id column:
CREATE TABLE IF NOT EXISTS `items` (
`id` int(6) unsigned NOT NULL AUTO_INCREMENT,
....
Now, to get the expected result on your price_mode, you may want to try using GROUP_CONCAT() with ORDER and define which of the data in there that you want to return. Let's say you do GROUP_CONCAT(max_price ORDER BY max_price DESC) to return the set with max_price in descending order like this:
SELECT category,
MIN(min_price),
MAX(max_price),
GROUP_CONCAT(max_price ORDER BY max_price DESC),
COUNT(*)
FROM items
GROUP BY category;
Then you'll get a result like this:
category
MIN(min_price)
MAX(max_price)
GROUP_CONCAT(max_price ORDER BY max_price DESC)
COUNT(*)
adult
10
500
500,200,200,100,100
5
both
20
100
100
2
kids
10
100
100,100
3
misc
NULL
NULL
NULL
1
So, there's a consistent pattern in the GROUP_CONCAT() result that you probably can work out with. Assuming that you want the second largest value in the set, you can apply SUBSTRING_INDEX() twice to get it like this:
SELECT category,
MIN(min_price) AS min_price,
MAX(max_price) AS max_price,
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(max_price ORDER BY max_price DESC),',',2),',',-1)
AS price_mode,
COUNT(*) AS no_items
FROM items
GROUP BY category;
This return the following result:
category
min_price
max_price
price_mode
no_items
adult
10
500
200
5
both
20
100
100
2
kids
10
100
100
3
misc
NULL
NULL
NULL
1
Demo fiddle
The following is an updated suggestion after getting further clarification:
SELECT i.category,
MIN(i.min_price),
MAX(i.max_price),
v2.mp AS price_mode,
COUNT(DISTINCT i.id)
FROM items i
LEFT JOIN
(SELECT cat,
mp,
cnt,
CASE WHEN cat = #cat
THEN #rownum := #rownum + 1
ELSE #rownum:=1 END AS rownum,
#cat := cat
FROM
(SELECT category cat,
max_price mp,
COUNT(*) cnt
FROM items
GROUP BY category,
max_price) v1
CROSS JOIN (SELECT #rownum := 1,
#cat := NULL) seq
WHERE mp IS NOT NULL
ORDER BY cat, cnt DESC, mp DESC) v2
ON i.category=v2.cat
AND v2.rownum=1
GROUP BY i.category, v2.mp;
The query starts with getting the COUNT(*) value of category and max_price combination. Then generating a custom row numbering on it with a WHERE condition that doesn't return max_price with NULL after the first operation. Probably the crucial part here is the ORDER BY cat, cnt DESC, mp DESC since the row numberings are assigned based on it. Otherwise, the row numbering will mess up. Finally, LEFT JOIN the items table with it with ON i.category=v2.cat AND v2.rownum=1 condition. It's important to make sure the v2.rownum=1 is placed at ON condition instead of WHERE in order to return the last row value of misc; since the subqueries will not have the value with the present sample data.
Here's an updated fiddle for reference, including the sample of 3 adult=NULL.
Maybe this query will help
with maximumvaluecounts
as ( select
count(max_price) as c, category, max_price
from yourtable
group by category
),
maximumcountpercategory
as ( select category,max(c) as c
from maximumvaluecounts
group by category
),
modes as ( select category, max_price as modevalue
from maximumcountpercategory m1
join maximumvaluecounts m2
on m1.category=m2.category
and m1.c=m2.c
)
, others as (
select
category,
min(min_price) as min_price,
max(max_price) as max_price,
count(max_price) as no_items
from yourtable
group by category
)
select o.*, m.modevalue as price_mode
from others o join
modes m on o.category=m.category
Let's say I have 2 tables A and B.
These 2 tables have 3 columns in common Name, Id and Price.
This is the query I used for 1 table :
SELECT Name, Id, Price FROM A WHERE Id = "123" and Price = (SELECT MIN(Price) FROM A);
I've just realised that this query doesn't work when the lowest price is held by another Id.
So I've look around and I think I should use GROUP BY ?
I've changed it to :
SELECT Name, MIN(Price) FROM A WHERE Id = "123" GROUP BY Name;
But this is not the expected result.
Let's say in table A I have :
Name
Id
Price
Au
123
12
Be
123
16
St
122
9
Ge
123
10
And for table B I have :
Name
Id
Price
La
123
14.5
La
123
12
St
123
13
Is
123
12
Is
123
10
La
123
10
Is
123
10
And the expected result is :
Name
Price
Ge
10
Is
10
La
10
The expected result is 1 row long because in the set of data there is only one row that match the condition but if I had another row with a Price of 10 and an Id of 123 it should be there also. So if there are more rows that matched the condition I want them in the result.
The problem is that when I do the following query using UNION I don't know how to get the lowest price for a specific Id:
SELECT Name, Id, Price FROM A UNION SELECT Name, Id, Price FROM B;
So what can I add to my query to have the expected result and then how would it work if I use union to get the lowest price of a specific Id over 2 tables ?
On MySQL 8+, we can use RANK here with a union query:
WITH cte AS (
SELECT Name, Id, Price FROM A WHERE Id = '123'
UNION ALL
SELECT Name, Id, Price FROM B WHERE Id = '123'
),
cte2 AS (
SELECT *, RANK() OVER (ORDER BY price) rnk
FROM cte
)
SELECT Name, Id, Price
FROM cte2
WHERE rnk = 1;
Here is a query which should work on earlier versions of MySQL:
SELECT Name, Id, Price
FROM
(
SELECT Name, Id, Price FROM A WHERE Id = '123'
UNION ALL
SELECT Name, Id, Price FROM B WHERE Id = '123'
) t
WHERE Price = (
SELECT Price FROM A WHERE Id = '123'
UNION ALL
SELECT Price FROM B WHERE Id = '123'
ORDER BY Price
LIMIT 1
);
This should be ok for you:
select *
from (select Name, id, Price FROM A
union
select Name, id, Price FROM B) Tab
where (Tab.id, Tab.price) = (select Tab2.id, min(Tab2.price)
from (select Name, id, Price FROM A
union
select Name, id, Price FROM B) Tab2
where Tab2.id = '123')
You have only one place where you put the ID you are looking for.
Here you can see the demo:
DEMO
/*This returns everything from your two tables*/
select *
from (SELECT Name, id, Price FROM A
union
select Name, id, Price FROM B) Tab
/*this returns the minimal price for your requested ID, here you requested id =123*/
select Tab2.id, min(Tab2.price)
from (SELECT Name, id, Price FROM A
union
select Name, id, Price FROM B) Tab2
where Tab2.id = '123'
--with this where clause:
where (Tab.id, Tab.price)
/*you are telling the query :
give me every row from all the data(first query)that has
this combination of ID + PRICE:
123 + 10 (you have found this with the second query)
So, you do not care what name it is, it only has to have :
ID = 123 and the lowest price which is 10.
ID 123 was requested from you and lowest price for that ID is 10,
which you have founded with the second query.*/
I tested this on db-fiddle.com and it returns all the rows with the lowest price:
SELECT Id, Name, Price
FROM (SELECT * FROM A UNION SELECT * FROM B) TMP
WHERE (Price, Id) = (
SELECT MIN(Price), Id
FROM (SELECT * FROM A UNION SELECT * FROM B) TMP2
WHERE Id = "123"
);
Here are the script for the tables I tested the query against:
create table A(
_id INT NOT NULL AUTO_INCREMENT,
Id VARCHAR(100) NOT NULL,
Name VARCHAR(100) NOT NULL,
Price INT NOT NULL,
PRIMARY KEY ( _id )
);
create table B(
_id INT NOT NULL AUTO_INCREMENT,
Id VARCHAR(100) NOT NULL,
Name VARCHAR(100) NOT NULL,
Price INT NOT NULL,
PRIMARY KEY ( _id )
);
INSERT INTO A(Id, Name, Price)
VALUES
('123', 'Name123a1', 21),
('123', 'Name123a2', 41),
('124', 'Name124a', 40);
INSERT INTO B(Id, Name, Price)
VALUES
('123', 'Name123b1', 22),
('123', 'Name123b2', 21),
('124', 'Name124b', 20);
The solution took some time to figure out, because I am rusty. Thanks to VBoka that helped me with sorting out bugs.
I would do this after your last query, the one that outputs 3 price values and you need the minimum.
create a cte
create a rank using ROW_NUMBER based on price in ASC order which is the default order if you want highest then add DESC in the end
filter the data with that rank column
with data as (
select
*, ROW_NUMBER () OVER(ORDER BY price ) as rank_ from table
)
select * from data where rank_ = 1
I would like to get the user_id and the sum of amount for the users who have largest summed amount. I cannot use LIMIT because that will return only 1 record (summed amount may be same for multiple users)
Here is my data schema and some records
CREATE TABLE transactions (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
user_id BIGINT(20) NOT NULL,
amount FLOAT NOT NULL, PRIMARY KEY (id)
);
INSERT INTO transactions (user_id, amount) VALUES
(1, 1000),
(1, 1000),
(1, 1000),
(2, 2000),
(2, 1000),
(3, 1000);
Here are the expected result.
+---------+------+
| user_id | sum |
+---------+------+
| 1 | 3000 |
| 2 | 3000 |
+---------+------+
I can get the above result by using the following sql. However, I don't know is there any better approach or not. Is it necessary to repeat the same subquery twice? Thanks.
SELECT T1.user_id, T1.sum
FROM (
SELECT user_id, SUM(amount) as sum
FROM transactions
GROUP BY user_id
) T1
WHERE T1.sum = (
SELECT MAX(T2.sum)
FROM (
SELECT user_id, SUM(amount) as sum
FROM transactions
GROUP BY user_id
) T2
)
GROUP BY T1.user_id;
Well you can simplify your query to
SELECT user_id, SUM(amount) as sum
FROM transactions
GROUP BY user_id
HAVING SUM(amount) = (
SELECT SUM(amount) as sum
FROM transactions
GROUP BY user_id
ORDER BY SUM(amount) DESC
LIMIT 1
)
I try to get a list of product-groups sorted by group.
My problem is that i need only the one product into a group that is available( 0=no, 1=yes ) and with the lowest price and amount>0.
select id, group_id, price, amount, available from products
Table rows:
id group_id price amount available
1 1 100 1 1
2 1 50 1 1
3 1 25 1 0
4 2 100 2 1
5 2 200 1 1
6 2 100 2 1
7 2 50 1 1
I need the rows with id 2 and 7 as the result.
My problem is into a group_id there are more than one id with the same price.
Any ideas to solve this without subselects?
This is ANSI standard and should return what you want. If there are multiple rows matching the criteria (ie with the same minimu price) it will return only the first one (lower id).
SELECT MIN(P.id), P.group_id, P.price, P.amount, P.available
FROM products P
INNER JOIN
(SELECT group_id, MIN(price) AS minprice
FROM products
WHERE available=1 AND amount > 0
GROUP BY group_id
) G ON P.group_id = G.group_id AND P.price = G.minprice
GROUP BY P.group_id, P.price, P.amount, P.available
ORDER BY P.group_id
Note that if you have a large table you might need to index the price column (or a composite index group_id + price)
It would have been simpler on other RDBMS that supports window functions
You can use HAVING clause and MIN on the GROUP BY, like this:
CREATE TABLE products (
id INT PRIMARY KEY NOT NULL,
group_id INT NOT NULL,
price INT NOT NULL,
amount INT NOT NULL,
available INT NOT NULL
);
INSERT INTO products (id, group_id, price, amount, available) VALUES (1, 1, 100, 1, 1);
INSERT INTO products (id, group_id, price, amount, available) VALUES (2, 1, 50 , 1, 1);
INSERT INTO products (id, group_id, price, amount, available) VALUES (3, 1, 25 , 1, 0);
INSERT INTO products (id, group_id, price, amount, available) VALUES (4, 2, 100, 2, 1);
INSERT INTO products (id, group_id, price, amount, available) VALUES (5, 2, 200, 1, 1);
INSERT INTO products (id, group_id, price, amount, available) VALUES (6, 2, 100, 2, 1);
SELECT id, group_id, price, amount, available FROM products;
SELECT id, group_id, price, amount, available
FROM products
WHERE amount > 0
AND available = 1
GROUP BY group_id HAVING min(price);
Ok, I have a query over two tables. I need to get two sums. I do a group by so the sum() works correctly.
SELECT sum(a.x), sum(b.y) FROM a,b GROUP BY a.n where a.n=b.m
So far this works well, but the problem is i need to group them differently for the second sum (sum(b.y)), than for the first sum (sum(a.x)).
The real query is somewhat more complex but this is my main problem.
This is what i actually try to select sum(stock.amount) - if( sold.amount IS NULL , 0, sum( sold.amount ) )
How can I solve that in one query?
since you are not writing down the tables I am gonna make a wild guess and assume the tables are like :
stock : id, item_id, amount
sold : id, item_id, amount
then again I assume that you need the stock_in_total, sold_total, left_total counts
SELECT
stock_sums.item_id,
stock_sums.st_sum as stock_in_total,
COALESCE(sold_sums.so_sum,0) as sold_total,
(stock_sums.st_sum - COALESCE(sold_sums.so_sum,0)) as left_total
FROM (
SELECT stock.item_id as item_id, SUM(stock.amount) as st_sum
FROM stock
GROUP BY item_id
) as stock_sums
LEFT JOIN (
SELECT sold.item_id as item_id, SUM(sold.amount) as so_sum
FROM sold
GROUP by item_id
) as sold_sums ON stock_sums.item_id = sold_sums.item_id
I hope this would help.
Here is how I would do it. I assume that Stock is the main table, with an ID and an amount, and that Sold maps to Stock via an ID value, and has zero to many records for each Stock item.
SELECT Q1.id, Q1.Total1, Q2.Total2
, Q1.Total1 - COALESCE(Q2.Total2,0) as Outstanding
FROM (
SELECT id, SUM(amount) as Total1
FROM Stock GROUP BY id
) as Q1
LEFT OUTER JOIN (
SELECT id, SUM(Amount) as Total2
FROM Sold GROUP BY id
) as Q2
ON Q2.id = Q1.id
Note that simply formatting your SQL into a clean way forces you to break it into logical parts and will often reveal exactly what is wrong with the query.
The example above also handles correctly the cases where there is not match in the Sold table.
Cheers,
Daniel
(Code Assumptions)
DROP TABLE Stock
CREATE TABLE Stock (
id integer
, amount decimal(10,2)
)
INSERT INTO Stock (id, amount ) VALUES ( 1, 10.1);
INSERT INTO Stock (id, amount ) VALUES ( 2, 20.2);
INSERT INTO Stock (id, amount ) VALUES ( 3, 30.3);
SELECT * FROM STOCK
DROP TABLE Sold
CREATE TABLE Sold (
id integer
, amount decimal(10,2)
)
INSERT INTO Sold (id, amount ) VALUES ( 1, 1.1);
INSERT INTO Sold (id, amount ) VALUES ( 1, 2.2);
INSERT INTO Sold (id, amount ) VALUES ( 1, 3.3);
INSERT INTO Sold (id, amount ) VALUES ( 2, 2.22);
SELECT * FROM Sold
SELECT Q1.id, Q1.Total1, Q2.Total2
, Q1.Total1 - COALESCE(Q2.Total2,0) as Outstanding
FROM (
SELECT id, SUM(amount) as Total1
FROM Stock GROUP BY id
) as Q1
LEFT OUTER JOIN (
SELECT id, SUM(Amount) as Total2
FROM Sold GROUP BY id
) as Q2
ON Q2.id = Q1.id
Results:
id Total1 Total2 Outstanding
1 10.10 6.60 3.50
2 20.20 2.22 17.98
3 30.30 30.30
REVISION
It sounds like you want the total amount of stock you have as one count for each different stock. Then you want how much stock you have left for each stock based on what has been sold. Correct?
If so check this out:
select stock, sum(a.x) as sharesBeforeSale, (sum(a.x) - sum(b.y)) as sharesAfterSale
FROM db.table1 a, db.table2 b
WHERE a.UNIQUEID = b.UNIQUEID AND b.y IS NOT NULL
GROUP BY a.UNIQUEID;
Does that accomplish what you are looking to do?
stock sharesBeforeSale sharesAfterSale
duk 100 25
orc 101 101
yrc 54 41
Enjoy!
Sample tables
db.table1 (stock owned):
UNIQUEID x stock
1 100 duk
2 101 orc
3 54 yrc
db.table2 (stock sold):
UNIQUEID y
1 75
2 0
3 13