Trigger to update rank dynamically - sql-server-2008-r2

I want to write trigger for insert update and Delete. O have one table named (tbl_rank) which have primary key (ID).
ID Name Rank
1 A 1
2 B 2
3 C 3
4 D 4
5 E 5
Now I want to insert new rank but conditions are
1) if I enter 6 it will be 6
2) if I enter 7 it also should be 6 (I mean in sequence)
3) if I enter 2 than than entered rank will be 2 and 2 will be 3 and so on
For delete trigger
1) if I delete 5 the rank should be 1 to 4
2) if I delete 2 the rank would be rearranged and 3 should be 2 and 4 would be 3 and so on
for update trigger
1) if I update 3 to 5 than 4 would be 3 and 5 would be 4
2) if I update 5 to 3 than 3 would be 4 and 4 would be 5
I wrote insert and delete trigger its working fine but in update I am getting uneven result.

Can you not just have tbl_rank as a view then you don't need any triggers? To rank them in the view you can use a windowed function row_number() over (order by Id)
How is the initial update performed? If you know it is an update then you need to do a delete and insert just for the effected range. Eg changing 3 to 5. You delete records for 3 to 5 then insert those 3 records again with the different ids. An update statement essentially does this anyway

There is an assumption that id is not an auto-identity column.
CREATE TRIGGER trg_tbl_rank
ON tbl_rank
INSTEAD OF INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #v_deleted_rank INT;
DECLARE #v_inserted_rank INT;
DECLARE #v_max_rank INT;
SELECT #v_deleted_rank = COALESCE(rank, 0) FROM deleted;
SELECT #v_inserted_rank = COALESCE(rank, 0) FROM inserted;
SELECT #v_max_rank = COALESCE(MAX(rank), 0) FROM tbl_rank;
IF #v_deleted_rank > 0
BEGIN
DELETE FROM tbl_rank
WHERE id = (SELECT id FROM deleted);
UPDATE tbl_rank
SET rank = rank - 1
WHERE rank > #v_deleted_rank;
END
IF #v_inserted_rank > 0
BEGIN
IF #v_inserted_rank <= #v_max_rank
BEGIN
UPDATE tbl_rank
SET rank = rank + 1
WHERE rank >= #v_inserted_rank;
INSERT INTO tbl_rank (id, name, rank)
SELECT id, name, #v_inserted_rank FROM inserted;
END
ELSE
INSERT INTO tbl_rank (id, name, rank)
SELECT id, name, #v_max_rank + 1 FROM inserted;
END
END
GO
Here are queries to test:
INSERT INTO tbl_rank (id, name, rank) VALUES (1, 'A', 1);
INSERT INTO tbl_rank (id, name, rank) VALUES (2, 'B', 2);
INSERT INTO tbl_rank (id, name, rank) VALUES (3, 'C', 3);
INSERT INTO tbl_rank (id, name, rank) VALUES (4, 'D', 4);
INSERT INTO tbl_rank (id, name, rank) VALUES (5, 'E', 5);
SELECT * FROM tbl_rank;
INSERT INTO tbl_rank (id, name, rank) VALUES (6, 'F', 7);
SELECT * FROM tbl_rank;
INSERT INTO tbl_rank (id, name, rank) VALUES (7, 'G', 2);
SELECT * FROM tbl_rank;
DELETE FROM tbl_rank WHERE rank = 7;
SELECT * FROM tbl_rank;
DELETE FROM tbl_rank WHERE rank = 2;
SELECT * FROM tbl_rank;
UPDATE tbl_rank SET rank = 5 WHERE rank = 3;
SELECT * FROM tbl_rank;
UPDATE tbl_rank SET rank = 3 WHERE rank = 5;
SELECT * FROM tbl_rank;
TRUNCATE TABLE tbl_rank;

