SQL - Select same values in column then rename them - mysql

Is it possible in SQL to select values in a column then rename the duplicate ones? (assuming maximum of one possible duplicate only)
Let's say I have a table..
| id | name | 0or1_id |
| 0 | Eddy | 0 |
| 1 | Allan | 0 |
| 2 | Eddy | 1 |
| 3 | Allan | 1 |
What query can I do to make it like this?
| id | name | 0or1_id |
| 0 | Eddy | 0 |
| 1 | Allan | 0 |
| 2 | Eddy-copy | 1 |
| 3 | Allan-copy | 1 |

Assuming you want to actually change the data, use update:
update t join
(select name, count(*) as cnt, min(id) as minid
from t
group by name
having cnt > 1
) tt
on t.name = tt.name and t.id <> tt.minid
set name = concat(name, '-copy');
If you only want a select, then the logic is quite similar.

This will work in SQL Server..
select id , name ,0or1_id from (
select id , name ,0or1_id ,row_number() over (partition by name order by id ) as rnm
from table)z1
where rnm =1
union
select id , name || '- Copy' as new_name ,0or1_id from (
select id , name ,0or1_id ,row_number() over (partition by name order by id ) as rnm
from table)z2
where rnm > 2

http://sqlfiddle.com/#!9/3ebaf/1
UPDATE mytable t
INNER JOIN mytable t1
ON t.name = t1.name
AND t.id>t1.id
SET t.name = CONCAT(t.name,'-copy');

Related

How do I get the first n records, per foreign key with MySQL?

I have the following table:
+----+-----------+------+
| id | table2_id | type |
+----+-----------+------+
| 1 | 100 | A |
| 2 | 100 | B |
| 3 | 100 | C |
| 4 | 100 | A |
| 5 | 250 | A |
+----+-----------+------+
I need a select statement that would get all the records before the first occurrence of type C, per table2_id.
So I want records 1, 2, and 5
I'd do this in code with a loop, but I need to do it in MySQL specifically.
If you are running MySQL 8.0, you can do this with window functions:
select *
from (
select t.*,
min(case when type = 'C' then id end) over(partition by table2_id) min_id
from mytable t
) t
where min_id is null or id < min_id
In all versions, you could use not exists:
select t.*
from mytable t
where not exists (
select 1
from mytable t1
where t1.table2_id = t.table2_id and t1.id <= t.id and t1.type = 'C'
)

How to track previous row status count

I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.

I want find duplicate data? and display duplicate records only?

in my table having data like this
+-----+----------+
| sno | name |
+-----+----------+
| 101 | Raju |
| 102 | Raju |
| 103 | Santhosh |
| 104 | Santhosh |
| 105 | madhavi |
| 106 | suheel |
+-----+----------+
in that i want find dupliacte records and display sno(number) only
for example output should be like this
+-----+
| sno |
+-----+
| 101 |
| 102 |
| 103 |
| 104 |
+-----+
In a Derived table, get all the name values which have duplicates. To do that, we can GROUP BY on name and use HAVING to consider only those names, where COUNT(*) (total number of rows for that name) is more than 1.
Now, we can join back to the main table to get their respective sno values
SELECT
t.sno
FROM your_table t
JOIN (SELECT name
FROM your_table
GROUP BY name
HAVING COUNT(*) > 1) dt
ON dt.name = t.name
Here is a MySQL 8+ way of doing this:
SELECT sno
FROM
(
SELECT t.*, COUNT(*) OVER (PARTITION BY name) cnt
FROM yourTable t
) t
WHERE cnt > 1;
You can try using correlated subquery
select sno from tablename a
where name in (select 1 from tablename b where a.name=b.name having count(name)>1 )

Ranking for unique users

