My Motive is to pass long array of ID as parameter to stored procedure and select data on the basis of ID. So i created Type in SQL Server
CREATE TYPE [dbo].[CategoryIdArray] AS TABLE(
[CategoryId] [bigint] NULL
)
GO
and stored procedure
ALTER PROCEDURE [dbo].[GetNewestArticleByCatsPageWise]
#dt as [dbo].[CategoryIdArray] READONLY,
#PageIndex INT = 1
,#PageSize INT = 10
,#PageCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER
(
ORDER BY [dateadded]
)AS RowNumber,[desid]
INTO #Results
FROM [DB_user1212].[dbo].[discussions] as d , [DB_user1212].[dbo].[CategoryMap] as c where d.desid=c.[Topic Id] and c.[Category Id] in (select CategoryId from [dbo].[CategoryIdArray]) and [TopicType]='1' order by [dateadded]
DECLARE #RecordCount INT
SELECT #RecordCount = COUNT(*) FROM #Results
SET #PageCount = CEILING(CAST(#RecordCount AS DECIMAL(10, 2)) / CAST(#PageSize AS DECIMAL(10, 2)))
PRINT #PageCount
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
END
Tried to use above stored procedure by Code below
public List<String> getNewestArticleByCategoryPageWise( long[] categoryId)
{
List<string> topicId= new List<string>();
try
{
DataTable dt_Categories = new DataTable();
dt_Categories.Columns.Add("Category", typeof(String));
DataRow workRow;
foreach(long cat in categoryId)
{
workRow = dt_Categories.NewRow();
workRow["Category"] = cat;
dt_Categories.Rows.Add(workRow);
}
int pageIndex = 1;
SqlCommand cmd = new SqlCommand("dbo.GetNewestArticleByCatsPageWise", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#PageIndex", pageIndex);
cmd.Parameters.AddWithValue("#PageSize", 10);
cmd.Parameters.Add("#PageCount", SqlDbType.Int, 4).Direction = ParameterDirection.Output;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#dt", dt_Categories);
tvparam.SqlDbType = SqlDbType.Structured;
con.Open();
sdr= cmd.ExecuteReader();
while(sdr.Read())
{
topicId.Add(sdr.GetString(0));
}
con.Close();
}
catch(Exception ex)
{
con.Close();
throw ex;
}
return topicId;
}
When i run above function exception is thrown Invalid object name 'dbo.CategoryIdArray'. But i created it as type. Help me out what i missed out. I refferred this.
Problem is with this line in stored procedure is with this line
select CategoryId from [dbo].[CategoryIdArray] .
We can not select from type like this, we should use
select CategoryId from #dt
The first thing that I do when I get these questions is to create a sample database. The code below creates the following.
1 - database named [test]
2 - table named [Discussions]
3 - table named [CategoryMap]
4 - user defined table type named [CategoryIdArray]
5 - load the tables with 100 records of data
--
-- Create a test db
--
USE [master];
go
CREATE DATABASE [Test];
GO
--
-- Create the user defined type
--
USE [Test];
go
CREATE TYPE [CategoryIdArray] AS
TABLE
(
[CategoryId] [bigint] NULL
);
--
-- Create skelton tables
--
create table Discussions
(
dis_id int identity (1,1),
dis_name varchar(64),
dis_added_dte datetime default getdate()
);
go
create table CategoryMap
(
cat_id int identity(1,1),
cat_topic_id int,
cat_topic_type char(1)
);
go
-- clear tables
truncate table Discussions;
truncate table CategoryMap;
go
--
-- Create 100 rows of dummy data
--
declare #cnt int = 0;
while #cnt < 100
begin
insert into Discussions (dis_name)
values ('sample discussion record # ' + str(#cnt, 2, 0));
insert into CategoryMap (cat_topic_id, cat_topic_type)
values (#cnt+1, '1')
set #cnt = #cnt + 1;
end;
go
--
-- Show the sample data
--
select * from Discussions;
go
select * from CategoryMap;
go
The second step is to re-write the stored procedure. If you are using below 2012, go with a window function rownumber(). In 2012, the offset and fetch clauses of the order by were included for paging.
http://technet.microsoft.com/en-us/library/ms188385(v=sql.110).aspx
--
-- Create my procedure
--
create procedure [GetArticlesByPage]
#Tvp as [CategoryIdArray] READONLY,
#PageIndex INT = 1,
#PageSize INT = 10,
#PageCount INT OUTPUT
AS
BEGIN
-- Declare variables
DECLARE #var_recs int = 0;
DECLARE #var_offset int = 0;
-- Do not count the records
SET NOCOUNT ON;
-- Start of paging
SET #var_offset = #var_offset + ((#PageIndex - 1) * #PageSize);
-- Set page count variable
SELECT #var_recs = count(*)
FROM
[dbo].[Discussions] as d
JOIN
[dbo].[CategoryMap] as c
ON
d.dis_id = c.cat_topic_id
JOIN
#TVP a
ON
c.cat_id = a.CategoryId
WHERE
cat_topic_type = '1';
set #PageCount = ceiling(cast(#var_recs as real) / cast(#PageSize as real));
--
-- Return the record set
--
SELECT
dis_id
FROM
[dbo].[Discussions] as d
JOIN
[dbo].[CategoryMap] as c
ON
d.dis_id = c.cat_topic_id
JOIN
#TVP a
ON
c.cat_id = a.CategoryId
WHERE
cat_topic_type = '1'
ORDER BY
dis_added_dte
OFFSET #var_offset ROWS
FETCH NEXT #PageSize ROWS ONLY;
END;
GO
I did leave the page count in place; However, I do not think it is needed since you can repeat the call until the result set is empty.
Please do not dump the record set into a temporary table since it could be quite large if you were return all the columns to display. I choose two separate calls. One for a total count. One for a single page.
The last TSQL part is to test the stored procedure from SSMS.
--
-- Call the stored procedure
--
-- instantiate tvp
DECLARE #my_tvp as [CategoryIdArray];
DECLARE #my_page_cnt as int;
-- add 25 entries
declare #cnt int = 25;
while #cnt < 50
begin
insert into #my_tvp (CategoryId)
values (#cnt + 1);
set #cnt = #cnt + 1;
end;
-- show the data in the tvp
select * from #my_tvp
-- call the function
exec [GetArticlesByPage] #my_tvp, 1, 10, #PageCount = #my_page_cnt OUTPUT;
-- show the data in the output
select #my_page_cnt as 'my_pages';
go
In my test example, I wanted rows 26 to 50 paged as 10 rows. Result 1 is the 25 rows, Result 2 is the 10 rows that were paged, and Result 3 is how many pages. Therefore, the TSQL part of the solution is sound.
Stay tuned for a C# program debug session later tonight.
http://www.mssqltips.com/sqlservertip/2112/table-value-parameters-in-sql-server-2008-and-net-c/
Take a look at this post. It is doing exactly what you are trying to do.
Here are some ideas to try.
1 - Make sure the connection properties, login's default database is [Test] for my example.
2 - Is the type defined in the [Test] database? Please double check this.
3 - Is this correct? The column name is [CategoryId] in the database type. You have the following - [Category]. Try changing the name in the C# code.
dt_Categories.Columns.Add("Category", typeof(String));
4 - Remove the [dbo]. from the type in the SP. It is not in the example from MS SQL Tips. Might be confusing the issue. SQL server will resolve the name.
5 - I noticed the type is defined as big int but the id in the tables is int? Make sure the data types are consistent.
Please try these suggestions. Get back to me on how you make out.
Can you get me a detailed call stack trace and error message if this is still an issue??
So here is a C# console application that I promised.
It works as expected.
You were mixing up some ideas that are the foundation of ADO.NET and data tables. You should get used to looking at the immediate window and local variables. This will help you track down issues.
Here is my sample call to the Stored Procedure.
1 - Setup data table (50 to 74)
2 - Page the data by 5's
3 - Look at second page
//
// Good Ref. - http://msdn.microsoft.com/en-us/library/ms254937(v=vs.110).aspx
//
// Basic stuff from C# console app
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Required for data table
using System.Data;
using System.Data.SqlClient;
// Standard stuff ...
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Debug info
Console.WriteLine("Test - Start");
// Create the table with one column
DataTable my_Table;
my_Table = new DataTable("Category");
my_Table.Columns.Add("CategoryId", typeof(string));
// Add data to table
for (int my_Cnt = 50; my_Cnt < 75; my_Cnt++)
{
DataRow my_Row = my_Table.NewRow();
my_Row["CategoryId"] = my_Cnt.ToString();
my_Table.Rows.Add(my_Row);
}
// Debug info
Console.WriteLine("Test - created data set");
// Create a connection
SqlConnection my_Conn;
string str_Conn = "Server=localhost;Database=Test;Trusted_Connection=True;";
my_Conn = new SqlConnection(str_Conn);
// Debug info
Console.WriteLine("Test - create connection");
// Create the command and set its properties.
SqlCommand my_Cmd = new SqlCommand();
my_Cmd.Connection = my_Conn;
my_Cmd.CommandText = "dbo.GetArticlesByPage";
my_Cmd.CommandType = CommandType.StoredProcedure;
// Add parameter 0
SqlParameter my_Parm0 = new SqlParameter();
my_Parm0.ParameterName = "#Tvp";
my_Parm0.SqlDbType = SqlDbType.Structured;
my_Parm0.Direction = ParameterDirection.Input;
my_Parm0.Value = my_Table;
my_Cmd.Parameters.Add(my_Parm0);
// Add parameter 1
SqlParameter my_Parm1 = new SqlParameter();
my_Parm1.ParameterName = "#PageIndex";
my_Parm1.SqlDbType = SqlDbType.Int;
my_Parm1.Direction = ParameterDirection.Input;
my_Parm1.Value = 2;
my_Cmd.Parameters.Add(my_Parm1);
// Add parameter 2
SqlParameter my_Parm2 = new SqlParameter();
my_Parm2.ParameterName = "#PageSize";
my_Parm2.SqlDbType = SqlDbType.Int;
my_Parm2.Direction = ParameterDirection.Input;
my_Parm2.Value = 5;
my_Cmd.Parameters.Add(my_Parm2);
// Add parameter 3
SqlParameter my_Parm3 = new SqlParameter();
my_Parm3.ParameterName = "#PageCount";
my_Parm3.SqlDbType = SqlDbType.Int;
my_Parm3.Direction = ParameterDirection.Output;
my_Parm3.Value = 5;
my_Cmd.Parameters.Add(my_Parm3);
// Open the connection
my_Conn.Open();
// Debug info
Console.WriteLine("Test - execute reader");
// Execute the reader
SqlDataReader my_Reader = my_Cmd.ExecuteReader();
if (my_Reader.HasRows)
{
while (my_Reader.Read())
{
Console.WriteLine("{0}", my_Reader[0].ToString());
}
}
else
{
Console.WriteLine("No rows found.");
}
// Close the reader
my_Reader.Close();
// Number of pages (output after reader - order is important)
Console.WriteLine("Pages = ");
Console.WriteLine(my_Cmd.Parameters["#PageCount"].Value.ToString());
// Close the connection
my_Conn.Close();
// Debug info
Console.WriteLine("Test - close connection");
// Debug info
Console.WriteLine("Test - End");
// Pause to view output
Console.Read();
}
}
}
Here is a snapshot of the correct output from the C# console application.
I have to thank you for your question!
It has been a while since I coded in C#. But like a bike, does not take long to get back on it. The T-SQL examples were done with SSMS 2012 and the C# program was done with VS 2013. The latest and greatest.
Good nite!
I make no claim about efficient or correct -- but readable modern syntax your base query can be written like this:
SELECT ROW_NUMBER() OVER (ORDER BY [dateadded]) AS RowNumber,[desid]
INTO #Results
FROM [DB_user1212].[dbo].[discussions] as d
JOIN [DB_user1212].[dbo].[CategoryMap] as c ON d.desid=c.[Topic Id]
JOIN [dbo].[CategoryIdArray] arr ON c.[Category Id] = arr.CategoryID
WHERE [TopicType]='1'
Here is your solution:
In your stored procedure, in your WHERE statement, you are selecting * from a "TYPE" rather than the actual parameter object being passed in. It is like doing "SELECT * FROM VARCHAR", which makes no sense. Try this:
...
and c.[Category Id] in (
select CategoryId from #dt -- select from the actual parameter, not its TYPE
)
...
Instead of:
workRow["Category"] = cat;
use
workRow["CategoryId"] = cat;
Check in the SQL server management studio if the user has default database set to the database you're trying to access. I had the same type of error and got stuck for days. Finally found out the user had Master set as its' default DB.
Related
I have an ArrayList (in C#) that contains some int numbers (those are IDs in a table), I want to select some data for each number(s) in this ArrayList and return a table variable or a #temporary table :)
I found a solution for passing this ArrayList as an user-defined table type to my stored procedure:
CREATE TYPE [dbo].[integer_list_tbltype] AS TABLE(
[n] [int] NOT NULL,
PRIMARY KEY CLUSTERED ([n] ASC)
WITH (IGNORE_DUP_KEY = OFF)
)
GO
CREATE PROCEDURE [dbo].[Sp_apr_get_apraisors]
(#listNumbers INTEGER_LIST_TBLTYPE readonly)
AS
....
but I didn't find an efficient way to read this array as easily as in C# :(
Is there any way to write a loop for each of these numbers and save data in a temp table and finally return it to C#??
SQL is set based, so your best option is to write a single select statement that would join your input table to the tables containing the data you would like to look up. The select statement would be the result set to be sent back to your application. Then if you want to use straight ADO.Net, you can use the SqlDataReader class to read back into C#, or you could use an ORM like Linq2Sql, Entity Framework, or NHibernate. By the way, if you must do a loop in Sql, please avoid cursors. They are slow and unnecessarily complicated both to manage and to develop. Use a while loop instead.
I would suggest you change the procedure parameter to varchar(n) and then send in those values as comma-delimited string.
DECLARE #IDs VARCHAR(MAX)
SELECT #IDs = '1,2,3'
DECLARE #ID INT
WHILE LEN(#IDs) > 0
BEGIN
SELECT #ID = CONVERT(INT, LEFT(#IDs, CHARINDEX(',', #IDs + ',') -1)
-- Do something with the ID here...
SELECT #IDs = STUFF(#IDs, 1, CHARINDEX(',', #IDs + ','), '')
END
mmmmm :), after 24h (!) search aorund the www , i found my problem Answer, #Toni's answer helped me on this :) Tanx #Toni :*
1) first define stored procedure
CREATE PROCEDURE [spName]( #list_entry VARCHAR(max)=NULL)
AS
BEGIN
SELECT [Column1,column2,...]
FROM [TABLE(s)]
WHERE ( #list_entry IS NULL
OR Column1 IN (SELECT value FROM Fn_split(#list_person, ',')) )
END
2) write a function to split items (comma delimited)
CREATE FUNCTION [dbo].[fn_Split](#text varchar(8000), #delimiter varchar(20) = ' ')
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value varchar(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
GO
3) pass my array as a comma-delimited string from .NET
//defin sample array list or your data
ArrayList array = new ArrayList();
//fill array with some data
for (int i = 1000; i<1010;i++)
array.Add(i);
//define connection and command
using(SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connString"].ConnectionString))
{
connection.Open();
SqlCommand cmd = new SqlCommand("",connection);
cmd.Parameters.AddWithValue("#list_entry", SqlDbType.varchar,8000,Get_comma_delimited_string(array));
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "yourSpName";
cmd.ExecuteNonQuery();
}
/// <summary>
/// Resturns a comma delimited string (sepearte each item in list with ',' )
/// </summary>
public string Get_comma_delimited_string(ArrayList arrayList)
{
string result = string.Empty;
foreach (object item in arrayList)
result += item.ToString() + ",";
return result.Remove(result.Length - 1);
}
I have over a million records in the list. I pass all records at once from table to stored procedure .In stored procedure i have to have iteration to go thorugh all the rows in the table and for each row it takes table row modified date based on jobid and checks if it exist in database and based on it either it updates or insert the record. I feel that my procedure is not correct, would be glad if someone help on this.
foreach (No_kemi no_list in newforSQL)
{
DataTable _dt = new DataTable("table");
_dt.Columns.Add("JobID", typeof(string));
_dt.Columns.Add("CreatedDate", typeof(datetime));
_dt.Columns.Add("ModifiedDate", typeof(datetime));
_dt.Columns.Add("DbDate", typeof(datetime));
_dt.Columns.Add("SubGUID", typeof(string));
_dt.Columns.Add("eType", typeof(string));
// adding over a million records in the table
_dt.Rows.Add(no_list.ID,no_list.CreatedDate,no_list.ModifiedDate,no_list.DbDate,no_list.SubGUID,no_list.eType);
}
using (SqlCommand sqlCommand = new SqlCommand())
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.CommandText = "Process_NO_table";
sqlCommand.Connection = connection;
SqlParameter typeParam = sqlCmd.Parameters.AddWithValue("#track", _dt);
typeParam .SqlDbType = SqlDbType.Structured;
sqlCmd.ExecuteNonQuery();
}
my tabletype and procedure:
CREATE TYPE TrackType AS TABLE
(
t_Id uniqueidentifier, t_JobID nvarchar(50), t_CreatedDate datetime2(7), t_ModifiedDate datetime2(7), t_DbDate datetime2(7)
t_SubGUID nvarchar(MAX), t_eType nvarchar(MAX)
);
GO
ALTER/CREATE PROCEDURE [dbo].[Process_NO_table] // i will change to alter after i create it
#track TrackType READONLY
AS
// i need to iterate all the rows of the table(over a million)
Declare #rows INT
Declare #i int = 0
Declare #count int = (SELECT COUNT(*) FROM #track)
DECLARE #is INT
WHILE (#i < #count)
BEGIN
-- first i check modified date from the database table
SELECT #is = COUNT(*) FROM NO_table WHERE [JobID] IN (SELECT [t_JobID] FROM #track)
MERGE [dbo].[NO_table] AS [Target]
USING #track AS [Source]
-- if the database modifed date is less than the modifeid date from the proceduretable(#track) then it updates the records
ON [Target].[ModifiedDate] < [Source].[t_ModifiedDate] AND JobID = t_JobID
WHEN MATCHED THEN
UPDATE SET [JobID] = [Source].[t_JobID],
[CreatedDate] = [Source].[t_CreatedDate]
[DbDate]= [Source].[t_DbDate]
[ModifiedDate] = [Source].[t_ModifiedDate]
[SubGUID] = [Source].[t_SubGUID]
[eType] = [Source].[t_eType]
-- if the database modifed dateis not existing then it insert the record
MERGE [dbo].[NO_table] AS [Target]
USING #track AS [Source]
ON (#is != 0)
WHEN NOT MATCHED THEN
INSERT INTO [NO_table] ( [JobID], [CreatedDate], [ModifiedDate], [DbDate], [SubGUID], [eType] )
VALUES ( [Source].[t_JobID], [Source].[t_CreatedDate], [Source].[t_ModifiedDate], [Source].[t_DbDate], [Source].[t_SubGUID], [Source].[t_eType] );
SET #i = #i + 1
END
GO
I think you have a large number of syntax errors in your SQL (assuming MS SQL), but your merge condition is probably giving you the invalid syntax near WHERE, because you need to use AND, not WHERE.
ON [Target].[ModifiedDate] < [Source].[t_ModifiedDate] WHERE JobID = t_JobID
should be
ON [Target].[ModifiedDate] < [Source].[t_ModifiedDate] AND JobID = t_JobID
The Select Top 1 and the WHEN MATCHED THEN after the null check for #dbmoddate need to go away as well, as those are also causing syntax issues.
The insert after the null check for #dbmoddate needs a table specified so it actually knows what to insert into.
You also need to end your merge statement with a semicolon.
UPDATED ANSWER:
Now that you have this more cleaned up, I can better see what you're trying to do. At a high level, you want to simply update existing records where the modified date is less than the modified date of on your custom type. If there does not exist a record in your table that does exist in your custom type, then insert it.
With that said, you don't actually need to loop because you aren't doing anything with your loop. What you currently have and what I'm posting below this is all set-based results, not iterative.
You can make this much simpler by getting rid of the merge statements and doing a simple Update and Insert like I have below. The merge would make more sense if your condition between the two statements was the same (i.e. if you didn't have the check for modified date, then merge would be OK) because then you can use the keywords WHEN MATCHED and WHEN NOT MATCHED and have it in one single merge statement. I personally stay away from MERGE statements because they tend to be a little buggy and there are a number of things you have to watch out for.
I think this solution will be better in the long run as it is easier to read and more maintainable...
CREATE TYPE TrackType AS TABLE
(
t_Id uniqueidentifier, t_JobID nvarchar(50), t_CreatedDate datetime2(7), t_ModifiedDate datetime2(7), t_DbDate datetime2(7)
,t_SubGUID nvarchar(MAX), t_eType nvarchar(MAX)
);
GO
CREATE PROCEDURE [dbo].[Process_NO_table] -- i will change to alter after i create it
#track TrackType READONLY
AS
-- i need to iterate all the rows of the table(over a million)
Update [NO_table]
SET [JobID] = T.[t_JobID],
[CreatedDate] = T.[t_CreatedDate],
[DbDate]= T.[t_DbDate],
[ModifiedDate] = T.[t_ModifiedDate],
[SubGUID] = T.[t_SubGUID] ,
[eType] = T.[t_eType]
From #track T
Where [NO_table].[JobID] = T.[t_JobID]
And [NO_table].[ModifiedDate] < T.[t_ModifiedDate]
Insert [NO_Table]
(
[JobID],
[CreatedDate],
[ModifiedDate],
[DbDate],
[SubGUID],
[eType]
)
Select T.[t_JobID],
T.[t_CreatedDate],
T.[t_ModifiedDate],
T.[t_DbDate],
T.[t_SubGUID],
T.[t_eType]
From #track T
Where Not Exists (Select 1 From [NO_table] where T.[t_JobID] = [NO_table].[JobID])
GO
I'm having a real problem getting a date from SQL Server into Oracle while maintaining the correct value.
The value in SQL Server looks like: "Apr 28 1969 12:00AM"
When I pull this value into a .NET DateTime it looks like: "04/28/1969 12:00AM"
When it gets inserted into Oracle it looks like: "28-APR-19"
The Oracle date looks correct at quick glance, but if I do a TO_CHAR(DATE, 'MM/DD/YYYY') I get "04/28/6919" <---- The year is backwards!!
Here's a set of dates from Oracle:
01/18/5919
09/19/8819
02/13/5619
08/30/5819
04/28/6519
08/22/6919
10/24/6119
02/27/6919
02/28/6019
12/20/6219
09/28/3619
10/02/6219
All years end in '19' because they are all backwards!
Because Oracle thinks every one of my years ends with "19" it thinks all leap years are invalid and I have a large set of data I can't even get inserted (not to mention the bad dates)
I'm using a simple stored proc to get the data out of SQL Server, the data is then stored in a simple POCO. I'm using Oracle.DataAccess.OracleBulkCopy to do my actual insert using the DataTable. I have no control over how I retrieve the data or how I save it... but I can manipulate it in between.
So far I've tried returning the date as a string and doing formatting on it (dd-MMM-yyyy) and (yyyymmdd) - neither worked. I've also tried setting the date to null if it was a leap year and trying to set it directly afterwards... it's a hack, but that didn't do any good either.
Any help is appreciated.
My stored proc:
USE [InsuranceFileProcessing]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[P_GET_INSURANCE_POLICY_HOLDERS_FOR_DATA_UPLOAD]
#INSURANCE_COMPANY_CODE VARCHAR(5)
, #INSURANCE_FILE_UPLOAD_LOG_ID INT
, #START_ROW_NUM INT
, #END_ROW_NUM INT
, #GET_LEAP_YEARS BIT
AS
BEGIN
DECLARE #ERROR_NUMBER INT
DECLARE #ERROR_SEVERITY INT
DECLARE #ERROR_STATE INT
DECLARE #ERROR_PROCEDURE NVARCHAR(100)
DECLARE #ERROR_LINE INT
DECLARE #ERROR_MESSAGE NVARCHAR(4000)
SET NOCOUNT ON
BEGIN
BEGIN TRY
SELECT DISTINCT
IT.INSURANCE_COMPANY_CODE INS_COMPANY_NUM
, IP.POLICY_NUMBER INS_POLICY_NUM
, PH.FIRST_NAME PH_FIRST_NAME
, PH.MIDDLE_NAME PH_MIDDLE_NAME
, PH.LAST_NAME PH_LAST_NAME
, LEFT(PH.NAME_SUFFIX,1) PH_NAME_SUFFIX
, PH.ADDRESS PH_ADDRESS
, PH.CITY PH_CITY
, PH.STATE PH_STATE
, PH.ZIPCODE PH_ZIP_CODE
, CONVERT(VARCHAR, PH.DOB, 100) PH_DATE_OF_BIRTH
, PH.GENDER PH_GENDER
, PH.FL_DLN INS_DL_NUMBER
, PH.FED_TIN INS_FEID
, PH.FL_DLN_CROSS_REF FL_DLN_CROSS_REF
, PH.FL_DLN_GENERATED FL_DLN_GENERATED
, PH.NON_STRUCTURED_NAME PH_NON_STRUCT_NAME
, PH.EFFECTIVE_DATE EFFECTIVE_DATE
, ( CASE PH.COMPANY_INDICATOR
WHEN 'Y' THEN 'F'
WHEN 'N' THEN 'T'
ELSE 'T'
END ) PERSONAL_FLAG
--, CONVERT(VARCHAR(12),IP.UPDATE_TS,110) INSERT_TIMESTAMP
, IP.CREATED_TS INSERT_TIMESTAMP
, IDENTITY(INT,1,1) AS ROWNUM
, ph.CUSTOMER_NUMBER CUSTOMER_NUMBER
INTO
#UPLOAD_POLICY_HOLDER
FROM INSURANCE_TRANSACTION IT
INNER JOIN INSURANCE_COMPANIES IC ON IC.INSURANCE_COMPANY_ID = IT.INSURANCE_COMPANY_ID
INNER JOIN INSURANCE_POLICY IP ON IT.INSURANCE_POLICY_ID = IP.INSURANCE_POLICY_ID
INNER JOIN POLICY_HOLDER PH ON IP.INSURANCE_POLICY_ID = PH.INSURANCE_POLICY_ID
WHERE IT.INSURANCE_COMPANY_CODE = #INSURANCE_COMPANY_CODE
AND IT.INSURANCE_FILE_UPLOAD_LOG_ID = #INSURANCE_FILE_UPLOAD_LOG_ID
AND IT.HAS_ERROR = 0
AND PH.HAS_ERROR = 0
AND ((LEFT(REPLACE(CONVERT(VARCHAR(10), DOB, 101), '/', ''), 4) = '0229' AND #GET_LEAP_YEARS = 1)
OR (LEFT(REPLACE(CONVERT(VARCHAR(10), DOB, 101), '/', ''), 4) <> '0229' AND #GET_LEAP_YEARS = 0))
ORDER BY IT.INSURANCE_COMPANY_CODE , IP.POLICY_NUMBER ;
SELECT * FROM #UPLOAD_POLICY_HOLDER
WHERE ROWNUM > #START_ROW_NUM AND ROWNUM <= #END_ROW_NUM
ORDER BY ROWNUM;
IF EXISTS
(
SELECT *
FROM tempdb.dbo.sysobjects
WHERE ID = OBJECT_ID(N'tempdb..#UPLOAD_POLICY_HOLDER')
)
BEGIN
DROP TABLE #UPLOAD_POLICY_HOLDER
END
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
SET #ERROR_NUMBER = ERROR_NUMBER()
SET #ERROR_SEVERITY = ERROR_SEVERITY()
SET #ERROR_STATE = ERROR_STATE()
SET #ERROR_PROCEDURE = ERROR_PROCEDURE()
SET #ERROR_LINE = ERROR_LINE()
SET #ERROR_MESSAGE = ERROR_MESSAGE()
EXEC P_INSERT_SQL_ERROR #ERROR_NUMBER, #ERROR_SEVERITY, #ERROR_STATE, #ERROR_PROCEDURE, #ERROR_LINE, #ERROR_MESSAGE
END CATCH
END
END
You can ignore the Leap_Year stuff - that was a separate attempt I'd made. The PH.DOB field is the one I'm having issues with. Previously I was just returning the DOB field, the CONVERT() was my attempt to get the value as a string so I could have more control.
My insert into Oracle code:
private void LoadDataIntoHSMVDBBulk(DataTable dt, string DestinationTableName, List ColumnMappings)
{
using (var bc = new OracleBulkCopy(GetConnectionString()))
{
bc.DestinationTableName = DestinationTableName;
bc.BulkCopyOptions = OracleBulkCopyOptions.UseInternalTransaction;
foreach (var colmapping in ColumnMappings)
{
var split = colmapping.Split(new[] { ',' });
bc.ColumnMappings.Add(split.First(), split.Last());
}
bc.BulkCopyTimeout = 20000;
bc.BatchSize = GetOracleBulkCountFromConfig();
bc.NotifyAfter = GetOracleBulkCountFromConfig();
bc.OracleRowsCopied += new OracleRowsCopiedEventHandler(bulkCopy_OracleRowsCopied);
bc.WriteToServer(dt);
bc.Close();
bc.Dispose();
dt.Clear();
}
}
I took an old copy of our project from several months ago and the dates are correct!! It seems the culprit is our version of the Oracle.DataAccess. When I point to Oracle 11g the dates are good, when I point to the newer Oracle 12c the dates are inverted. Any help is appreciated, but for now we're rolling the servers back to 11g.
This has been a problem for us as well and could not resolve it.
We've found workaround though which is not pretty but works. And is not a huge time consumer.
We bulkCopy close and after success we do update on Date type columns. Update should not take too long it is a bulk action.
Hope this helps. Cheers!
//EF 5.0.0.
private tables entities = new Tables();
private string validFrom; //try with date as well, should work
private int id;
using (OracleBulkCopy bulkCopy = new OracleBulkCopy(applicationContext.GetConnection(CrmContext.ConnectionEnum.CrmDatabase)))
{
bulkCopy.DestinationTableName = "TABLE";
bulkCopy.BulkCopyTimeout = 180;
bulkCopy.WriteToServer(dataTable);
bulkCopy.Close();
}
//this is a workaroud due to error in Oracle DataAccess Driver
//look at the
entities.Database.ExecuteSqlCommand("UPDATE TABLE SET DATE_FROM = TO_DATE(:p0,'DD.MM.YYYY.') WHERE ID = :p1", validFrom, id);
entities.SaveChanges();
I have a Gridview in front end where Grid have two columns : ID and Order like this:
ID Order
1 1
2 2
3 3
4 4
Now user can update the order like in front end Gridview:
ID Order
1 2
2 4
3 1
4 3
Now if the user click the save button the ID and order data is being sent to Stored Procedure as #sID = (1,2,3,4) and #sOrder = (2,4,1,3)
Now if I want to update the order and make save I want to store it into database. Through Stored procedure how can update into the table so that the table is updated and while select it gives me the results like:
ID Order
1 2
2 4
3 1
4 3
There is no built in function to parse these comma separated string. However, yo can use the XML function in SQL Server to do this. Something like:
DECLARE #sID VARCHAR(100) = '1,2,3,4';
DECLARE #sOrder VARCHAR(10) = '2,4,1,3';
DECLARE #sIDASXml xml = CONVERT(xml,
'<root><s>' +
REPLACE(#sID, ',', '</s><s>') +
'</s></root>');
DECLARE #sOrderASXml xml = CONVERT(xml,
'<root><s>' +
REPLACE(#sOrder, ',', '</s><s>') +
'</s></root>');
;WITH ParsedIDs
AS
(
SELECT ID = T.c.value('.','varchar(20)'),
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS RowNumber
FROM #sIDASXml.nodes('/root/s') T(c)
), ParsedOrders
AS
(
SELECT "Order" = T.c.value('.','varchar(20)'),
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS RowNumber
FROM #sOrderASXml.nodes('/root/s') T(c)
)
UPDATE t
SET t."Order" = p."Order"
FROM #tableName AS t
INNER JOIN
(
SELECT i.ID, p."Order"
FROM ParsedOrders p
INNER JOIN ParsedIDs i ON p.RowNumber = i.RowNumber
) AS p ON t.ID = p.ID;
Live Demo
Then you can put this inside a stored procedure or whatever.
Note that: You didn't need to do all of this manually, it should be some way to make this gridview update the underlying data table automatically through data binding. You should search for something like this instead of all this pain.
You could use a table valued parameter to avoid sending delimiter-separated values or even XML to the database. To do this you need to:
Declare a parameter type in the database, like this:
CREATE TYPE UpdateOrderType TABLE (ID int, Order int)
After that you can define the procedure to use the parameter as
CREATE PROCEDURE UpdateOrder (#UpdateOrderValues UpdateOrderType readonly)
AS
BEGIN
UPDATE t
SET OrderID = tvp.Order
FROM <YourTable> t
INNER JOIN #UpdateOrderValues tvp ON t.ID=tvp.ID
END
As you can see, the SQL is trivial compared to parsing XML or delimited strings.
Use the parameter from C#:
using (SqlCommand command = connection.CreateCommand()) {
command.CommandText = "dbo.UpdateOrder";
command.CommandType = CommandType.StoredProcedure;
//create a table from your gridview data
DataTable paramValue = CreateDataTable(orderedData)
SqlParameter parameter = command.Parameters
.AddWithValue("#UpdateOrderValues", paramValue );
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.UpdateOrderType";
command.ExecuteNonQuery();
}
where CreateDataTable is something like:
//assuming the source data has ID and Order properties
private static DataTable CreateDataTable(IEnumerable<OrderData> source) {
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(int));
table.Columns.Add("Order", typeof(int));
foreach (OrderData data in source) {
table.Rows.Add(data.ID, data.Order);
}
return table;
}
(code lifted from this question)
As you can see this approach (specific to SQL-Server 2008 and up) makes it easier and more formal to pass in structured data as a parameter to a procedure. What's more, you're working with type safety all the way, so much of the parsing errors that tend to crop up in string/xml manipulation are not an issue.
You can use charindex like
DECLARE #id VARCHAR(MAX)
DECLARE #order VARCHAR(MAX)
SET #id='1,2,3,4,'
SET #order='2,4,1,3,'
WHILE CHARINDEX(',',#id) > 0
BEGIN
DECLARE #tmpid VARCHAR(50)
SET #tmpid=SUBSTRING(#id,1,(charindex(',',#id)-1))
DECLARE #tmporder VARCHAR(50)
SET #tmporder=SUBSTRING(#order,1,(charindex(',',#order)-1))
UPDATE dbo.Test SET
[Order]=#tmporder
WHERE ID=convert(int,#tmpid)
SET #id = SUBSTRING(#id,charindex(',',#id)+1,len(#id))
SET #order=SUBSTRING(#order,charindex(',',#order)+1,len(#order))
END
I would like to know something .
I try to retrieve 2 files .
One is register for a group 2 , and one for a group 10 .
So the field is Files.Group .
One user is register to the group 1 and the group 10.
This is the query I use to retrieve files .
SELECT Files.Id, Files.Name, Files.Date, Files.Path, Files.[Group] FROM Files WHERE Files.[Group] = " + param + "ORDER BY Files.Id DESC"
Param is a cookie who get the group, creating a chain like this 2|10 .
This doesn't work actually.. And i don't know how can I pass in the query the two groups. Should I separate them by a coma ? like Files.Group = 2,10 ?
Or is it something else ? To pass 2 parameters ?
Baseline Structure
I don't have your entire structure so I have created the following simplified version of it:
CREATE TABLE [dbo].[Files]
(
[ID] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] NVARCHAR(64) NOT NULL,
[Group] INT NOT NULL -- Probably have a non-unique index over this.
);
GO
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 1', 1);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 2', 2);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 3', 3);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 4', 2);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 5', 3);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 6', 5);
Temp Table
You can insert the split values into a temp table and use a WHERE EXISTS against it - probably yielding decent performance.
-- This would be passed in from C#.
DECLARE #GroupsParam NVARCHAR(64) = N'2|3';
-- This is your SQL command, possibly a SPROC.
DECLARE #GroupsXML XML = N'<split><s>' + REPLACE(#GroupsParam, N'|', N'</s><s>') + '</s></split>';
-- Create an in-memory temp table to hold the temp data.
DECLARE #Groups TABLE
(
[ID] INT PRIMARY KEY
);
-- Insert the records into the temp table.
INSERT INTO #Groups ([ID])
SELECT x.value('.', 'INT')
FROM #GroupsXML.nodes('/split/s') as records(x);
-- Use a WHERE EXISTS; which should have extremely good performance.
SELECT [F].[Name], [F].[Group] FROM [dbo].[Files] AS [F]
WHERE EXISTS (SELECT 1 FROM #Groups AS [G] WHERE [G].[ID] = [F].[Group]);
Table-Values Parameters (SQL 2008+ Only)
SQL 2008 has a neat feature where you can send tables as parameters to the database. Clearly this will only work if you are using SqlCommands correctly (Executing Parameterized SQL Statements), unlike your example (appending user-created values to a SQL string is extremely bad practice - learn how to use parameters) - as you need to pass in a DataTable which you can't do with a simple string value.
In order to use this you first need to create the value type:
CREATE TYPE [dbo].[IntList] AS TABLE
([Value] INT);
GO
Next we will do things properly and used a stored procedure - as this is a static query and there are some performance implications of using a sproc (query plan caching).
CREATE PROCEDURE [dbo].[GetFiles]
#Groups [dbo].[IntList] READONLY
AS BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT [F].[Name], [F].[Group] FROM [dbo].[Files] AS [F]
WHERE EXISTS (SELECT 1 FROM #Groups AS [G] WHERE [G].[Value] = [F].[Group]);
END
GO
Next we need to hit this from C#, which is pretty straight-forward as we can create a table to do the call.
public static void GetFilesByGroups(string groupsQuery)
{
GetFilesByGroups(groupsQuery.Split('|').Select(x => int.Parse(x)));
}
public static void GetFilesByGroups(params int[] groups)
{
GetFilesByGroups((IEnumerable<int>)groups);
}
public static void GetFilesByGroups(IEnumerable<int> groups)
{
// Create the DataTable that will contain our groups values.
var table = new DataTable();
table.Columns.Add("Value", typeof(int));
foreach (var group in groups)
table.Rows.Add(group);
using (var connection = CreateConnection())
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[dbo].[GetFiles]";
// Add the table like any other parameter.
command.Parameters.AddWithValue("#Groups", table);
using (var reader = command.ExecuteReader())
{
// ...
}
}
}
Remember: Table-Valued Parameters are only supported on SQL 2008 and later.
Edit: I would like to point out that there is likely a cross-over point in terms of performance between dknaack's answer and the temp table approach. His will likely be faster for a small set of search-groups; where the temp table approach would probably be faster for a large set of search-groups. There is a possibility that table-valued parameters would nearly always be faster. This is all just theory based on what I know about how the SQL query engine works: temp table might do a merge or hash join where the TVP would hopefully do a nested loop. I haven't done any profiling (and haven't received enough upvotes to motivate me to do so) so I can't say for certain.
Description
You should use SqlParameter to prevent Sql injections. Use the IN Statetment to pass in a comma seperated list of you group ids.
Sample
// value from cookie
string groups = "2,10,99";
// Build where clause and params
List<string> where = new List<string>();
List<SqlParameter> param = new List<SqlParameter>();
foreach(string group in groups.Split(','))
{
int groupId = Int32.Parse(group);
string paramName = string.Format("#Group{0}", groupId);
where.Add(paramName);
param.Add(new SqlParameter(paramName, groupId));
}
// create command
SqlConnection myConnection = new SqlConnection("My ConnectionString");
SqlCommand command = new SqlCommand("SELECT Files.Id, Files.Name, Files.Date, " +
"Files.Path, Files.[Group] " +
"FROM Files " +
"WHERE Files.[Group] in (" + string.Join(",", param) + ")" +
"ORDER BY Files.Id DESC", myConnection);
command.Parameters.AddRange(param.ToArray());
More Information
MSDN - IN (Transact-SQL)
C# SqlParameter Example
You're probably (depending on your database) looking at using this:
IN (2, 10)
rather than an = operator.
Note that constructing SQL using string concatenation like this can expose your code to SQL injection vulnerabilities, and using a properly parameterised SQL query is generally better practice. However, in your case, where you have an indeterminate number of parameters, it is harder to achieve in practice.
You need to set Param in cookie to create a chain like 2,10.
Then, instead of using = you need to use in () like this:
SELECT Files.Id, Files.Name, Files.Date, Files.Path, Files.[Group] FROM Files WHERE Files.[Group] in (" + param + ") ORDER BY Files.Id DESC"
Another thing that you got wrong was missing a space in param + "ORDER part.