I want to write trigger for insert update and Delete I have one table named(tbl_rank)which have primary key(ID)
Please post DDL, so that people do not have to guess what the keys, constraints, Declarative Referential Integrity, data types, etc. in your schema are. Learn how to follow ISO-11179 data element naming conventions and formatting rules. Temporal data should use ISO-8601 formats. Code should be in Standard SQL as much as possible and not local dialect.
This is minimal behavior on SQL forums. Putting “tbl_” on table name is a classic design flaw called “tbling” and the column names are violations of ISO-11179 rules, too. Now we have to guess at keys, data types, etc. Here is my guess and clean up.
CREATE TABLE Prizes
(prize_id INTEGER NOT NULL PRIMARY KEY,
prize_name CHAR(1) NOT NULL,
prize_rank INTEGER NOT NULL);
INSERT INTO Prizes
VALUES
(1, 'A', 1),
(2, 'B', 2),
(3, 'C', 3),
(4, 'D', 4),
(5, 'E', 5);
Why triggers? RDBMS has virtual tables and columns. This not a deck of punch cards or a magnetic tape file. A VIEW is always current and correct.
CREATE VIEW Prize_List
AS
SELECT prize_id, prize_name,
ROW_NUMBER() OVER (ORDER BY prize_id)
AS prize_rank
FROM Prizes;
But it might be better to drop the prize_id column completely and re-arrange the display order based on the prize_rank column:
CREATE TABLE Prizes
(prize_name CHAR(1) NOT NULL,
prize_rank INTEGER NOT NULL PRIMARY KEY);
Now use procedures to manipulate the table as needed.
CREATE PROCEDURE Swap_Prize_Ranks (#old_prize_rank INTEGER, #new_prize_rank INTEGER)
AS
UPDATE Prizes
SET prize_rank
= CASE prize_rank
WHEN #old_prize_rank
THEN #new_prize_rank
ELSE prize_rank + SIGN(#old_prize_rank - #new_prize_rank)
END
WHERE prize_rank BETWEEN #old_prize_rank AND #new_prize_rank
OR prize_rank BETWEEN #new_prize_rank AND #old_prize_rank;
When you want to drop a few rows, remember to close the gaps with this:
CREATE PROCEDURE Close_Prize_Gaps()
AS
UPDATE Prizes
SET prize_rank
= (SELECT COUNT (P1.prize_rank)
FROM Prizes AS P1
WHERE P1.prize_rank <= Prizes.prize_rank);

Related

T-sql Concatenate variables for use in an Insert Into while loop

I'm trying to create a SQL statement that allows me to save time when creating a series of CTEs or temp tables that are all the same and only increment in certain places. I could do this with VBA, but I can't figure out if this is possible with SQL
I'm trying to run the following code
DECLARE #N as INT
DECLARE #POINTS as TABLE(ID int Not Null,n varchar(3) Not Null)
DECLARE #TABLENAME varchar(6)
Set #INC = 1
Set #N = 5
--WHILE #INC <= #N
--BEGIN
Declare #N1 INT
Declare #N2 INT
Declare #N3 INT
Declare #N4 INT
Declare #N5 INT
SET #N1=25
SET #N2=50
SET #N3=100
SET #N4=250
SET #N5=500
--END
WHILE #INC <= #N
BEGIN
INSERT INTO #POINTS(ID, n)
VALUES (#INC, CONCAT('#N',#INC))
SET #INC = #INC + 1
END
Select * from #POINTS
These are the results of the code after it runs:
ID n
1 #N1
2 #N2
3 #N3
4 #N4
5 #N5
Is it possible to have the values I set for each #Nx variable be inserted to the n column instead of the concatenated character values? If so, how? I haven't been able to find any results on someone trying to do this.
I can get around this problem by using outside tables, but I want to know if this is possible.
If you only have a small number of values values, you might as well write a single insert statement that will insert all 5 rows to the table:
INSERT INTO #POINTS(ID, n) VALUES
(1, '5'),
(2, '25'),
(3, '50'),
(4, '100'),
(5, '250'),
(6, '500');
If you have too many values to write like this, you can use a cte to generate an inline tally table, and use insert...select from that tally table:
DECLARE #Points as TABLE
(
ID int Not Null,
n varchar(3) Not Null
);
WITH E10(N) AS
(
SELECT 1
FROM (VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9))V(N)
), Tally(N) AS
(
-- Since all the numbers are multiples of 5,
-- there's no point of populating the tally
-- with numbers that aren't multiples of 5....
SELECT ROW_NUMBER() OVER(ORDER BY ##SPID) * 5
FROM E10 As Ten
CROSS JOIN E10 As Hundred
--Need more? Add more cross joins
-- each will multiply the number of rows by 10:
--CROSS JOIN E10 As Thousand
--CROSS JOIN E10 As [Ten Thousand] -- and so on
)
INSERT INTO #Points(ID, n)
SELECT ROW_NUMBER() OVER(ORDER BY N), CAST(N as varchar(3))
FROM Tally
WHERE N IN(5, 25, 50, 100, 250, 500);
SELECT *
FROM #Points;
Results:
ID n
1 5
2 25
3 50
4 100
5 250
6 500
Of course, if you already have a tally table, you don't need the cte...

SQL query to get the below output

Need help for query:
Id Name Balance
1 A 10
2 A -10
3 A 10
4 A 15
5 B 10.5
6 B -10.5
7 B 25
Requirement: I want to remove the balance with +ve and –ve values for each name. I am expecting the below output
Expected results: --
Id Name Balance
1 A 10
2 A 15
3 B 25
Can you please share me SQL query.
Here is one way to do it.
Create dummy data for testing
CREATE TABLE foo (Id INT, Name VARCHAR(50), Balance money);
INSERT INTO foo VALUES (1, 'A', 10);
INSERT INTO foo VALUES (2, 'A', -10);
INSERT INTO foo VALUES (3, 'A', 10);
INSERT INTO foo VALUES (4, 'A', 15);
INSERT INTO foo VALUES (5, 'B', 10.5);
INSERT INTO foo VALUES (6, 'B', -10.5);
INSERT INTO foo VALUES (7, 'B', 25);
1) Create a temp table that has one extra MatchFound column to record whether a match is found.
CREATE TABLE #TempTable(Id int, Name varchar(50), Balance decimal(10,2), MatchFound bit);
INSERT INTO #TempTable (Id, Name, Balance, MatchFound)
SELECT Id, Name, Balance, 0
FROM foo;
2) Loop through the data to find a match for each row. Update MatchFound column if a match is found.
DECLARE #Id INT, #Name VARCHAR(50), #Balance decimal(10,2), #MatchId INT;
DECLARE MyCursor CURSOR FOR SELECT Id, Name, Balance FROM #TempTable ORDER BY Id;
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #Id, #Name, #Balance
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS (SELECT 1 FROM #TempTable WHERE Id = #Id AND MatchFound = 0)
BEGIN
SET #MatchId = NULL
SELECT TOP 1 #MatchId = Id FROM #TempTable
WHERE Id <> #Id AND Name = #Name AND Balance = #Balance * -1 AND MatchFound = 0
IF #MatchId IS NOT NULL
UPDATE #TempTable SET MatchFound = 1 WHERE Id IN (#Id, #MatchId)
END
FETCH NEXT FROM MyCursor INTO #Id, #Name, #Balance
END
CLOSE MyCursor;
DEALLOCATE MyCursor;
3) Select and return rows that did not have a match
Select Id, Name, Balance From #TempTable Where MatchFound = 0
4) Clean up
drop table #TempTable