If I have three columns:
id, username, time
My data is:
+-------+------------------+-------------+
| id | username | time |
+-------+------------------+-------------+
| 1 | A | 1 min |
| 2 | A | 2 min |
| 3 | B | 3 min |
| 4 | B | 4 min |
+-------+------------------+-------------+
This query is working to get the ranking:
SELECT time,
FIND_IN_SET(MIN(time), (SELECT GROUP_CONCAT(time ORDER BY time ASC)
FROM table t1)) AS rank
FROM table t2
WHERE t2.username = 'B';
There is only one problem: It returns Rank 3de for the user B instead 2nd.
So I tried to use GROUP BY t2.username and also Distinct t2.username but did not work.
How can I get the rank of THE user B? It should be 2 (Not 3) because we have only 2 users.
E.g.:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,username CHAR(1) NOT NULL
,time TIME NOT NULL
);
INSERT INTO my_table VALUES
(1,'A','00:01:00'),
(2,'A','00:02:00'),
(3,'B','00:03:00'),
(4,'B','00:04:00');
SELECT * FROM my_table;
+----+----------+----------+
| id | username | time |
+----+----------+----------+
| 1 | A | 00:01:00 |
| 2 | A | 00:02:00 |
| 3 | B | 00:03:00 |
| 4 | B | 00:04:00 |
+----+----------+----------+
SELECT *
FROM
( SELECT username
, time
, #i:=#i+1 rank
FROM
( SELECT username
, MIN(time) time
FROM my_table
GROUP
BY username
) x
, (SELECT #i:=0) vars
ORDER
BY time
) n
WHERE username = 'B';
+----------+----------+------+
| username | time | rank |
+----------+----------+------+
| B | 00:03:00 | 2 |
+----------+----------+------+
I think this would work too, but it's slightly hacky, so I'm not sure...
SELECT x.*
, FIND_IN_SET(time,(SELECT GROUP_CONCAT(DISTINCT time ORDER BY time) FROM (SELECT MIN(time) time FROM my_table GROUP BY username) j )) rank
FROM my_table x HAVING rank <> 0 AND username = 'B';

MySQL get ids of the range between two conditions

Let's say I've a table
+----+------------+
| id | condition |
+----+------------+
| 1 | open |
+----+------------+
| 2 | content |
+----+------------+
| 3 | content |
+----+------------+
| 4 | close |
+----+------------+
| 5 | nocontentx |
+----+------------+
| 6 | nocontenty |
+----+------------+
| 7 | open |
+----+------------+
| 8 | content |
+----+------------+
| 9 | close |
+----+------------+
| 10 | nocontentz |
+----+------------+
| 11 | open |
+----+------------+
| 12 | content |
+----+------------+
and want to get a new table where I get the IDs (the first and the last) of the values between "close" and "open". Note that the values between this two conditions are dynamic (I can't search by "nocontent"whatever)
Such as I get this table:
+----+----------+--------+
| id | start_id | end_id |
+----+----------+--------+
| 1 | 5 | 6 |
+----+----------+--------+
| 2 | 10 | 10 |
+----+----------+--------+
Thanks in advance!
http://sqlfiddle.com/#!2/c255c8/2
You can do this using a correlated subquery:
select (#rn := #rn + 1) as id,
id as startid,
(select id
from atable a2
where a2.id > a.id and
a2.condition = 'close'
order by a2.id asc
limit 1
) as end_id
from atable a cross join
(select #rn := 0) vars
where a.condition = 'open';
The working SQL Fiddle is here.
Note this returns the third open as well. If you don't want it, then add having end_id is not null to the end of the query.
EDIT:
If you know the ids are sequential, you can just add and subtract 1 from the above query:
select (#rn := #rn + 1) as id,
id+1 as startid,
(select id
from atable a2
where a2.id > a.id and
a2.condition = 'open'
order by a2.id asc
limit 1
) - 1 as end_id
from atable a cross join
(select #rn := 0) vars
where a.condition = 'close';
You can also do this in a different way, which is by counting the number of open and closes before any given row and using this as a group identifier. The way your data is structured, every other group is what you are looking for:
select grp, min(id), max(id)
from (select t.*,
(select sum(t2.condition in ('open', 'close'))
from t t2
where t2.id <= t.id
) as grp
from t
) t
where t.condition not in ('open', 'close') and
grp % 2 = 0
group by grp;