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 )
Related
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 have a table as follows and what I want is to use get the initial row with least id of each uid group.
The table is as follows
_id uid type
1 a a
2 b bbb #satisfied
3 b ccc
4 b aaa #satisfied
5 a aaa #satisfied
6 b eee
I can already get the initial row using the following correlated subquery
SELECT *
FROM table
WHERE _id IN (
SELECT MIN(_id)
FROM table
WHERE type IN ('aaa','bbb')
GROUP BY uid
);
However, I want the 4th column shown the count of rows satisfied the condition (type IN ('aaa','bbb')), as cnt shown below:
_id uid type cnt
5 a aaa 1
2 b bbb 2
I think I can count this use several joins and then join the result to my code...But this is ugly...Is there any elegant way to achieve this...
You can try this:
SELECT t1.*, t2.cnt
FROM table t1 INNER JOIN (
SELECT MIN(_id) AS id, COUNT(_id) AS cnt
FROM table
WHERE type IN ('aaa','bbb')
GROUP BY uid
) t2 ON t1._id = t2.id
ORDER BY t1.uid
If you are running MySQL 8.0, you can just use window functions for this:
select _id, uid, type, cnt
from (
select
t.*,
count(*) over(partition by uid) cnt,
row_number() over(partition by uid order by _id) rn
from mytable t
where type in ('aaa', 'bbb')
) t
where rn = 1
You can do this without a subquery. In MySQL 8+, you can use this logic:
SELECT DISTINCT MIN(_id) OVER (PARTITION BY uid) as _id,
uid,
FIRST_VALUE(type) OVER (PARTITION BY uid ORDER BY _id) as type,
COUNT(*) OVER (PARTITION BY uid) as cnt
FROM table
WHERE type IN ('aaa', 'bbb');
Unfortunately, MySQL doesn't have a "first" aggregation function, but there is a trick if you like:
SELECT MIN(_id) as _id, uid,
SUBSTRING_INDEX(GROUP_CONCAT(type ORDER BY _id), ',', 1) as type,
COUNT(*) as cnt
FROM table
WHERE type IN ('aaa', 'bbb')
GROUP BY uid;
There are lots of questions/answers about selecting unique values in a MySQL query but I haven't seen any on creating a unique value flag.
I have a customer_ID that can appear more than once in a query output. I want to create a new column that flags whether the customer_ID is unique or not (0 or 1).
The output should look something like this:
ID | Customer ID | Unique_Flag
1 | 1234 | 1
2 | 2345 | 1
3 | 2345 | 0
4 | 5678 | 1
Please let me know if anybody needs clarifications.
You seem to want to mark the first occurrence as unique, but not others. So, let's join in the comparison value:
select t.*,
(id = min_id) as is_first_occurrence
from t join
(select customer_id, min(id) as min_id
from t
group by customer_id
) tt
on t.customer_id = tt.customer_id;
For most people, a "unique" flag would mean that the overall count is "1", not that this is merely the first appearance. If that is what you want, then you can use similar logic:
select t.*,
(id = min_id) as is_first_occurrence,
(cnt = 1) as is_unique
from t join
(select customer_id, min(id) as min_id, count(*) as cnt
from t
group by customer_id
) tt
on t.customer_id = tt.customer_id;
And, in MySQL 8+, you would use window functions:
select t.*,
(row_number() over (partition by customer_id order by id) = 1) as is_first_occurrence,
(count(*) over (partition by customer_id) = 1) as is_unique
from t;
You can try below
select id,a.customerid, case when cnt=1 then 1 else 0 end as Unique_Flag
from tablename a
left join
(select customerid, count(*) as cnt from tablename
group by customerid
)b on a.customerid=b.customerid
You can use lead function as given below to get the required output.
SELECT ID, CUSTOMER_ID,
CASE
WHEN CUSTOMER_ID != CUSTOMER_ID_NEXT THEN 1
ELSE 0
END AS UNIQUE_FLAG FROM
(SELECT ID, CUSTOMER_ID,LEAD(CUSTOMER_ID, 1, 0) OVER (ORDER BY CUSTOMER_ID) AS CUSTOMER_ID_NEXT FROM TABLE)T
I have a table like this:
id name
0 Bob
1 Alice
2 Bob
3
4 Bob
5 Mary
6 Alice
I need to assign a group_id to each distinct name:
id name group_id
0 Bob 0 -- Bob's group
1 Alice 1 -- Alice's group
2 Bob 0 -- Bob's group
3 -- no group (NULL)
4 Bob 0 -- Bob's group
5 Mary 2 -- Mary's group
6 Alice 1 -- Alice's group
Can this be done in one line in MySQL?
I know I could find the unique names with an autoincrement column, then JOIN back with the original table based on the name -- but I was wondering if there exists a simpler/faster solution...
Yes, use case. Just add a new computed column based on an expression that outputs the appropriate value for each string value of the name column
Select id, name,
case name
when 'Bob' then 0
when 'Alice' then 1
when 'Mary' then 2
-- etc.
end GroupId
From table
If you don't know the names in advance, or if there are too many, try this:
Select id, name,
(select count(distinct name)
from table
where name < t.Name) groupId
From table t
Unless you add an index on the name column, this will be very slow on a large table.
To output a null instead of a 0 for rows with name = null, use this:
Select id, name,
case when name is null then null
else (select count(distinct name)
from table
where name < t.Name) end groupId
From table t
One method is -- as you suggest -- group by and join. If the numbers do not have to be sequential:
select t.*, minid as group_id
from t join
(select name, min(id) as minid
from t
group by name
) tt
on not t.name <=> tt.name; -- to handle `NULL`
If they do, use variables:
select t.*, minid as group_id
from t join
(select name, min(id) as minid, (#grp := #grp + 1) as group_id
from t cross join
(select #grp := -1) params
group by name
) tt
on not t.name <=> tt.name; -- to handle `NULL`;
You could also do the whole operation with two sorts and variables:
select t.*
from (select t.*,
(#grp := if(not #n <=> name, #grp,
if(#n := name, 1, 1)
)
) as group_id
from t
(select #grp := -1, #n := '') params
order by name
) t
order by id;
I need to be able to select two records from a table based on ID.
I need the first one, and the last one (so min, and max)
Example:
Customer
ID NAME
1 Bob
50 Bob
Any ideas? Thanks.
SELECT MIN(id), MAX(id) FROM tabla
EDIT: If you need to retrive the values of the row you can do this:
SELECT *
FROM TABLA AS a, (SELECT MIN(id) AS mini,
MAX(id) AS maxi
FROM TABLA) AS m
WHERE m.maxi = a.id
OR m.mini = a.id;
Is this what you are looking for?
select id, name from customers where id = ( select max(id) from customers )
union all
select id, name from customers where id = ( select min(id) from customers )
Now I have tested this type of query on a MySQL database I have access, and it works. My query:
SELECT nome, livello
FROM personaggi
WHERE livello = (
SELECT max( livello )
FROM personaggi )
If ties for first and/or last place are not a concern, then consider the following query:
(SELECT id, name FROM customers ORDER BY id DESC LIMIT 1)
UNION ALL
(SELECT id, name FROM customers ORDER BY id LIMIT 1);
It worked for me:
select * from customer where id in ((select min(id) from customer),(select max(id)
from customer));
SELECT MIN(value), MAX(value) FROM table