I have this table in mysql:
name ID
B1 1
B2 1
B3 1
B4 2
B5 2
B6 2
A1 3
A2 3
A3 3
I'd like to add data into a column, notifying that the id will change at the next line. Something like this:
name beforeChange ID
B1 NO 1
B2 NO 1
B3 YES 1
B4 NO 2
B5 NO 2
B6 YES 2
A1 NO 3
A2 NO 3
A3 NO 3
A4 NO 3
Is there any way to do this in sql?
Thanks!
Ugly as sin & probably far from ideal from an efficiency point of view but seems to work:
create table myTable (id int(10) unsigned not null,name varchar(10));
insert into myTable (id,name) values (1,'B1');
insert into myTable (id,name) values (1,'B2');
insert into myTable (id,name) values (1,'B3');
insert into myTable (id,name) values (2,'B4');
insert into myTable (id,name) values (2,'B5');
insert into myTable (id,name) values (2,'B6');
insert into myTable (id,name) values (3,'A1');
insert into myTable (id,name) values (3,'A2');
insert into myTable (id,name) values (3,'A3');
insert into myTable (id,name) values (3,'A4');
select a.id,
case
when b.id is null then 'NO'
when b.id = (select max(id) from myTable) then 'NO' -- < handle last line of results set
else 'YES' end as beforeChange,a.name
from
-- rank within id
(
select mt.id,mt.name,
case mt.id when #curId then #curRow := #curRow + 1
else #curRow := 1 and #curId := mt.id END as rank
from myTable mt
join (select #curRow := 0, #curId := -1) r
order by id,name asc
) a
left outer join
-- max rank by id
(
select t.id,max(t.rank) as maxRank
from (
select mt.*,
case mt.id
when #curId
then #curRow := #curRow + 1
else #curRow := 1 and #curId := mt.id END
as rank
from myTable mt
join (select #curRow := 0, #curId := -1) r
order by id,name asc
) t
group by t.id
) b on a.id = b.id and b.maxRank = a.rank;
Related
I have the following [table a]
id res1 res2
1 a f
1 b f
1 b f
1 c f
2 e g
2 e g
2 e g
2 f g
I'm getting the following after doing a group_concat
select
id,
group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
group_concat(case when cnt = 1 then res2 else concat(cnt, ' ', res2) end) as r2
from
(
select id, res1,res2, count(*) as cnt
from [table a]
group by id, res1,res2
) t
group by id;
id r1 r2
1 a,2 b,c f,2 f,f
2 3 e,f 3 g,g
The res1 column is coming fine BUT res2 column is duplicating the res1 column.
Basically i want to print the value of how many times a character occurs before the character.
.I want in the following format..
id r1 r2
1 a,2 b,c 4 f
2 3 e,f 4 g
How can I achieve it ?
The way I would approach this is to do two rollups/aggregations, using two separate subqueries for the res1 and res2 columns. The first aggregation is over id and res1 (or res2), and obtains the counts for each letter or word. Then, aggregate again, this time only over the id, to obtain a comma separated string for each id. Finally, join these subqueries together to obtain the final result.
SELECT
t1.id, t1.r1, t2.r2
FROM
(
SELECT t.id, GROUP_CONCAT(res1agg ORDER BY res1) AS r1
FROM
(
SELECT
id,
res1,
CASE WHEN COUNT(*) = 1 THEN res1
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res1) END AS res1agg
FROM yourTable
GROUP BY id, res1
) t
GROUP BY t.id
) t1
INNER JOIN
(
SELECT t.id, GROUP_CONCAT(res2agg ORDER BY res2) AS r2
FROM
(
SELECT
id,
res2,
CASE WHEN COUNT(*) = 1 THEN res2
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res2) END AS res2agg
FROM yourTable
GROUP BY id, res2
) t
GROUP BY t.id
) t2
ON t1.id = t2.id;
Output:
Demo here:
Rextester
Just added 2 more conditions in your query without using more inner queries.
Try this:-
input:-
CREATE TABLE Table1 (id INT, res1 varchar(20), res2 varchar(20));
insert into Table1 values(1, 'a', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'c', 'f');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'f', 'g');
Query:-
Select t.id,group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
case when id=1 then trim(concat(sum(case when id = 1 then cnt end),' ',res2))
else trim(concat(sum(case when id = 2 then cnt end),' ',res2)) end as r2
from
(
select id, res1,res2,count(*) as cnt
from table1 a
group by id, res1,res2
) t
group by t.id
My Output:-
id r1 r2
1 a,c,2 b 4 f
2 f,3 e 4 g
Let me know if you have any questions
I want to select data from table
suppose we have a table
table Temp
sequence_number | breakdown_number | physical_account | logical_account | debit_amount | credit_amount
----------------+------------------+------------------+-----------------+--------------+---------------
1 | 1 | 10001 | 10 | 0
2 | 1 | 0011 | 10 | 0
Now I have to select physical_account from 1st row and logical account from second row and insert it into another table in single row based on the breakdown number.
How can I do this ?
I am going to assume that sequence_number actually provides the ordering of the rows and you want to do this for each breakdown_number. The most accurate method is probably to use variables:
INSERT INTO second_table(physical_account, logical_account)
SELECT MAX(CASE WHEN seqnum = 1 THEN t.physical_account END),
MAX(CASE WHEN seqnum = 2 THEN t.logical_account END)
FROM (SELECT t.*,
(#rn := if(#b = t.breakdown_number, #rn + 1,
if(#b := t.breakdown_number, 1, 1)
)
) as seqnum
FROM Temp t CROSS JOIN
(SELECT #rn := 0, #b := -1) params
ORDER BY t.breakdown_number, t.sequence_number
) t
WHERE rn IN (1, 2)
GROUP BY t.breakdown_number;
If the sequence_number restarts at 1 for each breakdown_number, then the subquery and variables are not needed:
INSERT INTO second_table(physical_account, logical_account)
SELECT MAX(CASE WHEN t.sequence_number = 1 THEN t.physical_account END),
MAX(CASE WHEN t.sequence_number = 2 THEN t.logical_account END)
FROM Temp t
WHERE t.sequence_number IN (1, 2)
GROUP BY t.breakdown_number;
Finally, in some cases, you can just use a hack:
INSERT INTO second_table(physical_account, logical_account)
SELECT SUBSTRING_INDEX(GROUP_CONCAT(t.physical_account), ',', 1),
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(t.logical_account), ',', 2), ',', -1)
FROM Temp t
WHERE t.sequence_number IN (1, 2)
GROUP BY t.breakdown_number;
Notes about this approach:
It converts the accounts to strings, if they are of some other time.
group_concat() has a (configurable) maximum length, so if there are many records for a given breakdown_number, then you can get a run-time error.
You can use a sub query in the select with LIMIT OFFSET:
INSERT INTO second_table (physical_account, logical_account)
SELECT t.physical_account,
(SELECT s.logical_account FROM temp s
ORDER BY s.breakdown_number
LIMIT 1,1)
FROM Temp t
ORDER BY t.breakdown_number
LIMIT 1
This will select the first and second values based on breakdown_number on ACSENDING order.
DECLARE #physical_account varchar(30); /*Data Type as required*/
DECLARE #logical_account varchar(30);
SELECT #physical_account=physical_account FROM Temp WHERE logical_account=NULL AND physical_account='10001'
SELECT #logical_account=logical_account FROM Temp WHERE logical_account='0011' AND physical_account=NULL
INSERT INTO Table_New(physical_account, logical_account) VALUES(#physical_account, #logical_account);
I have a table
email(email varchar(30),id integer(10),duplicated varchar(10))
with records
sai#gmail.com 101 null
kiran#gmail.com 102 null
sai123#gmail.com 103 null
sai#gmail.com 101 null
kiran#gmail.com 102 null
Now my question is i need to get "yes" in the duplicated column for the two duplicated records for the second time. so, the output table should be
sai#gmail.com 101 null
kiran#gmail.com 102 null
sai123#gmail.com 103 null
sai#gmail.com 101 yes
kiran#gmail.com 102 yes
Try this
update email set duplicated =
(case when (select count(*) from email x where x.email = e.email) > 1 then "yes" else null)
edited: this will update table
You can try this query for viewing:
select numerated.email, numerated.id, (case when cnt=1 OR numerated.rnum=grouped.min_rnum then null else "yes" end) as duplicated
from
(select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c order by id) as numerated
left join
(select email, id, min(rnum) as min_rnum, count(rnum) as cnt
from (select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c order by id) as numerated
group by email, id
) as grouped
on numerated.email=grouped.email and numerated.id=grouped.id
order by id;
Could you explain your situation in details? It looks like it needs another solution, not just SELECT query.
And try this one for updating:
update email u, (select #i:=0) urnum
set
id = id + (#i:=#i + 1) - #i,
duplicated = (
select duplicated from (
select
numerated.email,
numerated.id,
(case when cnt=1 OR numerated.rnum=grouped.min_rnum then null else "yes" end) as duplicated,
rnum
from
(select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c ) as numerated
left join
(select email, id, min(rnum) as min_rnum, count(rnum) as cnt
from (select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c ) as numerated
group by email, id
) as grouped
on numerated.email=grouped.email and numerated.id=grouped.id
order by rnum
) found_duplicates
where u.email=found_duplicates.email and u.id=found_duplicates.id and #i=found_duplicates.rnum
limit 1
)
;
It looks like it works, but you shouldn't rely on it.
If it is possible, you should do any of this:
1. change table structure - add unique field
2. change table filling logic - check uniqueness before inserting new row and insert it with proper 'duplicates' field value;
3. repopulate via temporary table like this:
CREATE TEMPORARY TABLE tmp_email AS <... 'SELECT' version of my query ...>;
TRUNCATE TABLE email;
INSERT INTO email SELECT * FROM tmp_email;
I want to give number to occurrence in sql result
for example I have table like this:
ID| Name | Login Date
1 |John | 12/1/2013
2 |John | 15/1/2013
3 |Ben | 12/2/2013
Now I want additional column to know what times they log in:
ID| Name | Visit times
1 |John |1
2 |John |2
3 |Ben |1
SELECT t1.ID
, t1.Name
(
SELECT COUNT(t2.ID)
FROM tablename AS t2
WHERE t1.Name = t2.Name
and t2.ID <= t1.ID
) AS 'Visit times'
FROM tablename AS t1;
Take a look at the example below.
create table log(id integer, name varchar(100), time datetime);
insert into log values (1, 'John', '2013-01-12 00:00:00');
insert into log values (2, 'John', '2013-01-15 00:00:00');
insert into log values (3, 'Ben', '2013-02-12 00:00:00');
SELECT #row_num := IF(#prev_value=l.name,#row_num+1,1) AS RowNumber
,#prev_value := l.name
FROM log l,
(SELECT #row_num := 1) x,
(SELECT #prev_value := '') y
ORDER BY l.name, l.time DESC
your problem has been solved in this so post.
adapted to your tables:
CREATE TABLE IF NOT EXIST staging_table (
id
, name
, visit_sequence
)
SELECT id
, name
, (
CASE name
WHEN #curType THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curName := name
END
) + 1 AS visit_sequence
, login_date
FROM your_table
, (SELECT #curRow := 0, #curNamee := '') r
ORDER BY name ASC
, login_date ASC
;
ALTER TABLE your_table ADD visit_sequence INTEGER AFTER login_date;
UPDATE your_table yt
, staging_table st
SET yt.visit_sequence = st.visit_sequence
WHERE st.id = yt.id
;
Is there any way I can get the actual row number from a query?
I want to be able to order a table called league_girl by a field called score; and return the username and the actual row position of that username.
I'm wanting to rank the users so i can tell where a particular user is, ie. Joe is position 100 out of 200, i.e.
User Score Row
Joe 100 1
Bob 50 2
Bill 10 3
I've seen a few solutions on here but I've tried most of them and none of them actually return the row number.
I have tried this:
SELECT position, username, score
FROM (SELECT #row := #row + 1 AS position, username, score
FROM league_girl GROUP BY username ORDER BY score DESC)
As derived
...but it doesn't seem to return the row position.
Any ideas?
You may want to try the following:
SELECT l.position,
l.username,
l.score,
#curRow := #curRow + 1 AS row_number
FROM league_girl l
JOIN (SELECT #curRow := 0) r;
The JOIN (SELECT #curRow := 0) part allows the variable initialization without requiring a separate SET command.
Test case:
CREATE TABLE league_girl (position int, username varchar(10), score int);
INSERT INTO league_girl VALUES (1, 'a', 10);
INSERT INTO league_girl VALUES (2, 'b', 25);
INSERT INTO league_girl VALUES (3, 'c', 75);
INSERT INTO league_girl VALUES (4, 'd', 25);
INSERT INTO league_girl VALUES (5, 'e', 55);
INSERT INTO league_girl VALUES (6, 'f', 80);
INSERT INTO league_girl VALUES (7, 'g', 15);
Test query:
SELECT l.position,
l.username,
l.score,
#curRow := #curRow + 1 AS row_number
FROM league_girl l
JOIN (SELECT #curRow := 0) r
WHERE l.score > 50;
Result:
+----------+----------+-------+------------+
| position | username | score | row_number |
+----------+----------+-------+------------+
| 3 | c | 75 | 1 |
| 5 | e | 55 | 2 |
| 6 | f | 80 | 3 |
+----------+----------+-------+------------+
3 rows in set (0.00 sec)
SELECT #i:=#i+1 AS iterator, t.*
FROM tablename t,(SELECT #i:=0) foo
Here comes the structure of template I used:
select
/*this is a row number counter*/
( select #rownum := #rownum + 1 from ( select #rownum := 0 ) d2 )
as rownumber,
d3.*
from
( select d1.* from table_name d1 ) d3
And here is my working code:
select
( select #rownum := #rownum + 1 from ( select #rownum := 0 ) d2 )
as rownumber,
d3.*
from
( select year( d1.date ), month( d1.date ), count( d1.id )
from maindatabase d1
where ( ( d1.date >= '2013-01-01' ) and ( d1.date <= '2014-12-31' ) )
group by YEAR( d1.date ), MONTH( d1.date ) ) d3
You can also use
SELECT #curRow := ifnull(#curRow,0) + 1 Row, ...
to initialise the counter variable.
Assuming MySQL supports it, you can easily do this with a standard SQL subquery:
select
(count(*) from league_girl l1 where l2.score > l1.score and l1.id <> l2.id) as position,
username,
score
from league_girl l2
order by score;
For large amounts of displayed results, this will be a bit slow and you will want to switch to a self join instead.
If you just want to know the position of one specific user after order by field score, you can simply select all row from your table where field score is higher than the current user score. And use row number returned + 1 to know which position of this current user.
Assuming that your table is league_girl and your primary field is id, you can use this:
SELECT count(id) + 1 as rank from league_girl where score > <your_user_score>
I found the original answer incredibly helpful but I also wanted to grab a certain set of rows based on the row numbers I was inserting. As such, I wrapped the entire original answer in a subquery so that I could reference the row number I was inserting.
SELECT * FROM
(
SELECT *, #curRow := #curRow + 1 AS "row_number"
FROM db.tableName, (SELECT #curRow := 0) r
) as temp
WHERE temp.row_number BETWEEN 1 and 10;
Having a subquery in a subquery is not very efficient, so it would be worth testing whether you get a better result by having your SQL server handle this query, or fetching the entire table and having the application/web server manipulate the rows after the fact.
Personally my SQL server isn't overly busy, so having it handle the nested subqueries was preferable.
I know the OP is asking for a mysql answer but since I found the other answers not working for me,
Most of them fail with order by
Or they are simply very inefficient and make your query very slow for a fat table
So to save time for others like me, just index the row after retrieving them from database
example in PHP:
$users = UserRepository::loadAllUsersAndSortByScore();
foreach($users as $index=>&$user){
$user['rank'] = $index+1;
}
example in PHP using offset and limit for paging:
$limit = 20; //page size
$offset = 3; //page number
$users = UserRepository::loadAllUsersAndSortByScore();
foreach($users as $index=>&$user){
$user['rank'] = $index+1+($limit*($offset-1));
}