I have a stored procedure I'm trying to create to fill a temporary table. But I need to make several passes at adding data based upon some conditions and capture those conditions with an additional field added to the temp table.
I start out like this:
select top 0
into #mytable
from UserTable
This I have found simply copies the basic structure with the same columns and types. Then I need to add a field:
alter table #mytable ADD reasontype varchar
The I make several passes at examining the table, here is one of them:
insert into #mytable
select distinct a.*, 'Annual'
from UserTable a
where (a.EnrollmentDate < DATEADD(year, -1, getdate())
This is to select those that require an annual review. The procedure compiles without an error but when I try to fill a datatable I get the error that string or binary data would be truncated. What am I doing wrong?
alter table #mytable ADD reasontype varchar(max)
If that works, either use "max" or a value that won't truncate your values...or use a LEFT statement where the length matches the longest value of reasontype
example for left:
alter table #mytable ADD reasontype varchar(3)
insert into #mytable
select distinct a.*, LEFT('Annual',3)
from UserTable a
where (a.EnrollmentDate < DATEADD(year, -1, getdate())
but you probably just want this:
alter table #mytable ADD reasontype varchar(6) /* where 6 is the length of the string "Annual" */
Use a length on your varchar, 'Annual' is being truncated to 'A'. If you're planning on indexing the reasontype field, don't use varchar(max), it exceeds the maximum width of an indexable field
Related
I'm faced with a little problem. The situation is:
I have a MSSQL table which contains IDs (int, identity, primarykey), and names (string).
This table is huge, so i don't want to fill the entire dataset just for one LINQ-query.
I have a search algorithm, which fills a List<int> with more than one ID.
I want to load the matching rows in a datagridview with the following code:
dataGridView1.DataSource = tbl_WorklistTableAdapter.GetDataByID(ids_here);
But i can't handle multiple IDs, just a single. The GetDataByID() code needs to be this (i think):
SELECT [ID]
,[NAME]
FROM [DataBase].[dbo].[tbl_Namelist]
WHERE ID IN (#searchterm)
However WHERE ID IN accepts comma-separated ints, like 1,2,3. But the #variable is just one int.
How should i convert string to ints?
Thank you, and sorry for bad eng. :)
In that case you need to change in sql so you need to generate one temp table for comma separated id and apply condition in the your query.
Like:-
DECLARE #variables VARCHAR(200) = #searchterm -- Your Variable
CREATE TABLE #temp(ID NUMERIC) DECLARE #temp VARCHAR(200)
SET #temp = #variables WHILE LEN(#temp) > 0 BEGIN DECLARE #t1 VARCHAR(200)
IF CHARINDEX(',',#temp) > 0
BEGIN
SET #t1 = SUBSTRING(#temp,0,CHARINDEX(',',#temp))
INSERT INTO #TEMP SELECT #t1
SET #temp = SUBSTRING(#temp,CHARINDEX(',',#temp)+1,len(#temp))
END
ELSE
BEGIN
INSERT INTO #TEMP SELECT #temp
SET #temp = ''
END
END
SELECT [ID]
,[NAME]
FROM [DataBase].[dbo].[tbl_Namelist]
WHERE ID IN (SELECT ID FROM #temp)
You can use the built-in function SPLIT_STRING function:
SELECT [ID], [NAME]
FROM [DataBase].[dbo].[tbl_Namelist]
WHERE ID IN (SELECT VALUE FROM SPLIT_STRING(#searchterm,','))
This only works on Compatibility level 130 or greater.
If you are on an older version of SQL Server you can follow this answer, which defines an equivalent function:
SELECT [ID], [NAME]
FROM [DataBase].[dbo].[tbl_Namelist]
WHERE ID IN (SELECT VALUE FROM fn_split_string(#searchterm,','))
TableName: Information
Stored procedure that inserts data into the above table.
CREATE PROCEDURE sp_insert_information
(
#profileID as int,
#profileName as varchar(8)
#profileDescription as varchar(100)
)
AS
BEGIN
INSERT INTO information(profileid, profilename, profiledescription)
VALUES (#profileID, #profileName, #profileDescription);
END
I call this procedure from .NET, is there a way to do multiple inserts if I pass profileID's as a comma separated parameter? (can I use split function?)
I can either loop through the profileID's and send 1 by 1 to procedure, however my data is going to be the same except different profileID.
Table data (with 3 columns):
1 profileUnavailable User Error
2 profileUnavailable User Error
3 profileUnavailable User Error
4 profileUnavailable User Error
5 profileUnavailable User Error
Any other approaches that I can try to do this in a single shot?
You have a couple options:
SqlBulkInsert - You can create a dataset that you can dump to the table. This is useful for many inserts. This will bypass the procedure altogether.
Table Valued Parameters - You can use a table value parameter as a parameter of the stored procedure, again manipulating data using a dataset.
The CSV Parameter with string split IS an option, but I would recommend one of the above over it.
Nope. That sproc does one insert at a time as it is written presently. You have to invoke it separately.
You might also consider wrapping that up into a transaction so if one fails, all of them won't be committed.
My favourite technique up to some years ago was to have an arsenal of splitting functions, that could split a delimited list of homogeneous values (e.g. all integers, all booleans, all datetimes, etc.) into a table variable. Here is an example of such a function.
CREATE FUNCTION [dbo].[fn_SplitInt](#text varchar(8000),
#delimiter varchar(20) = '|')
RETURNS #Values TABLE
(
pos int IDENTITY PRIMARY KEY,
val INT
)
AS
BEGIN
DECLARE #index int
SET #index = -1
-- while the list is not over...
WHILE (LEN(#text) > 0)
BEGIN
-- search the next delimiter
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) -- if no more delimiters (hence this field is the last one)
BEGIN
IF (LEN(#text) > 0) -- and if this last field is not empty
INSERT INTO #Values VALUES (CAST (#text AS INT)) -- then insert it
ELSE -- otherwise, if this last field is empty
INSERT INTO #Values VALUES (NULL) -- then insert NULL
BREAK -- in both cases exit, since it was the last field
END
ELSE -- otherwise, if there is another delimiter
BEGIN
IF #index>1 -- and this field is not empty
INSERT INTO #Values VALUES (CAST(LEFT(#text, #index - 1) AS INT)) -- then insert it
ELSE -- otherwise, if this last field is empty
INSERT INTO #Values VALUES (NULL) -- then insert NULL
SET #text = RIGHT(#text, (LEN(#text) - #index)) -- in both cases move forward the read pointer,
-- since the list was not over
END
END
RETURN
END
When you have a set of functions like these, then your problem has a solution as simple as this one:
CREATE PROCEDURE sp_insert_information
(
#profileID as varchar(2000),
#profileName as varchar(8)
#profileDescription as varchar(100)
)
AS
BEGIN
DECLARE #T TABLE (Id int)
INSERT INTO #T (Id)
SELECT val FROM dbo.fn_SplitInt(#profileID)
INSERT INTO information(profileid, profilename,profiledescription)
SELECT Id, #profileName, #profileDescription
FROM #T
END
But today it might be quicker to execute, and even require less coding, to generate an XML representation of the data to insert, then pass the XML to the stored procedure and have it INSERT INTO table SELECT FROM xml, if you know what I mean.
WHILE len(#ProfileId) > 0
BEGIN
DECLARE #comm int= charindex(',',#ProfileId)
IF #comm = 0 set #comm = len(#ProfileId)+1
DECLARE #Profile varchar(1000) = substring(#ProfileId, 1, #comm-1)
INSERT INTO Information(ProfileId,ProfileName,ProfileDescription)
VALUES (#ProfileId,#ProfileName,#ProfileDescription)
SET #ProfileId= substring(#ProfileId, #comm+1, len(#ProfileId))
END
I am inserting records through a query similar to this one:
insert into tbl_xyz select field1 from tbl_abc
Now I would like to retreive the newly generated IDENTITY Values of the inserted records. How do I do this with minimum amount of locking and maximum reliability?
You can get this information using the OUTPUT clause.
You can output your information to a temp target table or view.
Here's an example:
DECLARE #InsertedIDs TABLE (ID bigint)
INSERT into DestTable (col1, col2, col3, col4)
OUTPUT INSERTED.ID INTO #InsertedIDs
SELECT col1, col2, col3, col4 FROM SourceTable
You can then query the table InsertedIDs for your inserted IDs.
##IDENTITY will return you the last inserted IDENTITY value, so you have two possible problems
Beware of triggers executed when inserting into table_xyz as this may change the value of ##IDENTITY.
Does tbl_abc have more than one row. If so then ##IDENTITY will only return the identity value of the last row
Issue 1 can be resolved by using SCOPE__IDENTITY() instead of ##IDENTITY
Issue 2 is harder to resolve. Does field1 in tbl_abc define a unique record within tbl_xyz, if so you could reselect the data from table_xyz with the identity column. There are other solutions using CURSORS but these will be slow.
SELECT ##IDENTITY
This is how I've done it before. Not sure if this will meet the latter half of your post though.
EDIT
Found this link too, but not sure if it is the same...
How to insert multiple records and get the identity value?
As far as I know, you can't really do this with straight SQL in the same script. But you could create an INSERT trigger. Now, I hate triggers, but it's one way of doing it.
Depending on what you are trying to do, you might want to insert the rows into a temp table or table variable first, and deal with the result set that way. Hopefully, there is a unique column that you can link to.
You could also lock the table, get the max key, insert your rows, and then get your max key again and do a range.
Trigger:
--Use the Inserted table. This conaints all of the inserted rows.
SELECT * FROM Inserted
Temp Table:
insert field1, unique_col into #temp from tbl_abc
insert into tbl_xyz (field1, unique_col) select field1, unique_col from tbl_abc
--This could be an update, or a cursor, or whatever you want to do
SELECT * FROM tbl_xyz WHERE EXISTS (SELECT top 1 unique_col FROM #temp WHERE unique_col = tbl_xyz.unique_col)
Key Range:
Declare #minkey as int, #maxkey as int
BEGIN TRANS --You have to lock the table for this to work
--key is the name of your identity column
SELECT #minkey = MAX(key) FROM tbl_xyz
insert into tbl_xyz select field1 from tbl_abc
SELECT #maxkey = MAX(key) FROM tbl_xyz
COMMIT Trans
SELECT * FROM tbl_xyz WHERE key BETWEEN #minkey and #maxkey
How do I pass paramter values for a command where SQL Text contains IN.
i.e. My command SQL text is something like SELECT * FROM USERS WHERE USERID IN (1,2,3).
There are plenty of examples like MSDN has but coudn't find one to pass values for IN. Tried taking a variable and set values as a single string but SQL wont work that way.
You should have a UDF which converts a (for example) csv string into a table of values.
Then, your query can be some thing like:
select *
from yourTable
where yourField IN ( select yourColumn from dbo.yourUDF(yourCSV) )
The short answer is there's no good way to do this.
If you're indeed only working with a list of numeric values, you probably don't need to use parameters as this feature is mainly designed to prevent SQL injection attacks. However, if you have an array of ints, and use that to build your query, you're safe.
The only other way I can think of would be to add one parameter per item. For example:
SELECT * FROM USERS WHERE USERID IN (#p1, #p2, #p3)
Then, loop through your array in C# and create a parameter for each item.
Hope this helps!
This has been asked many, many times. Two ways spring to mind:
If you have SQL2008, you can pass a "table value parameter" (tvp), where your table parameter has the values for your "in" clause, then have a "where" clause that checks values are "in (select whatever from tvp)".
If you do not have SQL2008, you can use a "common table expression" (cte), such as...
declare #t table (xyz varchar(100))
insert into #t values ('hello')
insert into #t values ('there')
insert into #t values ('world')
insert into #t values ('10')
insert into #t values ('20')
insert into #t values ('30')
declare #a as varchar(120)
set #a = 'hello,10,30'
;with cte as
(
select cast(null as varchar(max)) as 'v', 1 as s, charindex(',' , #a, 0) as e
union all
select cast(substring(#a, s, e-s) as varchar(max)), e+1, charindex(',' , #a + ',', e+1)
from cte where e!=0
)
select *
from #t
where (xyz in (select v from cte where v is not null))
In this example (above), the string #a is the comma separated list of values you want for your "in" clause, and the (recursive) cte just strips out the values, one by one. Of course, this is just a quick example and would probably need checks for empty strings, successive commas, and the like.
Of course, the usual caveats apply with regard to using SQL to (effectively) do string manipulation.
Regards,
Ross
I am running SQL Server and I have a stored procedure. I want do a select statement with a WHERE IN clause. I don't know how long the list will be so right now I have tried something as follows
SELECT * FROM table1 WHERE id IN (#idList)
in this solution #idList is a varChar(max). but this doesn't work. I heard about passing in table values, but I am confused about how to do that. Any help would be great
I would suggest using a function to split the incoming list (use the link that Martin put in his comment).
Store the results of the split function in a temporary table or table variable and join it in your query instead of the WHERE clause
select * into #ids from dbo.Split(',', #idList)
select t.*
from table1 t
join #ids i
on t.id = i.s
The most efficient way would be to pass in a table valued parameter (if you're on SQL Server 2008), or an XML parameter (if you're on SQL Server 2005/2000). If your list is small (and you're on SQL Server 2005/2000), passing in your list as a comma (or otherwise) delimited list and using a split function to divide the values out into rows in a temporary table is also an option.
Whichever option you use, you would then join this table (either the table parameter, the table resulting from the XML select, or the temporary table created by the values from the split) to your main query.
Here is a table valued function that takes a nvarchar and returns a table to join on:
Create function [ReturnValues]
(
#Values nvarchar(4000)
)
Returns #ValueTable table(Value nvarchar(2000))
As
Begin
Declare #Start int
Declare #End int
Set #Start = 1
Set #End = 1
While #Start <= len(#Values)
Begin
Set #End = charindex(',', #Values, #Start)
If #End = 0
Set #End = len(#Values) + 1
Insert into #ValueTable
Select rtrim(ltrim(substring(#Values, #Start, #End - #Start)))
Set #Start = #End + 1
End
Return
End
GO
Binding an #idList parameter as you suggested is not possible with SQL.
The best would be bulk inserting the ids into a separated table and than query that table by using an subselect, or joining the IDs.
e.g.
INSERT INTO idTable (id, context) values (#idValue, 1);
INSERT INTO idTable (id, context) values (#idValue, 1);
INSERT INTO idTable (id, context) values (#idValue, 1); // as often as you like
SELECT * FROM table1, idTable WHERE table1.id == idTable.id and idTable.context = 1
The context must be a unique value that identifies the Id Range. That is important for running the stored proc parallel. Without the context information, running the stored procecure in parallel would mix the values from different selections.
If the number of parameters are reasonably small (< 100) you can use several parameters
SELECT * FROM table1 WHERE IN id IN (#id1, #id2, #id3)
If it is longer, look for a split function.