Converting Traditional IF EXIST UPDATE ELSE INSERT into MERGE is not working?

I am going to use MERGE to insert or update a table depending upon ehether it's exist or not. This is my query,
declare #t table
(
id int,
name varchar(10)
)
insert into #t values(1,'a')
MERGE INTO #t t1
USING (SELECT id FROM #t WHERE ID = 2) t2 ON (t1.id = t2.id)
WHEN MATCHED THEN
UPDATE SET name = 'd', id = 3
WHEN NOT MATCHED THEN
INSERT (id, name)
VALUES (2, 'b');
select * from #t;
The result is,
id name
1 a
I think it should be,
id name
1 a
2 b
You have your USING part slightly messed up, that's where to put what you want to match against (although in this case you're only using id)
declare #t table
(
id int,
name varchar(10)
)
insert into #t values(1,'a')
MERGE INTO #t t1
USING (SELECT 2, 'b') AS t2 (id, name) ON (t1.id = t2.id)
WHEN MATCHED THEN
UPDATE SET name = 'd', id = 3
WHEN NOT MATCHED THEN
INSERT (id, name)
VALUES (2, 'b');
select * from #t;
As Mikhail pointed out, your query in the USING clause doesn't contain any rows.
If you want to do an upsert, put the new data into the USING clause:
MERGE INTO #t t1
USING (SELECT 2 as id, 'b' as name) t2 ON (t1.id = t2.id) --This no longer has an artificial dependency on #t
WHEN MATCHED THEN
UPDATE SET name = t2.name
WHEN NOT MATCHED THEN
INSERT (id, name)
VALUES (t2.id, t2.name);
This query won't return anything:
SELECT id FROM #t WHERE ID = 2
Because where is no rows in table with ID = 2, so there is nothing to merge into table.
Besides, in MATCHED clause you are updating a field ID on which you are joining table, i think, it's forbidden.
For each DML operations you have to commit (Marks the end of a successful the transaction)Then only you will be able to see the latest data
For example :
GO
BEGIN TRANSACTION;
GO
DELETE FROM HumanResources.JobCandidate
WHERE JobCandidateID = 13;
GO
COMMIT TRANSACTION;
GO

