Update sub-index when inserting in SQL - sql

I have a table that simplified looks like this:
group index value
1 1 text 1
1 2 text 2
1 3 text 3
2 1 text 4
2 2 text 5
2 3 text 6
Group is an foreign key that is also used for grouping items in the table.
Index is an internal index for sorting the items within the group.
Text is just a value.
Then if I do an insert or running a stored procedure to do the insert
INSERT INTO Table VALUES (1, 2, 'new text')
I would like to update the index for the group 1 items so the table looks like this:
group index value
1 1 text 1
1 2 new text (inserted)
1 3 text 2 (index updated)
1 4 text 3 (index updated)
2 1 text 4
2 2 text 5
2 3 text 6
(Running on MS SQL-Server 2008)

This is a store procedure you can use. Takes 3 parameters, grp, idx, and value:
CREATE PROCEDURE ReIndex
#grp int,
#idx int,
#value nvarchar(30)
AS
IF NOT EXISTS(SELECT 1 FROM [tbl] WHERE grp = #grp and idx =#idx)
INSERT INTO tbl VALUES (#grp, #idx, #value)
else
update tbl
set idx = idx+1
where
grp =#grp and
idx >= #idx
INSERT INTO tbl VALUES (#grp, #idx, #value)
Usage:
exec reindex 1, 2, 'new text'
sqlfiddle: http://sqlfiddle.com/#!3/3323d/11

In this answer I'll assume the first column names are grp and idx, as group and index are reserved words in SQL.
You can use the next available idx value by using a MAX(idx)+1 formula, like this:
INSERT INTO t1(grp, idx, value)
SELECT 1, IFNULL(MAX(idx),0)+1, 'new text'
FROM t1
WHERE grp = 1;
But this way two records inserted at about the same time might get the same idx value. To avoid this you could lock your table while inserting the record. You can do that by wrapping the above statement with table lock/unlock statements:
LOCK TABLES t1;
...
UNLOCK TABLES;
However, this method cannot be used within procedures. There the solution involves creating a transaction, and a SELECT ... FOR UPDATE. Note that you need InnoDB Storage for this:
CREATE PROCEDURE ins_val(IN pgrp int, IN pvalue varchar(200))
BEGIN
DECLARE newidx INT;
START TRANSACTION;
SELECT IFNULL(MAX(idx),0)+1
INTO newidx
FROM t1
WHERE grp = pgrp;
FOR UPDATE;
INSERT INTO t1(grp, idx, value)
VALUES (pgrp, newidx, pvalue);
COMMIT;
END;
Note that if you later delete records, these will leave gaps in the idx numbering that will not be filled again by the above method. On the other hand, if the records with the highest idx value is deleted, the next inserted record will re-use that idx value.

Related

ignoring last value if its the same as current value

I have an table where i would like to query the following:
The data comes in batches . This data is combined with an id.
This ID only gets send ones when the new batch comes in. After that the ID only changes when there is a new batch . In the mean time the value stays null
What i need to do is if new data comes in and it has the same id as the previous batch i have to continue the insert with null in the id field instead of pushing a new row with the same id value.
Beneath is a simplistic view of the table
ID Values
1 10
null 20
null 20
null 20
null 20
2 20
null 20
null 20
null 20
null 20
1 20
null 20
If you could help me point in a directions that would help me a lot.
Maybe to clearify the id value is a set of tags. So there are some definied tags(100 or more) and when a new batch comes the batch gets a tag with it. And if that tag is the same as the previous the null has to continue instead of inserting the same tag
You'll need to add an identity field (or a timestamp) in order to be able to query the latest ID.
ALTER TABLE MyTable ADD MyIdent INT IDENTITY(1, 1) NOT NULL
Then on your insert (if your Id value is NULL) you can call
INSERT INTO MyTable (Id, Values)
SELECT TOP 1 Id, #ValuesVariable
FROM MyTable
WHERE Id IS NOT NULL
ORDER BY MyIdent DESC
This below Sp may helps to inert data try this
IF OBJECT_ID('tempdb..#Temp')IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp (ID INT,[Values] INT)
CREATE PROCEDURE usp_Insert
(
#Id INT,
#Values INT
)
AS
BEGIN
IF NOT EXISTS (SELECT 1 FROM #Temp WHERE ID = #ID)
BEGIN
INSERT INTO #Temp(ID,[Values])
SELECT #Id,#Values
END
ELSE
INSERT INTO #Temp(ID,[Values])
SELECT NULL,#Values
END
EXEC usp_Insert 2,12
SELECT * FROM #Temp

Know identity before insert

I want to copy rows from the table within the table itself. But before inserting I need to modify a varchar column appending the value of identity column to it.
My table structure is:
secID docID secName secType secBor
1 5 sec-1 G 9
2 5 sec-2 H 12
3 5 sec-3 G 12
4 7 sec-4 G 12
5 7 sec-5 H 9
If I want to copy data of say docID 5, currently this runs through a loop one row at a time.
I can write my query as
insert into tableA (docID, secName, secType, secBor)
select 8, secName, secType, secBor from tableA where docID = 5
But how can I set value of secName before hand so that it becomes sec-<value of secID column>?
Don't try to guess the value of identity column. In your case you could simply create a computed column secName AS CONCAT('sec-', secID). There is no further need to update that column.
DB Fiddle
It is also possible to create an AFTER INSERT trigger to update the column.
Since SQL Server does not have GENERATED ALWAYS AS ('Sec - ' + id) the only simple option I see is to use a trigger.
Adding to my comment something like:
insert into tableA (docID, secName, secType, secBor)
select
ROW_NUMBER() OVER (ORDER BY DocID),
'Sec -'+ ROW_NUMBER() OVER (ORDER BY DocID),
secType, secBor
from tableA
where docID = 5
In SQL Server 2012 and later, you can achieve this by using the new sequence object.
CREATE SEQUENCE TableAIdentitySeqeunce
START WITH 1
INCREMENT BY 1 ;
GO
create table TableA
(
secId int default (NEXT VALUE FOR TableAIdentitySeqeunce) not null primary key,
varcharCol nvarchar(50)
)
declare #nextId int;
select #nextId = NEXT VALUE FOR TableAIdentitySeqeunce
insert TableA (secId, varcharCol)
values (#nextId, N'Data #' + cast(#nextId as nvarchar(50)))

Use Check Constraints to determine if a bit column and be set to true based on another column value, possible?

To elabortate,
is it possible to enforce a rule where ONLY one record entry can have a column named 'IsPrimaryUser' set to true, whereas all others grouped by another column are set to false. The condition for deciding which entry will have a true 'IsPrimaryUser' field will be the CompanyId column.
I am only interested in whether it can be done using check constraints. Obviously, there is a SQL approach to something like this.
Example:
User Table
int UserId | int CompanyId | bit IsPrimaryUser
Data:
UserId | CompanyId | IsPrimaryUser
1 1 1
2 1 0
3 1 0
4 1 0
5 2 1
6 2 0
7 2 0
8 2 0
Check Constraints only work on a single row, but you can use scalar UDFs within the constraint.
You can breach the single-row check by using UDFs that check other rows in the table. Although unlike a trigger where you can access the DELETED virtual table and process individually, SQL Server seems to hold the records in a sort of transaction, and perform a CHECK on EACH row after the change, then finally accepting or aborting the CRUD in batch.
See this test case
Create table
create table usertable (UserId int,
CompanyId int, IsPrimaryUser int)
Populate
insert usertable select
1, 1, 1 union all select
2, 1, 0 union all select
3, 1, 0 union all select
4, 1, 0 union all select
5, 2, 1 union all select
6, 2, 0 union all select
7, 2, 0 union all select
8, 2, 0
Scalar function helper
create function dbo.anyprimaryuser(#userid int, #company int) returns bit as
begin
return
case when exists (
select * from usertable
where companyid=#company and isprimaryuser=1 and userid<>#userid)
then 1 else 0 end
end
The CHECK constraint
alter table usertable add constraint usertable_ck1
check (isprimaryuser=0 or dbo.anyprimaryuser(userid,companyid)=0)
Tests
insert usertable select 9,2,1 -- fail
insert usertable select 9,2,0 -- ok
insert usertable select 19,4,1 union all select 20,4,0 -- ok
insert usertable select 19,3,1 union all select 20,3,0 union all select 21,3,1
-- not ok, accepting the multi-row insert will breach the constraint
update usertable set IsPrimaryUser=1-IsPrimaryUser where CompanyId=4
-- ok! sets one and unsets the other in one go
(note) I updated the answer after Martin's comment below

Select data, setting a calculated value into a column

I'm selecting data from a table within a trigger (FOR UPDATE). Some rows I'm selecting have been updated by a transaction, that initiated the trigger, and some rows are not. I'd like to write somewhere in the dataset a flag for every row, which value would be calculated as "id IN (SELECT [id] FROM INSERTED)". This flag would show, is a row updated with the last transaction or not.
Is it possible in SQL?
Sure, I can do 2 separate queries, with 2 different conditions, but the trigger perfomance is real bottleneck...
Here's an example for SQL Server:
if object_id('TriggerTest') is not null
drop table TriggerTest
create table TriggerTest (id int identity, name varchar(50), inLastUpdate bit)
insert TriggerTest (name) values ('joe'), ('barrack'), ('george'), ('dick')
go
create trigger dbo.TriggerTestDelete
on TriggerTest
after update
as begin
declare #date datetime
set #date = GETDATE()
update dbo.TriggerTest
set inLastUpdate =
case when id in (select id from inserted) then 1
else 0
end
end
go
update TriggerTest set name = name where id in (1,2)
update TriggerTest set name = name where id in (1,3)
select * from TriggerTest
This prints:
id name inLastUpdate
1 joe 1
2 barrack 0
3 george 1
4 dick 0

MySQL: Is it possible to return a "mixed" dataset?

I'm wondering if there's some clever way in MySQL to return a "mixed/balanced" dataset according to a specific criterion?
To illustrate, let's say that there are potential results in a table that can be of Type 1 or Type 2 (i.e. a column has a value 1 or 2 for each record). Is there a clever query that would be able to directly return results alternating between 1 and 2 in sequence:
1st record is of type 1,
2nd record is of type 2,
3rd record is of type 1,
4th record is of type 2,
etc...
Apologies if the question is silly, just looking for some options. Of course, I could return any data and do this in PHP, but it does add some code.
Thanks.
Something like this query should do:
Select some_value, x, c
From
(
Select
some_value, x,
Case When x=1 Then #c1 Else #c2 End As c,
#c1 := Case When x=1 Then #c1+2 Else #c1 End As c1,
#c2 := Case When x=2 Then #c2+2 Else #c2 End As c2
From test_data, (Select #c1:=0, #c2:=1) v
Order By some_value
) sub
Order By c
It assigns unique even numbers to x=0, and odd numbers to x=1, and uses these values as sort criteria.
It returns
some_value x c
A 1 0
X 2 1
B 1 2
Y 2 3
C 1 4
Z 2 5
for the following test-data:
Create Table test_data (
some_value VARCHAR(10),
x INT
);
Insert Into test_data Values('A', 1);
Insert Into test_data Values('B', 1);
Insert Into test_data Values('C', 1);
Insert Into test_data Values('Z', 2);
Insert Into test_data Values('Y', 2);
Insert Into test_data Values('X', 2);
Within the alternating rule values are sorted by some_value, you can change this in the inner select, or add your conditions there.
If there are more values of a certain type (1 or 2), you get them after the rest (1 2 1 2 2 2).
You can use IF function as a part of your SELECT statement to change columns, but I'm not sure how to make is alternate automatically between two columns. If you however find proper condition this will work for you
SELECT IF(condition, first_column, second_column) FROM your_table
first_column and second_column can be of different type, for example:
SELECT IF(id > 10, name, status_id) FROM clients
works well when name is a VARCHAR and status_id is an INT