I want to write a query that will update duplicates per group:
INPUT
+-------+-------+
| group | name |
+-------+-------+
| 1 | name1 |
| 1 | name1 |
| 1 | name1 |
| 1 | name2 |
| 2 | name1 |
| 2 | name1 |
| 3 | name1 |
| 3 | name2 |
+-------+-------+
OUTPUT
+-------+----------------+
| group | name |
+-------+----------------+
| 1 | name1 |
| 1 | name1 - Copy 1 |
| 1 | name1 - Copy 2 |
| 1 | name2 |
| 2 | name1 |
| 2 | name1 - Copy 1 |
| 3 | name1 |
| 3 | name2 |
+-------+----------------+
There is something like that here Renaming the duplicate data in sql but my problem is how to deal with groups.
It is not so important how to name this duplicates but it will be cool if I can do it as specified in my example.
If you have a primary key id column, then try this:
update (
select `group`, name, min(id) as min_id
from test
group by `group`, name
) x
join test t using (`group`, name)
set t.name =
case when t.id <> x.min_id
then concat(t.name, ' - Copy ', t.id - x.min_id)
else t.name
end
;
Demo: http://rextester.com/AWEX77086
Here is another way, which is probably slower, but will guarantee consecutive copy numbers.
update (
select l.id, count(*) as copy_nr
from test l
join test r
on r.group = l.group
and r.name = l.name
and r.id < l.id
group by l.id
) x
join test t using (id)
set t.name = concat(t.name, ' - Copy ', x.copy_nr);
Demo: http://rextester.com/NWSF57017
Try the below, replace Groups, with Group.
Ok, I was pointed out that this is a MySQL question, so the below wont work for MySQL, but only for t-sql.
SELECT Groups,
CASE WHEN Duplicate > 1
THEN Name + ' - Copy ' + CONVERT(VARCHAR(10), Duplicate)
ELSE Name
END AS Name
FROM
(
SELECT Groups,
Name,
ROW_NUMBER() OVER(PARTITION BY Name, Groups ORDER BY Name ) As Duplicate
FROM TableName
) AS Data
ORDER BY Groups
This is untested, but you can similate the LAG function seen in many other RDBMS' with variables (more info here).
The idea is you store the group and name fields and compare against them before updating them.
SET #RowNumber = 0;
SET #PreviousGroup = NULL;
SET #PreviousName = NULL;
SELECT
#PreviousGroup AS PreviousGroup,
#PreviousName AS PreviousName,
CASE
WHEN #PreviousGroup = `group` AND #PreviousName = `name` THEN (#RowNumber := #RowNumber + 1)
ELSE #RowNumber := 0
END AS `Counter`,
CASE
WHEN #PreviousGroup = `group` AND #PreviousName = `name` THEN CONCAT(`name`,'- Copy ',#RowNumber)
ELSE `name`
END AS `Name`,
#PreviousGroup := `group` AS RawGroup,
#PreviousName := `name` AS RawName
FROM
tbl1
ORDER BY
`group` ASC,
`name` ASC;
You can do this using variables. I would recommend:
set #i = 0;
set #gn := '';
update t
set name = concat_ws(' - Copy ', name,
nullif(if(#gn = concat_ws(':', group, name), #i := #i + 1,
if(#gn := concat_ws(':', group, name), #i := 1, #i := 1)
), 0)
order by t.group, name;
Related
I have in my Moodle db table for every session sessid and timestart. The table looks like this:
+----+--------+------------+
| id | sessid | timestart |
+----+--------+------------+
| 1 | 3 | 1456819200 |
| 2 | 3 | 1465887600 |
| 3 | 3 | 1459839600 |
| 4 | 2 | 1457940600 |
| 5 | 2 | 1460529000 |
+----+--------+------------+
How to get for every session the first date from the timestamps in SQL?
You can easy use this:
select sessid,min(timestart) FROM mytable GROUP by sessid;
And for your second question, something like this:
SELECT
my.id,
my.sessid,
IF(my.timestart = m.timestart, 'yes', 'NO' ) AS First,
my.timestart
FROM mytable my
LEFT JOIN
(
SELECT sessid,min(timestart) AS timestart FROM mytable GROUP BY sessid
) AS m ON m.sessid = my.sessid;
Try this.
SELECT
*
FROM
tbl
WHERE
(sessid, timestart) IN (
SELECT tbl2.sessid, MIN(tbl2.timestart)
FROM tbl tbl2
WHERE tbl.sessid = tbl2.sessid
);
Query
select sessid, min(timestart) as timestart
from your_table_name
group by sessid;
Just an other perspective if you need even the id.
select t.id, t.sessid, t.timestart from
(
select id, sessid, timestart,
(
case sessid when #curA
then #curRow := #curRow + 1
else #curRow := 1 and #curA := sessid end
) as rn
from your_table_name t,
(select #curRow := 0, #curA := '') r
order by sessid,id
)t
where t.rn = 1;
I got table orders and order_comments. Each order can have from 0 to n comments. I would like to get list of all orders with their comments in a sepcific order.
Table orders:
order_id | order_nr
1 | 5252
4 | 6783
5 | 6785
Table order_comments
id_order_comments | order_fk | created_at | email | content
1 | 4 | 2015-01-12 | jack | some text here
2 | 5 | 2015-01-13 | marta | some text here
3 | 5 | 2015-01-14 | beata | some text here
4 | 4 | 2015-01-16 | julia | some text here
As a result, I would like to get 1 row for each order. Comments should be shown in separate columns, starting from the oldest comment. So desired output in this case is:
order_id | 1_comment_created_at | 1_comment_author | 1_comment_content | 2_comment_created_at | 2_comment_author | 2_comment_content
1 | NULL | NULL | NULL | NULL | NULL | NULL
4 | 2015-01-12 | jack | some text here | 2015-01-16 | Julia | some text here
5 | 2015-01-13 | marta | some text here | 2015-01-14 | beata | some text here
I found this: MySQL - Rows to Columns - but I cannot use 'create view'.
I found this: http://dev.mysql.com/doc/refman/5.5/en/while.html - but I cannot create procedure in this db.
What I got:
SELECT #c := (SELECT count(*) FROM order_comments GROUP BY order_fk ORDER BY count(*) DESC LIMIT 1);
SET #rank=0;
SET #test=0;
SELECT
CASE WHEN #test < #c AND temp.comment_id = #test THEN temp.created_at END AS created,
CASE WHEN #test < #c AND temp.comment_id = #test THEN temp.author END AS author,
CASE WHEN #test < #c AND temp.comment_id = #test THEN temp.content END AS content
/*But I cannot set #test as +1. And I cannot name column with variable - like CONCAT(#test, '_created')*/
FROM (
SELECT #rank := #rank +1 AS comment_id, created_at, author, content
FROM order_comments
WHERE order_fk = 4
ORDER BY created_at
) AS temp
Problem: I would like to search more than 1 order. I should get orders with no comments too.
What can I do?
You can use variables for this type of pivot, but the query is a bit more complicated, because you need to enumerate the values for each order:
SELECT o.order_id,
MAX(case when rank = 1 then created_at end) as created_at_1,
MAX(case when rank = 1 then email end) as email_1,
MAX(case when rank = 1 then content end) as content_1,
MAX(case when rank = 2 then created_at end) as created_at_2,
MAX(case when rank = 2 then email end) as email_2,
MAX(case when rank = 2 then content end) as content_2,
FROM orders o LEFT JOIN
(SELECT oc.*,
(#rn := if(#o = order_fk, #rn + 1,
if(#o := order_fk, 1, 1)
)
) as rank
FROM order_comments oc CROSS JOIN
(SELECT #rn := 0, #o := 0) vars
ORDER BY order_fk, created_at
) oc
ON o.order_id = oc.order_fk
GROUP BY o.order_id;
My table
+------+-------+--------+
| NAME | MARKS | POINTS |
+------+-------+--------+
| S1 | 53 | (null) |
| S2 | 55 | (null) |
| S3 | 56 | (null) |
| S4 | 55 | (null) |
| S5 | 52 | (null) |
| S6 | 51 | (null) |
| S7 | 53 | (null) |
+------+-------+--------+
Refer : http://www.sqlfiddle.com/#!2/5d046/1
I would like to add 3,2,1 points to the highest Marks. Here S3 goes to 3 points, S2,S4 goes to 2 points and S1,S7 goes to 1 points.
Final outputs looks,
+------+-------+--------+
| NAME | MARKS | POINTS |
+------+-------+--------+
| S1 | 53 | 1 |
| S2 | 55 | 2 |
| S3 | 56 | 3 |
| S4 | 55 | 2 |
| S5 | 52 | 0 |
| S6 | 51 | 0 |
| S7 | 53 | 1 |
+------+-------+--------+
Plz help
My suggestion is that you first calculate the ranking of each mark, and then use that in a case statement in an update.
The following query shows one way to calculate the ranking:
select t.*,
#rn := if(#marks = marks, #rn, #rn + 1) as ranking,
#marks := marks
from myTable t cross join
(select #rn := 0, #marks := -1) const
order by t.marks desc;
(As a note: I am a bit uncomfortable with this method, because MySQL does not guarantee the order of evaluation of the two expressions with constants. If #marks were set before #rn, then it wouldn't work. In practice, that does not seem to happen. And, this is more efficient that the equivalent with a correlated subquery.)
You can then put this into an update using join:
update myTable join
(select t.*,
#rn := if(#marks = marks, #rn, #rn + 1) as ranking,
#marks := marks
from myTable t cross join
(select #rn := 0, #marks := -1) const
order by t.marks desc
) mr
on myTable.Name = mr.Name
set myTable.Points := (case when mr.ranking = 1 then 3
when mr.ranking = 2 then 2
when mr.ranking = 3 then 1
else 0
end);
This has been tested on your SQL Fiddle.
You can do it via variables (see samples in other answers), or via case:
select
myTable.*,
case
when max1.marks is not null then 3
when max2.marks is not null then 2
when max3.marks is not null then 1
else 0
end as score
from
myTable
LEFT JOIN
(select marks from myTable order by marks desc limit 1) AS max1
ON myTable.marks=max1.marks
LEFT JOIN
(select marks from myTable order by marks desc limit 2,1) AS max2
ON myTable.marks=max2.marks
LEFT JOIN
(select marks from myTable order by marks desc limit 3,1) AS max3
ON myTable.marks=max3.marks;
the demo can be found here.
UPDATE myTable t1
INNER JOIN
(
SELECT #row:=#row-1 AS RowPoints, Marks
FROM (
SELECT Marks
FROM myTable
GROUP BY Marks
ORDER BY Marks DESC
LIMIT 3
) AS TopMarks
INNER JOIN (SELECT #row:=4) AS RowInit
) AS AddPoints ON t1.Marks = AddPoints.Marks
SET Points = COALESCE(Points, 0) + AddPoints.RowPoints;
This should work just fine. You should and and index on the Marks column.
The simplest way to do this:
SELECT t.Name Name, t.Marks Marks,
(CASE WHEN Marks = (Select max(marks) from mytable) THEN 3 ELSE 0 END+
CASE WHEN Marks = (Select min(marks) from (Select distinct marks
from mytable order by marks desc limit 2) a) THEN 2 ELSE 0 END+
CASE WHEN Marks = (Select min(marks) from (Select distinct marks
from mytable order by marks desc limit 3) b) THEN 1 ELSE 0 END)
AS `Points`
FROM mytable t;
SQL Fiddle
The current table looks something like this:
[id | section | order | thing]
[1 | fruits | 0 | apple]
[2 | fruits | 0 | banana]
[3 | fruits | 0 | avocado]
[4 | veggies | 0 | tomato]
[5 | veggies | 0 | potato]
[6 | veggies | 0 | spinach]
I'm wondering how to make the table look more like this:
[id | section | order | thing]
[1 | fruits | 1 | apple]
[2 | fruits | 2 | banana]
[3 | fruits | 3 | avocado]
[4 | veggies | 1 | tomato]
[5 | veggies | 2 | potato]
[6 | veggies | 3 | spinach]
"order" column updated to a sequential number, starting at 1, based on "section" column and "id" column.
You can do this with an update by using a join. The second table to the join calculates the ordering, which is then used for the update:
update t join
(select t.*, #rn := if(#prev = t.section, #rn + 1, 1) as rn
from t cross join (select #rn := 0, #prev := '') const
) tsum
on t.id = tsum.id
set t.ordering = tsum.rn
You don't want to do this as an UPDATE, as that will be really slow.
Instead, do this on INSERT. Here's a simple one-line INSERT that will grab the next order number and inserts a record called 'kiwi' in the section 'fruits'.
INSERT INTO `table_name` (`section`, `order`, `thing`)
SELECT 'fruits', MAX(`order`) + 1, 'kiwi'
FROM `table_name`
WHERE `section` = `fruits`
EDIT: You could also do this using an insert trigger, e.g.:
DELIMITER $$
CREATE TRIGGER `trigger_name`
BEFORE INSERT ON `table_name`
FOR EACH ROW
BEGIN
SET NEW.`order` = (SELECT MAX(`order`) + 1 FROM `table_name` WHERE `section` = NEW.`section`);
END$$
DELIMITER ;
Then you could just insert your records as usual, and they will auto-update the order value.
INSERT INTO `table_name` (`section`, `thing`)
VALUES ('fruits', 'kiwi')
Rather than storing the ordering, you could derive it:
SELECT t.id
,t.section
,#row_num := IF (#prev_section = t.section, #row_num+1, 1) AS ordering
,t.thing
,#prev_section := t.section
FROM myTable t
,(SELECT #row_num := 1) x
,(SELECT #prev_value := '') y
ORDER BY t.section, t.id
Note that order is a keyword and is therefore not the greatest for a column name. You could quote the column name or give it a different name...
I have some data in database:
id user
1 zhangsan
2 zhangsan
3 zhangsan
4 lisi
5 lisi
6 lisi
7 zhangsan
8 zhangsan
I want keep order, and combine near same user items, how to do it?
When I use shell script, I will(data in file test.):
cat test|cut -d " " -f2|uniq -c
this will get result as:
3 zhangsan
3 lisi
2 zhangsan
But how to do it use sql?
If you try:
SET #name:='',#num:=0;
SELECT id,
#num:= if(#name = user, #num, #num + 1) as number,
#name := user as user
FROM foo
ORDER BY id ASC;
This gives:
+------+--------+------+
| id | number | user |
+------+--------+------+
| 1 | 1 | a |
| 2 | 1 | a |
| 3 | 1 | a |
| 4 | 2 | b |
| 5 | 2 | b |
| 6 | 2 | b |
| 7 | 3 | a |
| 8 | 3 | a |
+------+--------+------+
So then you can try:
SET #name:='',#num:=0;
SELECT COUNT(*) as count, user
FROM (
SELECT #num:= if(#name = user, #num, #num + 1) as number,
#name := user as user
FROM foo
ORDER BY id ASC
) x
GROUP BY number;
Which gives
+-------+------+
| count | user |
+-------+------+
| 3 | a |
| 3 | b |
| 2 | a |
+-------+------+
(I called my table foo and also just used names a and b because I was too lazy to write zhangsan and lisi over and over).
if in oracle, you can do like below.
SELECT NAME,
num - lagnum
FROM (SELECT lagname,
NAME,
num,
nvl(lag(num) over(ORDER BY num), 0) lagnum
FROM (SELECT id,
lag(NAME) over(ORDER BY ID) lagname,
NAME,
lead(NAME) over(ORDER BY ID) leadname,
ROWNUM num
FROM (SELECT * FROM test ORDER BY ID))
WHERE (lagname = NAME AND (NAME <> leadname OR leadname IS NULL))
OR (lagname IS NULL AND NAME <> leadname)
OR (lagname <> NAME AND leadname IS NULL)
ORDER BY ID);
if in sql server, oracle, db2...
with x as(
select c.*, rn = row_number() over (order by c.id)
from test c
left join test n
on c.[user] = n.[user]
and c.[id] + 1 = n.[id]
where n.id is null
)
select a.[user], a.id - coalesce(b.id, 0)
from x a
left join x b
on a.rn = b.rn + 1
I think what you are looking for is to COUNT(ID):
SELECT COUNT(ID) FROM table GROUP BY user
You cannot do this in sql without doing some sort of sequential (iterative) analysis. Remember sql is set operation language.
A little improvement to the selected answer would be not to have to define those variables. So this query can be solved in just a single statement:
SELECT COUNT(*) cnt, user
FROM (
SELECT #num := #num + (#name != user) as number,
#name := user as user
FROM t, (select #num := 0, #name := '') as s
ORDER BY id
) x
GROUP BY number
Output:
| CNT | USER |
|-----|----------|
| 3 | zhangsan |
| 3 | lisi |
| 2 | zhangsan |
Fiddle here