How can I randomly assign one user to another in T-SQL?

I'm trying to write a sproc that will take a table of users and assign them to each other randomly with no duplicates and no one assigned to themselves.
The user table has an UserId column and a Username column. I have a separate table for storing the IDs. That table has a UserId column and an AssignedUserId column.
I used this snippet for randomly sorting rows:
SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]
When the code above is part of a select, it works fine. I'm just having a hard time thinking as a data set. I can work the problem row-by-row or in C#, but I'm hoping there's a SQL guru that can show me how to do this in a nice, efficient query.
Thanks!
It can be done, but not good practice on large tables, but with your table being so small this will work great.
DECLARE #A TABLE (A1 INT, A2 INT)
INSERT INTO #A VALUES(1, 1)
INSERT INTO #A VALUES(2, 2)
INSERT INTO #A VALUES(3, 3)
INSERT INTO #A VALUES(4, 4)
INSERT INTO #A VALUES(5, 5)
SELECT * FROM #A ORDER BY NEWID()
Result1:
3 3
1 1
4 4
5 5
2 2
Result2:
2 2
3 3
4 4
5 5
1 1
Result3:
4 4
3 3
1 1
5 5
2 2
Result4:
3 3
5 5
1 1
4 4
2 2
Result5:
5 5
4 4
1 1
3 3
2 2
Ok, here's an alternate solution I came up with in the mean time. I decided that the users are already in the system in random order, so maybe I should try a decoder-ring style approach. So, this code generates a shift value and then shifts the user IDs around. The select statement does the "wrap-around" with a formula. It's not completely random and it could use some polish, but this is what I have. The users probably won't perceive the lack of randomness. Thanks for the help!
DECLARE #people TABLE
(
id INT,
name VARCHAR(50)
)
INSERT INTO #people VALUES (1,'Matthew')
INSERT INTO #people VALUES (2,'Mark')
INSERT INTO #people VALUES (3,'Luke')
INSERT INTO #people VALUES (4,'John')
INSERT INTO #people VALUES (5,'Doug')
INSERT INTO #people VALUES (6,'Jamie')
INSERT INTO #people VALUES (7,'John')
INSERT INTO #people VALUES (8,'Cameron')
INSERT INTO #people VALUES (9,'Emily')
INSERT INTO #people VALUES (10,'Tyler')
INSERT INTO #people VALUES (11,'Todd')
INSERT INTO #people VALUES (12,'Kathryn')
DECLARE #Random INT;
DECLARE #Upper INT;
DECLARE #Lower INT;
DECLARE #MaxId INT;
SET #Lower = 1 -- The lowest record ID
SELECT #Upper = (MAX(Id) / 2) FROM #people -- select random int that is somewhere between 1 and half of max ID for shift
SELECT #MaxId = MAX(Id) FROM #people -- select the largest ID in the table
SELECT #Random = ROUND(((#Upper - #Lower -1) * RAND() + #Lower), 0) -- calculate a random number to shift IDs off by
SELECT id, ((id + #Random) % #MaxId) + 1, #Random
from #people

where exists - all - group by?

I use SQL Server 2008 R2.
I have a weird problem as following. I have a table as shown in
I need to write such a query like:
SELECT DISTINCT Field1
FROM MYTABLE
WHERE Field2 IN (96,102)
in this query, WHERE Field2 IN (96,102) gives me 96 or 102 or both!
More over, I would like to return rows that contains 96 and 102 at the same time!
Is there any suggestion? please write result oriented...
I have made a sqlfiddle for this..
create table a (id int, val int)
go
insert into a select 1, 22
insert into a select 1, 122
insert into a select 2, 22
insert into a select 3, 122
insert into a select 4, 22
insert into a select 4, 122
then select like this
select count(distinct id), id
from a
where val in (22, 122)
group by id
having count(id) > 1
EDIT: count(distinct id) will only show distinct counts..
EDIT:
Here's a sqlfiddle example (thanks to Mark Kremers):
http://sqlfiddle.com/#!3/df201/1
create table mytable (field1 int, field2 int)
go
insert into mytable values (199201, 84)
insert into mytable values (199201, 96)
insert into mytable values (199201, 102)
insert into mytable values (199201, 103)
insert into mytable values (581424, 96)
insert into mytable values (581424, 84)
insert into mytable values (581424, 106)
insert into mytable values (581424, 122)
insert into mytable values (687368, 79)
insert into mytable values (687368, 96)
insert into mytable values (687368, 102)
insert into mytable values (687368, 104)
insert into mytable values (687368, 106)
Here's the query:
select distinct a.field1 from
( select field1 from mytable where field2=96) a
inner join
( select field1 from mytable where field2=102) b
on a.field1 = b.field1
And here are the results:
FIELD1
199201
687368
Finally, here's a simplified version of the query (thans to pst):
select distinct a.field1 from mytable a
inner join mytable b
on a.field1 = b.field1
where a.field2=96 and b.field2=102
Use a self-join? Not the most tidy, but I think it works well for 2 values
SELECT *
FROM T R1
JOIN T R2 -- join table with itself
ON R1.F1 = R2.F1 -- where the first field is the same
WHERE R1.F2 = 96 AND R2.F2 = 102 -- and each has one of the required values
(T = Table, Rx = Relation Alias, Fx = Field)
If there can be an arbitrary number of fields, this can be solved as
CREATE TABLE #T (id int, val int)
GO
INSERT INTO #T (id, val)
VALUES
(1, 22), (1, 22), -- no, only 22 (but 2 records)
(2, 22), (2, 122), -- yes, both values (only)
(3, 122), -- no, only 122
(4, 22), (4,122), -- yes, both values ..
(4, 444), (4, null), -- and extra values
(5, 555) -- no, neither value
GO
-- Using DISTINCT over filtered results first, as
-- SQL Server 2008 does not support HAVING COUNT(DISTINCT F1, F2)
SELECT id
FROM (SELECT DISTINCT id, val
FROM #T
WHERE val IN (22, 122)) AS R1
GROUP BY id
HAVING COUNT(id) >= 2 -- or 3 or ..
GO
-- Or a similar variation, as can COUNT(DISTINCT ..)
-- in the SELECT of a GROUP BY
SELECT id
FROM (SELECT id, COUNT(DISTINCT val) as ct
FROM #T
WHERE val IN (22, 122)
GROUP BY id) AS R1
WHERE ct >= 2 -- or 3 or ..
GO
For larger IN (..) sizes, say above 20 values, it may be advisable to use a separate table or table-value and a JOIN for performance reasons.
Try from your original query:
SELECT DISTINCT Field1
FROM MYTABLE
WHERE rtrim(ltrim(cast(Field2 as varchar))) IN ('96','102')