I want to update a table's column using the results of a stored procedure
create procedure seq
as
DECLARE #NextValue INT;
INSERT SequencesTable DEFAULT VALUES;
DELETE SequencesTable WITH(READPAST);
SELECT SCOPE_IDENTITY();
go
I can't use a UDF since it is nodeterministic. Something like this won't work
UPDATE [dbo].[t1] SET [c1] = seq
I have a feeling I am approaching this the wrong way.
I just want to do update a table that looks like this
1 1
2 2
1 4
1 4
5 5
1 2
To look like this
1 1
2 2
3 4
4 4
5 5
6 2
Try something like this:
update a set c1 = c1_new
from (
select
c1, c2
, c1_new = row_number() over (order by nullif(c1,c1))
from dbo.t1
) a
Play around with the inner query until you get something you like, then apply the update.
The NULLIF(c1,c1) attempts to preserve original order on disk. It always returns null.
Related
For a table like
create table Stations_in_route
(
ID_station_in_route int primary key,
ID_route int,
ID_station int,
Number_in_route int not null
)
There is the following trigger that changes the values in the Number_in_route column after a new row is added to the route. The list of numbers in the route must remain consistent.
create trigger stations_in_route_after_insert on Stations_in_route
after insert
as
if exists
(select *from Stations_in_route
where Stations_in_route.ID_station_in_route not in (select ID_station_in_route from inserted)
and Stations_in_route.ID_route in (select ID_route from inserted)
and Stations_in_route.Number_in_route in (select Number_in_route from inserted))
begin
update Stations_in_route
set Number_in_route = Number_in_route + 1
where Stations_in_route.ID_station_in_route not in (select ID_station_in_route from inserted)
and Stations_in_route.ID_route in (select ID_route from inserted)
and Stations_in_route.Number_in_route >= (select Number_in_route from inserted where Stations_in_route.ID_route = inserted.ID_route)
end
this trigger will throw an error if insertion into one ID_route is performed:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
For example,
Insert into Stations_in_route values(25, 4, 11, 3),(26, 4, 10, 5)
How to fix?
ID_station_in_route
ID_route
ID_station
Number_in_route
1
4
1
1
2
4
2
2
3
4
3
3
4
4
4
4
5
4
5
5
6
4
6
6
7
4
7
7
8
4
8
8
i expect the list after adding will become like this
ID_station_in_route
ID_route
ID_station
Number_in_route
1
4
1
1
2
4
2
2
25
4
11
3
3
4
3
4
26
4
10
5
4
4
4
6
5
4
5
7
6
4
6
8
7
4
7
9
8
4
8
10
this is not the whole table, as there are other routes too
Based on the requirements, when you add new stops to the route, you need to insert them into their desired sequence correctly, and push all existing stops from that point forward so that a contiguous sequence is maintained. When you insert one row this isn't very hard (just number_in_route + 1 where number_in_route > new_number_in_route), but when you insert more rows, you need to basically push the entire set of subsequent stops by 1 for each new row. To illustrate, let's say you start with this:
If we insert two new rows, such as:
INSERT dbo.Stations_in_route
(
ID_station_in_route,
ID_route,
ID_station,
Number_in_route
)
VALUES (25, 4, 11, 3),(26, 4, 10, 5);
-- add a stop at 3 ^ ^
----------------- add a stop at 5 ^
We can illustrate this by slowing it down into separate steps. First, we need to add this row at position #3:
And we do this by pushing all the rows > 3 down by 1:
But now when we add this row at position #5:
That's the new position #5, after the previous shift, so it looks like this:
We can do this with the following trigger, which is possibly a little more complicated than it has to be, but is better IMHO than tedious loops which might otherwise be required.
CREATE TRIGGER dbo.tr_ins_Stations_in_route ON dbo.Stations_in_route
FOR INSERT AS
BEGIN
;WITH x AS
(
SELECT priority = 1, *, offset = ROW_NUMBER() OVER
(PARTITION BY ID_route ORDER BY Number_in_route)
FROM inserted AS i
UNION ALL
SELECT priority = 2, s.*, offset = NULL FROM dbo.Stations_in_route AS s
WHERE s.ID_route IN (SELECT ID_route FROM inserted)
),
y AS
(
SELECT *, rough_rank = Number_in_route
+ COALESCE(MAX(offset) OVER (PARTITION BY ID_Route
ORDER BY Number_in_route ROWS UNBOUNDED PRECEDING),0)
- COALESCE(offset, 0),
tie_break = ROW_NUMBER() OVER
(PARTITION BY ID_route, ID_station_in_route ORDER BY priority)
FROM x
),
z AS
(
SELECT *, new_Number_in_route = ROW_NUMBER() OVER
(PARTITION BY ID_Route ORDER BY rough_rank, priority)
FROM y WHERE tie_break = 1
)
UPDATE s SET s.Number_in_route = z.new_Number_in_route
FROM dbo.Stations_in_route AS s
INNER JOIN z ON s.ID_route = z.ID_route
AND s.ID_station_in_route = z.ID_station_in_route;
END
Working example db<>fiddle
I've mentioned a couple of times that you might want to handle ties for new rows, e.g. if the insert happened to be:
Insert into Stations_in_route values(25, 4, 11, 3),(26, 4, 10, 3)
For that you can add additional tie-breaking criteria to this clause:
new_Number_in_route = ROW_NUMBER() OVER
(PARTITION BY ID_Route ORDER BY rough_rank, priority)
e.g.:
new_Number_in_route = ROW_NUMBER() OVER
(PARTITION BY ID_Route ORDER BY rough_rank, priority,
ID_station_in_route DESC)
I'm unable to repro the exception with the test code/data in the question, however I'm gonna guess that the issue is with this bit of the code in the trigger:
AND Stations_in_route.Number_in_route >=
(
SELECT Number_in_route
FROM inserted
WHERE Stations_in_route.ID_route = inserted.ID_route
)
The engine there will implicitly expect that subquery on the right-side of the >= operator to return a scalar result (single row, single column result), however the inserted table is in fact, a table...which may contain multiple records (as would be the case in a multi-row insert/update/etc. type statement as outlined in your example). Given that the filter (i.e. WHERE clause) in that subquery isn't guaranteed to be unique (ID_route doesn't appear to be unique, and in your example you have an insert statement that actually inserts multiple rows with the same ID_route value), then it's certainly possible that query will return a non-scalar result.
To fix that, you'd need to adjust that subquery to guarantee a result of a scalar value (single row and single column). You've guaranteed the single column already with the selector...now you need to add logic to guarantee a single result/record as well. That could include one or more of the following (or possibly other things also):
Wrap the selected Number_in_route column in an aggregate function (i.e. a MAX() perhaps?)
Add a TOP 1 with an ORDER BY to get the record you want to compare with
Add additional filters to the WHERE clause to ensure a single result is returned
The title of this post is probably not correct. I have a table like the one below and I need SQL that will select all the records that are related to a certain value. This is a "history" table that keeps track of ID values where I keep track of what an ID used to be. Record 1 splits into two and becomes 2 and 3, then maybe 2 and 3 merge together and become 4. For example:
OldID NewID
1 2
1 3
3 4
5 4
2 6
2 7
In the above example, record 1 has become 2 and 3. Record 4 is 3 and 5 merged together. Record 2 was part of 1 and has now split into 6 and 7.
So if we look at record NewID = 7, it is related to record 2, 6, and 1
NewID 4 is related to 3, 5, 1, and 2.
So in the end I need syntax that will select all records that were related in this way to NewID = X. Is this possible? Is this like a recursion?
Are you looking something like this?
declare #x int = 4
;with cte as (
select oldid, newid from table4 where newid = #x
union all
select t.oldid, t.newid from cte c inner join table4 t on c.OldId = t.newid
) select * from cte
I have a table
rate_id service_id
1 1
1 2
2 1
2 3
3 1
3 2
4 1
4 2
4 3
I need to find and insert in a table the unique combinations of sevice_ids by rate_id...but when the combination is repeated in another rate_id I do not want it to be inserted
In the above example there are 3 combinations
1,2 1,3 1,2,3
How can I query the first table to get the unique combinations?
Thanx!
Try doing something like this:
DECLARE #TempTable TABLE ([rate_id] INT, [service_id] INT)
INSERT INTO #TempTable
VALUES (1,1),(1,2),(2,1),(2,3),(3,1),(3,2),(4,1),(4,2),(4,3)
SELECT DISTINCT
--[rate_id], --include if required
(
SELECT
CAST(t2.[service_id] AS VARCHAR) + ' '
FROM
#TempTable t2
WHERE
t1.[rate_id] = t2.[rate_id]
ORDER BY
t2.[rate_id]
FOR XML PATH ('')
) AS 'Combinations'
FROM
#TempTable t1
I put the values in a table variable just for ease of testing the SELECT query.
Let's say I have an sql server table:
NumberTaken CompanyName
2 Fred 3 Fred 4 Fred 6 Fred 7 Fred 8 Fred 11 Fred
I need an efficient way to pass in a parameter [StartingNumber] and to count from [StartingNumber] sequentially until I find a number that is missing.
For example notice that 1, 5, 9 and 10 are missing from the table.
If I supplied the parameter [StartingNumber] = 1, it would check to see if 1 exists, if it does it would check to see if 2 exists and so on and so forth so 1 would be returned here.
If [StartNumber] = 6 the function would return 9.
In c# pseudo code it would basically be:
int ctr = [StartingNumber]
while([SELECT NumberTaken FROM tblNumbers Where NumberTaken = ctr] != null)
ctr++;
return ctr;
The problem with that code is that is seems really inefficient if there are thousands of numbers in the table. Also, I can write it in c# code or in a stored procedure whichever is more efficient.
Thanks for the help
Fine, if this question isn't going to be closed, I may as well Copy and paste my answer from the other one:
I called my table Blank, and used the following:
declare #StartOffset int = 2
; With Missing as (
select #StartOffset as N where not exists(select * from Blank where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from Blank where ID = #StartOffset
union all
select b.ID from Blank b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
You basically have two cases - either your starting value is missing (so the Missing CTE will contain one row), or it's present, so you count forwards using a recursive CTE (Sequence), and take the max from that and add 1
Tables:
create table Blank (
ID int not null,
Name varchar(20) not null
)
insert into Blank(ID,Name)
select 2 ,'Fred' union all
select 3 ,'Fred' union all
select 4 ,'Fred' union all
select 6 ,'Fred' union all
select 7 ,'Fred' union all
select 8 ,'Fred' union all
select 11 ,'Fred'
go
I would create a temp table containing all numbers from StartingNumber to EndNumber and LEFT JOIN to it to receive the list of rows not contained in the temp table.
If NumberTaken is indexed you could do it with a join on the same table:
select T.NumberTaken -1 as MISSING_NUMBER
from myTable T
left outer join myTable T1
on T.NumberTaken= T1.NumberTaken+1
where T1.NumberTaken is null and t.NumberTaken >= STARTING_NUMBER
order by T.NumberTaken
EDIT
Edited to get 1 too
1> select 1+ID as ID from #b as b
where not exists (select 1 from #b where ID = 1+b.ID)
2> go
ID
-----------
5
9
12
Take max(1+ID) and/or add your starting value to the where clause, depending on what you actually want.
I have a table that looks like this:
nid vid tid
1 2 3
2 2 4
3 2 4
4 2 3
I'd like to run some SQL that will take each one of those rows and create another based on it like so:
foreach row where vid=2, create a duplicate row where vid = 3
So I'll end up with this:
nid vid tid
1 2 3
2 2 4
3 2 4
4 2 3
1 3 3
2 3 4
3 3 4
4 3 3
The table is not that big (less than 500 rows, I think) and this is a one-time thing (so the code does not need to be amazingly optimized or anything). Thanks!
you’ll probably want this:
INSERT INTO `table`(`nid`, `vid`, `nid`)
SELECT `nid`, `vid`+1, `nid`
FROM `table`
WHERE `vid` = 2
it inserts all rows, with vid incremented by 1
If you want a general solution (i.e. all values of vid, but adding one to each of them), this is just adapting dandres' answer:
INSERT INTO TheTable(nid, vid_incremented, nid)
SELECT nid, vid + 1 as vid_incremented , nid
FROM TheTable
Edit: fixed to do arithmetic in select statement.
Could you do the following:
INSERT INTO TheTable(nid, vid, nid)
SELECT nid, 3, nid
FROM TheTable
WHERE vid = 2
Two columns appear to have the same name, but the above should work. It takes the set of Vid = 2 elements, inserts them again but using Vid = 3.
Create another table with 2 fixed records, e.g. TwoRec (vvid), with values 2 and 3
Do a cartesian join with TwoRec, i.e.
SELECT TheTable.nid, TwoRec.vvid, TheTable.nid
FROM TheTable, TwoRec
WHERE TheTable.vid = 2;
I'd create a temp table of all of the vids you want to use:
create temporary table Vids
(
vid int
)
declare #vid int
declare #maxvid int
set #vid = 1
set #maxvid = 500
while #vid < #maxvid
begin
insert into vids values(#vid)
set #vid = #vid + 1
end
Then, I'd use that temp table to build out your other table:
select
t2.id,
t1.vid,
t2.nid
into newtable
from
vids t1
cross join tbl t2
order by
t1.vid,
t2.id