Currently, I have a task: for the presented table, to change students' order by replacing odd and even sequence (example below). There is only one condition, if the number of students is odd, that last number should not be changed. I currently wrote a code like this, however, for me, it seems pretty clumsy. How different and more efficient should this code be written?
CREATE TABLE student (id int, name varchar(128));
INSERT INTO student (id,name) VALUES
(1,'Aurimas'),
(2,'Darius'),
(3,'Eligijus'),
(4,'Giedrius'),
(5,'Justinas');
SELECT CASE
WHEN mod((SELECT id FROM student ORDER BY id DESC LIMIT 1),2) = 0 THEN
CASE
WHEN mod(id, 2) = 0 THEN id-1
ELSE id=id+1
END
ELSE
CASE
WHEN mod(id, 2) = 0 AND id <> (SELECT id FROM student ORDER BY id DESC LIMIT 1) THEN id-1
WHEN mod(id, 2) = 1 AND id <> (SELECT id FROM student ORDER BY id DESC LIMIT 1) THEN id+1
ELSE id
END
END AS new_id, name
FROM student
ORDER BY new_id ASC;
I have this:
id name
1 Aurimas
2 Darius
3 Eligijus
4 Giedrius
5 Justinas
And it should look like this:
id name
1 Darius
2 Aurimas
3 Giedrius
4 Eligijus
5 Justinas
You could group your ids by the value of (id+1) DIV 2. Then all you have to do is swap the minimum id with the maximum id in each pair of rows and make sure you handle the case when there are odd number of rows properly.
Something like this:
set #maxId = (select max(id) from student);
select
if (
#maxId = id and mod(id, 2) != 0,
id,
if (
mod((id+1), 2) = 0,
id+1,
id-1
)
) as id,
name
from student
order by id;
db<>fiddle
I'm not sure how you're defining 'efficient', but how about...
select ROW_NUMBER() OVER (
ORDER BY ceiling(id/2), id desc
) new_id,name from student
The ROW_NUMBER function has been supported in MySQL since version 8.0.
EDIT: I suppose something like will satisfy the quibblers...
WITH cte (id,n) AS (SELECT id, ROW_NUMBER() OVER (ORDER BY id) n FROM student)
SELECT x.name
, ROW_NUMBER() OVER (ORDER BY CEILING(cte.n/2), x.id desc) new_id
FROM student x
JOIN cte
ON cte.id = x.id;
I would recommend to use the below query because
it has many advantages here I am calculating only one max id select but in your case for every case, one select will execute this will reduce that time and only one max id will be used for the odd case
your odd and even operation is the same but in odd you are skipping last record so I added that condition
SET #MAXID = (SELECT MAX(ID) FROM STUDENT);
SELECT
CASE
WHEN MOD(#MAXID,2) != 0 AND ID=#MAXID THEN ID
ELSE CASE WHEN MOD(ID, 2) = 0 THEN ID-1 ELSE ID+1 END
END AS NEW_ID, NAME
FROM STACK_USER.STUDENT
ORDER BY NEW_ID ASC;
I think this would be useful in your case I have checked every condition in my local it will work properly
Screen Shot For Better Understanding:
Related
I am working adjacent id exchange problem. I found one solution, it partly works.
SELECT tmp.id, tmp.student FROM
(
SELECT id-1 AS id, student FROM seat WHERE id%2 = 0 -- even id -1
UNION
SELECT id+1 AS id, student FROM seat WHERE id%2 = 1 -- odd id +1
) tmp
ORDER BY tmp.id
I know the basic idea, but still confuse to the syntax or expression. I am wondering where the tmp comes from? Is the other way to use Alias in SQL?
IN the sample the tmp alias is changed in my_table_alias
SELECT my_table_alias.id, my_table_alias.student FROM
(
SELECT id-1 AS id, student FROM seat WHERE id%2 = 0 -- even id -1
UNION
SELECT id+1 AS id, student FROM seat WHERE id%2 = 1 -- odd id +1
) my_table_alias
ORDER BY my_table_alias.id
This is introducued by the select ,,,, FROM ( subquery ) my_table_alias sintax
when you use a FROM( subquery ) you need a my_table_alias ..
The table alias for then FROM(subquery) in mandatory If you want refer to the column coming from the FROM(subquery) in outer part of query
You can just use a case expression:
SELECT (CASE WHEN s.id % 2 = 0 THEN s.id - 1 ELSE s.id + 1
END) AS id,
s.student
FROM seat s;
UNION is definitely not the right way to approach this problem.
Note that the s plays the same role as tmp in your question. It is a table alias that names a table or subquery in the from clause.
Is there a way to rewrite this query without getting error?: Subquery returned more than 1 value.
This is query is used in a LEFT JOIN in a table-valued function. Per requirement, I need to by default pull two scenario IDs (if parameter value is NULL or empty)
DECLARE #pScenarioName AS VARCHAR(30)
select
externalID,
PropertyAssetId,
LeaseID,
BeginDate
from ae11.dbo.ivw_Leases
WHERE PropertyAssetID IN
(select ID from AE11.dbo.PropertyAssets where scenarioID IN
(CASE WHEN isnull(#pScenarioName, '') = ''
THEN (select top 2 ID from rvw_Scenarios where Name like '[0-9][0-9][0-9][0-9]%'
AND LEN(Name) = 8
order by Name desc)
ELSE
(select ID from aex.dbo.rvw_Scenarios
where [Name] IN (#pScenarioName))
END)
)
I haven't tested this, but I use a similar approach when dealing with parameters. Of course, this won't necessarily work if the order of the ID is crucial in your second subquery.
SELECT ExternalID
,PropertyAssetId
,LeaseID
,BeginDate
FROM ae11.dbo.ivw_Leases
WHERE PropertyAssetID IN
(SELECT ID
FROM AE11.dbo.PropertyAssets
WHERE scenarioID IN
(SELECT TOP 2 ID
FROM rvw_Scenarios
WHERE (#ISNULL(#pScenarioName,'') = ''
AND Name LIKE '[0-9][0-9][0-9][0-9]%'
AND LEN(Name) = 8)
ORDER BY Name DESC
UNION ALL
SELECT ID FROM aex.dbo.rvw_Scenarios
WHERE (#pScenarioName IS NOT NULL)
AND [Name] IN (#pScenarioName)))
my DB has this structure:
ID | text | time | valid
This is my current code. I'm trying to find a way to do this as one query.
rows = select * from table where ID=x order by time desc;
n=0;
foreach rows{
if(n > 3){
update table set valid = -1 where rows[n];
}
n++
}
I'm checking how many rows exist for a given ID. Then I need to set valid=-1 for all rows where n >3;
Is there a way to do this with one query?
You can use a subquery in the WHERE clause, like this:
UPDATE table
SET valid=-1
WHERE (
SELECT COUNT(*)
FROM table tt
WHERE tt.time > table.time
AND tt.ID = table.ID
) > 3
The subquery counts the rows with the same ID and a later time. This count will be three or less for the three latest rows; the remaining ones would have a greater count, so their valid field would be updated.
Assuming that (id,time) has a UNIQUE constraint, i.e. no two rows have the same id and same time:
UPDATE
tableX AS tu
JOIN
( SELECT time
FROM tableX
WHERE id = #X -- the given ID
ORDER BY time DESC
LIMIT 1 OFFSET 2
) AS t3
ON tu.id = #X -- given ID again
AND tu.time < t3.time
SET
tu.valid = -1 ;
update table
set valid = -1
where id in (select id
from table
where id = GIVEN_ID
group by id
having count(1) >3)
Update: I really like dasblinkenlight's solution because is very neat, but I wanted to try also to do it in my way, a quite verbose one:
update Table1
set valid = -1
where (id, time) in (select id,
time
from (select id,time
from table1
where id in (select id
from table1
group by id
having count(1) >3)
-- and id = GIVEN_ID
order by time
limit 3, 10000000)
t);
Also in SQLFiddle
to do it for all ids, or only for one if you set a where in the a subquery
UPDATE TABLE
LEFT JOIN (
SELECT *
FROM (
SELECT #rn:=if(#prv=id, #rn+1, 1) AS rId,
#prv:=id AS id,
TABLE.*
FROM TABLE
JOIN ( SELECT #prv:=0, #rn:=0 ) tmp
ORDER BY id, TIMESTAMP
) a
WHERE rid > 3
) ordered ON ordered.id = TABLE.id
AND ordered.TIMESTAMP = TABLE.TIMESTAMP
AND ordered.text = TIMESTAMP.text
SET VALID = -1
WHERE rid IS NOT NULL
I have a table :
ID | time
1 | 300
1 | 100
1 | 200
2 | 200
2 | 500
I want to get 2nd row for every ID
I know that I can get 1st row as
select ID,time from T group by ID;
But I don't know about how to get 2nd row for every ID.
I know about limit and offset clause in mysql, but can't figure out how to use them here.
How can I do it ?
EDIT : Actually, time is not ordered. I forgot to specify that. I have made an edit in the table.
i have just an idee how to make it but i couldnt fix it , maybe you can fix it. any suggest is appreciated to correct my query
first this to select the first row of each id.
SELECT min(id) id
FROM TableName t2
group by id
then select the min(id) which are not in the first query to select to min(id) (which is second row)
like that
SELECT min(id) id ,time
FROM TableName
WHERE id NOT IN (
SELECT min(id) id
FROM TableName
GROUP BY id
)
GROUP BY id
** as i said its just suggest . it returns me 0 values.if u fix it let me edit my post to be helpful
here a demo
SELECT ID, MAX(time) time
FROM
(
select ID, Time
from TableName a
where
(
select count(*)
from TableName as f
where f.ID = a.ID and f.time <= a.time
) <= 2
) s
GROUP BY ID
SQLFiddle Demo
SELECT x.*
FROM test x
JOIN test y
ON y.id = x.id
AND y.time >= x.time
GROUP
BY id,time
HAVING COUNT(*) = n;
Note that any entries with less than n results will be omitted
You cannot do this with the tables that you have. You could make a valiant attempt with:
select id, time
from (select id, time
from t
group by t
) t
where not exists (select 1 from t t2 where t2.id = t.id and t2.time = t.time)
group by id
That is, attempt to filter out the first row.
The reason this is not possible is because tables are inherently unordered, so there is not real definition of "second" in your tables. This gives the SQL engine the opportunity to rearrange the rows as it sees fit during processing -- which can result in great performance gains.
Even the construct that you are using:
select id, time
from t
group by id
is not guaranteed to return time from the first row. This is a (mis)feature of MySQL called Hidden Columns. It is really only intended for the case where all the values are the same. I will admit that in practice it seems to get the value from the first row, but you cannot guarantee that.
Probably your best solution is to select the data into a new table that has an auto-incrementing column:
create table newtable (
autoid int auto_increment,
id int,
time int
);
insert into newtable(id, time)
select id, time from t;
In practice, this will probably keep the same order as the original table, and you can then use the autoid to get the second row. I want to emphasize, though, the "in practice". There is no guarantee that the values are in the correct order, but they probably will be.
DECLARE topScorer INT default 0;
SELECT id INTO topScorer FROM game_player
WHERE game_player.score = (SELECT max(score) FROM game_player)
A bad example but one that could easily result from naive coding... it doesn't work in my testing if multiple rows are returned, how can I get the first returned row into the variable?
Do you need just the one score?
SELECT id
INTO topScorer
FROM game_player
WHERE game_player.score = ( SELECT max(score) as maxScore
FROM game_player
) LIMIT 1
Update:
Sir Rufo was right, the code above has now been corrected.
Apply limit in sub query to get only 1 value from sub query
SELECT id
INTO topScorer
FROM game_player
WHERE game_player.score = ( SELECT max(score)
FROM game_player LIMIT 1 );
Or to get multiple value from sub query used below one:
SELECT id
INTO topScorer
FROM game_player
WHERE game_player.score in ( SELECT max(score)
FROM game_player );
Use LIMIT x to ensure you are receiving only x rows from your query.
In this case you only want to get 1 row:
SELECT id
INTO topScorer
FROM game_player
WHERE game_player.score = ( SELECT max(score)
FROM game_player )
LIMIT 1
SQL Fiddle DEMO
As a working alternative you can also use this
SELECT id
INTO topScorer
FROM game_player
ORDER BY score DESC
LIMIT 1
SQL Fiddle DEMO
1) declare variable in SP:
declare #CourseID int
set #CourseID = 0
2) We need two query first for assign ID to variable and inner query for select only Top 1 record form table. In where clause of first query we compare ID with result of inner query:
SELECT #CourseID = ID FROM Course ID = ( Select Top 1 ID from Course )
3) Now check Variable value:
if(#CourseID > 0 )
Begin
//This mean ID of first row is assigned to CourseID
End
Else
Begin
//Can't found any record.
End