How to get the min value after an union in mysql? - mysql

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

Related

MySQL Query to select from table specific rows

I have a table which looks has the following values:
product_id
custom_id
custom_value
1
10
A
1
9
V
2
10
B
3
3
Q
I am looking for a mysql query to get all values from product_id once and select the row which has custom_id = "10" in case it is available. Nevertheless in case custom_id = 10 is not available for a product_id I would still like to return the product_id but also only once.
So the result I am looking for is
product_id
custom_id
custom_value
1
10
A
2
10
B
3
NULL
NULL
Could please someone direct me in the right direction.
select product_id, custom_id, custom_value from table where custom_id = 10
does of course only return the values for product_id "1" and "2"
You can select the first set of rows, then union by a distinct of all the other product id's
select product_id, custom_id, custom_value from table where custom_id = 10
union
select distinct product_id, NULL as custom_id, NULL as custom_value where custom_id <> 10
You can first generate a ROW_NUMBER to get the first element for each "product_id", then transform to NULL values for which "product_id" does not match your value 10, using the IF function.
WITH cte AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY product_id ORDER BY custom_id = 10 DESC) AS rn
FROM tab
)
SELECT product_id,
IF(custom_id=10, custom_id, NULL) AS custom_id,
IF(custom_id=10, custom_value, NULL) AS custom_value
FROM cte
WHERE rn = 1
Check the demo here.

Mysql select query until reach the condition

