Insert and Update an array of dynamic length into a table - c#

My table is
(id int,property varchar)
Suppose I want to insert {3, 'a, b, c'}. How can I do it in a stored procedure?
The table entry would be:
id property
3 a
3 b
3 c
Also when i want to update my id=3 row by {3, 'ab, b, bg, ht'} . What would be the stored procedure for this insert and update operation? My table entries should become
id property
3 ab
3 b
3 bg
3 ht

sample table:
create table yourtable (id int,property varchar(5))
Procedure for that table:
create procedure p_test
(
#id int,
#prolist varchar(2000)
) as
begin
;with x as
(
SELECT * FROM yourtable WHERE id = #ID
)
MERGE INTO
x t1
using
(SELECT #id id, ltrim(t.c.value('.', 'VARCHAR(2000)')) property
FROM (
SELECT x = CAST('<t>' +
REPLACE(#prolist, ',', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)) t2 on t1.id = t2.id and t1.property = t2.property
when not matched then INSERT (id,property)
VALUES(t2.id, t2.property)
when matched
THEN UPDATE SET t1.id = t2.id
WHEN NOT MATCHED BY SOURCE THEN DELETE
;
end
Testing:
exec p_test 3, 'b,c'
select * from yourtable
exec p_test 3, 'a,b,c'
select * from yourtable
exec p_test 3, 'a,c'
select * from yourtable
exec p_test 4, 'g,h'
select * from yourtable
Result:
id property
3 b
3 c
id property
3 b
3 c
3 a
id property
3 c
3 a
id property
4 g
3 c
3 a
4 h
EDIT:
in order to update a new column use this table:
create table yourtable (id int,property varchar(5), is_active bit default 1)
Use this procedure:
alter procedure p_test
(
#id int,
#prolist varchar(2000)
) as
begin
;with x as
(
SELECT * FROM yourtable WHERE id = #ID
)
MERGE INTO
x t1
using
(SELECT #id id, ltrim(t.c.value('.', 'VARCHAR(2000)')) property
FROM (
SELECT x = CAST('<t>' +
REPLACE(#prolist, ',', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)) t2 on t1.id = t2.id and t1.property = t2.property
when not matched then INSERT (id,property, is_active)
VALUES(t2.id, t2.property, 1)
when matched
THEN UPDATE SET t1.id = t2.id, is_active = 1
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET t1.is_active = 0
;
end

First one:
insert into <tableName>(id, property) values(3, 'a');
insert into <tableName>(id, property) values(3, 'b');
insert into <tableName>(id, property) values(3, 'c');
Second issue:
update <tableName> set property='ab' where property = 'a';
update <tableName> set property='bg' where property = 'c';
insert into <tableName>(id, property) values(3, 'ht');
And now, a question: are you sure this is what your problem needs? Usually, when we call a column id we want it to be an identifier, that is, unique for each row. This may be a little bit off topic, but just in case...

There should be an id/primary key which will be unique. Profide any unique field and update records on the base of that unique field. Or you can make property unique or pair of id and property unique.

If I understood well, you could do something similar to this:
Insert INTO #tempTable
SELECT 3, * FROM dbo.splitstring('a,b,c,d')
It's just a pseudocode and you should do it inside your stored procedure.
If this is the right approach you have to take a look at this: T-SQL split string

Related

Given a sorted row of n numbers in SQL server, how do i find at what point the sum of the rows reach the value k?

Assume i got the below row of number and max quantity value is 10.
Quantity BatchValue
2 0
4 0
4 0
6 1
8 2
Summation of 2+4+4 gives me a value less than or equal to max quatity 10 and so the batch value for those rows become 0. The pending rows are 6 and 8. They cannot be summed up to be < max quantity. So they will be seperate. Can we get an sql query or an algorith that can do this?
Here's a nice running sum routine you can use
create table #temp (rowid int identity, quantity int)
insert #temp
select quantity from yourtable order by your order
declare #holding table (quantity int, runningsum int)
declare #quantity int
declare #running int=0
declare #iterator int = 1
while #iterator<=(select max(rowid) from #temp)
begin
select #quantity=quantity from #temp where rowid=#iterator
set #running=#quantity+#running
insert #holding
select #quantity, #running
set #iterator=#iterator+1
end
Edited code from Daniel Marcus above to give the actual response requested in query.
CREATE TABLE #temp(rowid int identity(1,1), quantity int)
INSERT INTO #temp
SELECT 2
UNION ALL SELECT 4
UNION ALL SELECT 4
UNION ALL SELECT 6
UNION ALL SELECT 8
declare #batchValue int = 0 ,#maxquantity int = 10
declare #holding table (quantity int, batchvalue int)
declare #quantity int
declare #running int=0
declare #iterator int = 1
while #iterator<=(select max(rowid) from #temp)
begin
select #quantity=quantity from #temp where rowid=#iterator
set #running=#quantity+#running
-- Newly added condition
if (#running > #maxquantity) BEGIN
SET #batchValue = #batchValue + 1 -- increment the batch value
insert #holding select #quantity, #batchValue
SET #running = #quantity -- reset the running value
END
ELSE
insert #holding select #quantity, #batchValue
set #iterator=#iterator+1
end
SELECT * FROM #holding
DROP TABLE #temp
Hope the snippet works for your purpose. I tested this in SQL azure and provides the result you mentioned.
--The below query will help you if you working on sql server 2012 or higher
CREATE TABLE #RUN_TOT(ID INT)
INSERT INTO #RUN_TOT VALUES(2),(4),(4),(6),(8)
;WITH CTE AS
(
SELECT ID,
ROW_NUMBER() OVER(ORDER BY ID) RNUM,
CASE
WHEN SUM(ID) OVER(ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) <=
(SELECT MAX(ID) FROM #RUN_TOT) THEN 0
ELSE
ID
END VAL
FROM #RUN_TOT
)
SELECT ID,VAL FROM CTE WHERE VAL=0
UNION ALL
SELECT ID,ROW_NUMBER() OVER(ORDER BY VAL) VAL FROM CTE WHERE VAL<>0

Generating Sequence number based on the column value

I want to generate a sequence number based on the column value. I want to have this kind of output. I gonna use this in c# .net winform as GridView output
TABLE1
ID Name NoStub
1 arte 3
2 gonzake 2
TABLE2
ID Name StubNumberStart StubNumberEnd
1 arte 0001 0003
2 gonzake 0004 0005
Try this query.. it will give result from Table 2 to Table 1
DECLARE #T1 AS TABLE (ID INT, NAME VARCHAR(50), STUBNUMBER VARCHAR(10))
INSERT INTO #T1 VALUES ( 1, 'ARTE', '001')
INSERT INTO #T1 VALUES ( 1, 'ARTE', '002')
INSERT INTO #T1 VALUES ( 1, 'ARTE', '003')
INSERT INTO #T1 VALUES ( 1, 'GONZAKE', '004')
INSERT INTO #T1 VALUES ( 1, 'GONZAKE', '005')
SELECT * FROM #T1
SELECT DISTINCT ID ,NAME, COUNT(*) AS NOSTUB FROM #T1
GROUP BY ID, NAME
If your request is different from Table 1 to Table 2 then please let me know .. you will get new query...
ALTER PROCEDURE ExpandIt
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Id int;
DECLARE #name varchar(50);
DECLARE #noStub int;
DECLARE #stubNumber char(8);
DECLARE #count as int = 0;
DECLARE #continuedID as int = 0;
DECLARE t1 CURSOR FAST_FORWARD FOR
SELECT ID, Name,NoStub from Table1
OPEN t1
FETCH NEXT FROM t1 INTO #Id, #name, #noStub
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE (#count < #noStub)
BEGIN
SET #count = #count + 1;
SET #stubNumber = ('0000' + CONVERT (CHAR, #continuedID + #count));
SET #stubNumber = SUBSTRING (#stubNumber,LEN(#stubNumber)-4+1, 4);
INSERT INTO Table2 (ID, Name, StubNumber)
VALUES (#Id, #name,#stubNumber);
END
SET #continuedID = #count;
SET #count = 0;
FETCH NEXT FROM t1 INTO #Id, #name, #noStub
END
CLOSE t1 ;
DEALLOCATE t1
END

Obtain the missing number [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Find the smallest unused number in SQL Server
I have this table in Sql server
ID | LetterID | LetterName
ID => int and identity
LetterID => int and unique and NotNull
LetterName => string
LetterID fill from my C# application and user set number for it.for example 1,2,3,4,5,...,100,.. (increment an unit for each row) and now my LetterID is 100 but Sometimes user delete one row from table for example delete row where LetterID is 50, now for insert new row (in application) I suggested to him LetterID chose 50, How can I get the missing numbers from table?
var output = Enumerable.Range(1, list.Max(item => item.LetterID))
.Except(list.Select(item => item.LetterID))
get the blank/missing row
SELECT TOP 1 x.LetterID +1
FROM tablename x
WHERE NOT EXISTS(SELECT * FROM tablename xx WHERE xx.LetterID = x.LetterID + 1)
ORDER BY x.LetterID
select t1.ID, t1.LetterID-1
from yourtable t1
left join yourtable t2
on t1.LetterID = t2.LetterID+1
and t1.ID = t2.ID
where t2.ID is null
and t1.LetterID>(select MIN(LetterID) from yourtable where ID = t1.ID)
;with cte as
(
select max(LetterID) as id from your_table
union all
select id-1 from cte where id>1
)
select cte.id as LetterID
from cte
left join your_table yt on cte.id=yt.LetterID
where yt.id is null
order by cte.id

convert xml string in a sql table to dynamic columns

I have two tables (using table variables for illustration. You can run these directly in management studio) that are related by the Id column.
Items in the first table has some standard set of columns and the second table has some extended parameter data for the same record. I'm storing the extended set as xml as it is dynamic in all aspects (different per product or new values being added etc).
I'm able to join these two tables and flatten out the column list as you can see in the example below. But my query requires to have the dynamic columns be defined beforehand. I would like to have this truly dynamic in the sense that if I were to add a new column in the #extended table, it should automatically come out as a new column in the output column list.
Basically the list of additional columns should be determined by the xml for that record. column name should be the xml tag and value should be value for the xml tag for each id.
Any pointers? (and can it be fast too with around 100k records or more in each table)
declare #standard table
(
Id INT,
Column1 varchar(10),
Column2 varchar(10),
Column3 varchar(10)
)
declare #extended table
(
Id INT,
column1 xml
)
insert into #standard values (1,'11', '12', '13')
insert into #standard values (2,'21', '22', '23')
insert into #extended values (1,'<FieldSet><Field><id>1</id><column4>1x</column4><column5>4x</column5></Field></FieldSet>')
insert into #extended values (2,'<FieldSet><Field><id>2</id><column4>2x</column4><column5>5x</column5></Field></FieldSet>')
select s.column1, s.column2,
(
SELECT Item2.value('(column4)[1]', 'varchar(50)')
FROM
e.column1.nodes('/FieldSet') AS T(Item)
CROSS APPLY e.column1.nodes('/FieldSet/Field') AS T2(Item2)
) column4,
(
SELECT Item2.value('(column5)[1]', 'varchar(50)')
FROM
e.column1.nodes('/FieldSet') AS T(Item)
CROSS APPLY e.column1.nodes('/FieldSet/Field') AS T2(Item2)
) column5
from #extended e
join #standard s on s.Id = e.Id
First off you can simplify your current query a bit.
select s.column1,
s.column2,
e.column1.value('(/FieldSet/Field/column4)[1]', 'varchar(50)') as column4,
e.column1.value('(/FieldSet/Field/column5)[1]', 'varchar(50)') as column5
from extended as e
join standard as s
on s.Id = e.Id
To do what you want will not be easy or fast. You need to get a list of all name/value pairs in your XML.
select T1.X.value('.', 'int') as Id,
T2.X.value('local-name(.)', 'sysname') as Name,
T2.X.value('.', 'varchar(10)') as Value
from extended as e
cross apply e.column1.nodes('/FieldSet/Field/id') as T1(X)
cross apply e.column1.nodes('/FieldSet/Field/*[position() > 1]') as T2(X)
Use that in a pivot query and join to standard.
select S.column1,
S.column2,
P.column4,
P.column5
from standard as s
inner join
(
select id, P.column4, P.column5
from (
select T1.X.value('.', 'int') as Id,
T2.X.value('local-name(.)', 'sysname') as Name,
T2.X.value('.', 'varchar(10)') as Value
from extended as e
cross apply e.column1.nodes('/FieldSet/Field/id') as T1(X)
cross apply e.column1.nodes('/FieldSet/Field/*[position() > 1]') as T2(X)
) as e
pivot (min(Value) for Name in (column4, column5)) P
) P
on S.Id = P.Id
To do this with a dynamic number of columns returned you need to build this pivot query dynamically.
Store the Name/Value pairs in temp table, use that table to figure out the columns you need and to build your query.
create table #ext
(
Id int,
Name sysname,
Value varchar(10),
primary key(Id, Name)
)
insert into #ext(Id, Name, Value)
select T1.X.value('.', 'int') as Id,
T2.X.value('local-name(.)', 'sysname') as Name,
T2.X.value('.', 'varchar(10)') as Value
from extended as e
cross apply e.column1.nodes('/FieldSet/Field/id') as T1(X)
cross apply e.column1.nodes('/FieldSet/Field/*[position() > 1]') as T2(X)
declare #SQL nvarchar(max)
set #SQL =
'select S.column1,
S.column2,
[COLLIST]
from standard as s
inner join
(
select id, [COLLIST]
from #ext as e
pivot (min(Value) for Name in ([COLLIST])) P
) P
on S.Id = P.Id'
declare #ColList nvarchar(max)
set #ColList =
(select ','+Name
from #ext
group by Name
for xml path(''), type).value('.', 'nvarchar(max)')
set #SQL = replace(#SQL, '[COLLIST]', stuff(#ColList, 1, 1, ''))
exec (#SQL)
drop table #ext
I hope it will help you
SELECT #COUNT_XML=0
SELECT #COUNT_XML=(SELECT #xxxxx_GROUP_ID.value('count(/NewDataSet/position/ID)', 'INT'))
IF(#COUNT_XML > 0)
BEGIN
IF OBJECT_ID('tempdb..#TBL_TEMPOSITION') IS NOT NULL
DROP TABLE #TBL_TEMPOSITION
CREATE TABLE #TBL_TEMPOSITION (ID NUMERIC(18,0))
INSERT INTO #TBL_TEMPOSITION (ID)
SELECT XMLxxxxGroup.PositionGPItem.value('.','NUMERIC(18,0)')
FROM #xxxxx_GROUP_ID.nodes('/NewDataSet/position/ID') AS XMLPositionGroup(PositionGPItem)
SELECT #emp_cond =#emp_cond+ N' AND CM.STATIC_EMP_INFO.POSITION_ID IN (SELECT ID FROM #TBL_TEMPOSITION) '
END

Parameters to the EXISTS clause in a stored procedure

I have a table DEPT, which holds 2 columns - ID, NAME.
A search form is presented with the IDs from the DEPT table and the user can chose any number of IDs and submit the form, to get the related NAMEs.
Clarification/Inputs:
I don't want to build a dynamic query - its not manageable.
I prefer a stored procedure using table-valued parameters
Any other solutions to proceed?
NOTE:
This example is simple with 1 table - in real life, I have to deal with more than 6 tables!
Thanks for any suggestions
CREATE TYPE dbo.DeptList
AS TABLE
(
ID INT
);
GO
CREATE PROCEDURE dbo.RetrieveDepartments
#dept_list AS dbo.DeptList READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT Name FROM dbo.table1 WHERE ID IN (SELECT ID FROM #dept)
UNION ALL
SELECT Name FROM dbo.table2 WHERE ID IN (SELECT ID FROM #dept)
-- ...
END
GO
Now in your C# code, create a DataTable, fill it in with the IDs, and pass it in to the stored procedure. Assuming you already have a list called tempList and the IDs are stored in id:
DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("ID"));
foreach(var item in tempList)
{
tvp.Rows.Add(item.id);
}
using (connObject)
{
SqlCommand cmd = new SqlCommand("StoredProcedure", connObject);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#dept_list", tvp);
tvparam.SqlDbType = SqlDbType.Structured;
...
}
You can also use a split function. Many exist, this is the one I like if you can guarantee that the input is safe (no <, >, & etc.):
CREATE FUNCTION dbo.SplitInts_XML
(
#List VARCHAR(MAX),
#Delimiter CHAR(1)
)
RETURNS TABLE
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'int')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>') + '</i>').query('.')
) AS a
CROSS APPLY x.nodes('i') AS y(i)
);
GO
Now your procedure can be:
CREATE PROCEDURE dbo.RetrieveDepartments
#dept_list VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
;WITH d AS (SELECT ID = Item FROM dbo.SplitInts(#dept_list, ','))
SELECT Name FROM dbo.table1 WHERE ID IN (SELECT ID FROM d)
UNION ALL
SELECT Name FROM dbo.table2 WHERE ID IN (SELECT ID FROM d)
-- ...
END
GO

Categories