Lets say I have list of users in my user table like below. I need to find the count of users from my table until the userid is equal to 100.
So here the answer is (3). But how can i find this is MySQL query. Any idea?
userid name
---------------
10 aaa
30 bbb
100 ccc
60 ddd
This is one of the work around to achieve your expectation.
SET #row_number:=0;
SELECT A.row_number FROM (
SELECT Userid, name, #row_number:=#row_number+1 AS row_number
FROM UserDetail
) AS A
WHERE A.Userid = 100;
Working DEMO
In case if the UserId is the not the unique id and it can repeat, you may add the ORDER BY with LIMIT 1
SET #row_number:=0;
SELECT A.row_number FROM (
SELECT Userid, name, #row_number:=#row_number+1 AS row_number
FROM UserDetail
) AS A
WHERE A.Userid = 100
ORDER BY A.row_number
LIMIT 1;
Working DEMO
Sample execution with given data:
--DROP TABLE UserDetail;
CREATE TABLE UserDetail (Userid INT, name VARCHAR (50));
INSERT INTO UserDetail (Userid, name) VALUES
(10 , 'Aaa'),
(30 , 'Bbb'),
(100, 'Ccc'),
(60 , 'ddd');
SET #row_number:=0;
SELECT A.row_number FROM (
SELECT Userid, name, #row_number:=#row_number+1 AS row_number
FROM UserDetail
) AS A
WHERE Userid = 100;
This is difficult to do in MySql as the 'first' instance of a number can be difficult to pin down.
One way to resolve this would be to create row numbers, then choose the minimum row number with Userid = 100
select min(row_number) from
(
SELECT #rownum:=#rownum + 1 as row_number,
p.*
FROM p,
(SELECT #rownum := 0) r
) g
where userid = 100
Here is a functional example

Sql query to find subtotal of distinct column and grand total from a single table?

I have a table in which I have 3 columns
ID Amount District
1 100 A
2 500 B
1 250 A
2 240 B
1 100 A
Now I want to display Subtotal of distinct district and Grand total. Please guide me how should I write my sql query.
I want to show output as-
District Subtotal Grand Total
A 450
B 740 1190
You can use the SUM function to get the grand total, like this:
SELECT SUM(Amount) FROM data;
That'll return a single row containing a single number, the total of all of the Amounts in your entire table.
To get the per-district total, combine SUM with a GROUP BY clause:
SELECT SUM(Amount), District FROM data GROUP BY District;
This will return a table with one row for each district containing the total for that district.
Declaring the sample table and insert some data
DECLARE #tbl TABLE (ID INT, Amount INT, District CHAR(1))
INSERT #tbl
SELECT 1, 100, 'A' UNION ALL
SELECT 2, 500, 'B' UNION ALL
SELECT 1, 250, 'A' UNION ALL
SELECT 2, 240, 'B' UNION ALL
SELECT 1, 100, 'A'
Query
SELECT DISTINCT t.District,
CASE WHEN ID = (SELECT TOP 1 ID FROM #tbl
WHERE District = t.District
ORDER BY ID DESC)
THEN (SELECT SUM(Amount)
FROM #tbl
WHERE ID <= t.ID
AND District = t.District)
ELSE ' ' END AS [Subtotal],
CASE WHEN ID = (SELECT TOP 1 ID FROM #tbl
ORDER BY District DESC)
THEN (SELECT SUM(Amount)
FROM #tbl)
ELSE ' ' END AS [Grand Total]
FROM #tbl AS t
You can do the same thing for MySQL but instead of using TOP you need to use LIMIT
If you want the data in this format you can try using below code :
SELECT District As 'District', SUM(Amount) as 'Total'
FROM tbl
GROUP BY District
UNION
SELECT 'Grand Total', sum(Amount)
FROM tbl

Find distinct values in SQL when subquery returns more than one row?

These are my tables
Table1
price
city_category_id
city_product_id
Here are three rows"
price | city_category_id | city_product_id
------+------------------+--------------------------
1500 | CHDELCLAPTOPDELL | CHDELCLAPTOPDELLVOSTR8
1200 | CHDELCLAPTOPDELL | CHDELCLAPTOPDELLVOSTR816
1000 | CHDELCLAPTOPDELL | CHDELCLAPTOPDELLVOSTR816
Here I have to find firstly distinct product_name and then select min price of the distinct elements.I want output as CHDELCLAPTOPDELLVOSTR816 and 1200 and CHDELCLAPTOPDELLVOSTR816 and 1000.
QUERY
select min(price)
from sellers_product
where city_product_id=
(
select distinct city_product_id
from sellers_product
where city_category_id='CHDELCLAPTOPDELL'
)
ERROR
I know why this error is coming because there are more than 1 rows returned by subquery but is there any way to get the desired output using only 1 query.
As I understand you want something like this:
DECLARE #sellers_product TABLE (price INT, Category_id VARCHAR(100), product_name VARCHAR(100))
INSERT INTO #sellers_product SELECT 1500, 'DELL', 'Vostro123'
INSERT INTO #sellers_product SELECT 1200, 'DELL', 'Vostro1234'
INSERT INTO #sellers_product SELECT 1000, 'DELL', 'Vostro123'
SELECT product_name, MIN(price) AS minPrice
FROM #sellers_product
WHERE Category_id = 'DELL'
GROUP BY product_name
Results:
product_name minPrice
Vostro123 1000
Vostro1234 1200
This select first filters by Category_id for rows/categories, which you need, and then groups by 'product_name' to get unique name. In this case we group 2 rows with 'Vostro123' product_name. From this grouped rows we can also take MIN price value.
Try this and let me know if it is working. If your subquery returns more than one row then you cant use = as it will return single value:
select distinct city_product_id,min(price),product_name from sellers_product
where city_category_id='CHDELCLAPTOPDELL'
Group By product_name
;WITH CTE_RESULT AS
(
SELECT price, city_category_id, city_product_id, -- USE THE SELECT COLUMNS HERE
ROW_NUMBER() OVER (PARTITION BY city_product_id --USE THE DISTINCT COLUMN HERE
ORDER BY price ASC -- CHANGE THE ORDER HERE) ROW_NO
FROM #TABLE1
)
SELECT price, city_category_id, city_product_id FROM CTE_RESULT
WHERE ROW_NO=1 -- FILTERS THE DUPLICATE ROWS
here is my answer using sqlite version 3 with your new data
.mode column
.separator ","
.header ON
.width 25 25
DROP TABLE IF EXISTS sellers_product;
CREATE TABLE sellers_product (
price INT,
city_category_id VARCHAR(100),
city_product_id VARCHAR(100));
INSERT INTO sellers_product VALUES(1500, 'CHDELCLAPTOPDELL', 'CHDELCLAPTOPDELLVOSTR8');
INSERT INTO sellers_product VALUES(1200, 'CHDELCLAPTOPDELL', 'CHDELCLAPTOPDELLVOSTR816');
INSERT INTO sellers_product VALUES(1000, 'CHDELCLAPTOPDELL', 'CHDELCLAPTOPDELLVOSTR816');
SELECT city_product_id, MIN(price) AS minPrice
FROM sellers_product
WHERE city_category_id = 'CHDELCLAPTOPDELL'
GROUP BY city_product_id;
------------- output -----------------
city_product_id minPrice
------------------------- -------------------------
CHDELCLAPTOPDELLVOSTR8 1500
CHDELCLAPTOPDELLVOSTR816 1000

Getting the second max value of col2 while being grouped by col1

I'm facing a corner here...
The background:
TABLE myrecord (
id int # primary key
name varchar(32) # test name
d_when varchar(8) # date in yyyymmdd string format
)
Content:
id name d_when
100 Alan 20110201
101 Dave 20110304
102 Alan 20121123
103 Alan 20131001
104 Dave 20131002
105 Bob 20131004
106 Mike 20131101
In layman terms, I want to figure out who is a "returner" and when was his last (i.e., 'penultimate') visit.
something like the over enthusiastic:
SELECT SECOND_MAX(id), CORRESPONDING(d_when)
FROM myrecord
GROUP BY name
HAVING count(name)>1;
result expected:
101 Dave 20110304
102 Alan 20121123
I tried the following so far.
SELECT T1.id, t1.name, T1.d_when
FROM myrecord t1
WHERE id IN (SELECT MAX(id),
COUNT(id) cn
WHERE cn>1
ORDER BY d_when DESC)
but something is clearly not right here.
Here's one way...
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.name=x.name
AND y.d_when >= x.d_when
GROUP
BY x.name
, x.d_when
HAVING COUNT(*) = 2;
why is the second last id necessary?
if it's not:
SELECT MAX(id), name, MAX(d_when)
FROM myrecord
GROUP BY name
HAVING count(name)>1
if it is:
SELECT name, max(id), max(d_when)
FROM myrecord
WHERE
-- get only the names that have more then one occurrence
name in (
SELECT name
FROM myrecord
GROUP BY name
HAVING COUNT(*) > 1
)
-- filter out the max id's
AND id NOT IN(
SELECT max(id)
FROM myrecord
GROUP BY name
)
GROUP BY name
or even better (thanks to #Andomar for the mention):
SELECT name, max(id), max(d_when)
FROM myrecord
WHERE
-- filter out the max id's, this will also filter out those with one record
AND id NOT IN(
SELECT max(id)
FROM myrecord
GROUP BY name
)
GROUP BY name
In MySQL, for retrieving all those who have made second visit and their second visited date.
Query:
SELECT *
FROM
(
SELECT
#ID:=CASE WHEN #Name <> Name THEN 1 ELSE #ID+1 END AS rn,
#Name:=Name AS Name,
ID, d_when
FROM
(SELECT ID, Name, d_when
FROM myrecord
ORDER BY Name asc, d_when asc
) rec1, (SELECT #ID:= 0) rec1_id, (SELECT #Name:= 0) rec1_nm
) rec
where rec.rn=2
Output:
rn Name ID d_when
2 Dave 104 20131002
2 Alan 102 20121123
Assuming the id column ascends over time, you can select the second-highest d_when per name like:
select name
, d_when
from YourTable yt1
where id in
(
select max(id)
from YourTable yt2
where id not in
(
select max(id)
from YourTable yt3
group by
name
)
group by
name
)
select * from
myrecord
where id in (
SELECT max(id)
FROM myrecord
WHERE id not in (SELECT MAX(id)
FROM myrecord
GROUP BY name
HAVING count(name)>1)
group by name )