Normally I would have code to post. However, I'm not sure where to start on this one. Maybe I am incorrect but this request is a little different than most (from clients). So, they are needing a new feature added to their page. They want to be able to run a report which shows all employees total hours for a given set of weeks. They will use a DatePicker to select a start date and another to select and end date. Then, click a button to run the report. This report will have the employee name as the first column and then each column after that will have a header which is a date. So if they selected 8-5-2017 and 8-19-2017 the column headers will be 8-5-2017| 8-12-2017| 8-19-2017. Underneath those columns will be the total hours each employee worked for that given week.
I've attached an image of what I am needing to do. Hopefully this will provide additional clarification.
I've had a similar situation and managed to solve it by using a DataTable that contained my data. A simple way to fill your DataTable would be by creating a stored procedure.
In my example I'll simply do it for every day between 2 given dates, you should be able to change this code to have it work for once every 7 days, starting on a monday/sunday, depending on what your requirement is. If you need help with that as well, just leave a comment and I'll see what I can do if I find some time.
CREATE TYPE [dbo].[DayTableType] AS TABLE(
[Datum] [date] NOT NULL,
[DayCol] [int] NOT NULL
)
GO
CREATE PROCEDURE [dbo].[MyStoredProcedure]
-- Add the parameters for the stored procedure here
#d1 datetime, #d2 datetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET DATEFIRST 1;
-- Insert statements for procedure here
DECLARE #StartDate date = CONVERT(date, #d1);
DECLARE #EndDate date = CONVERT(date, #d2);
DECLARE #Days dbo.DayTableType;
WITH cte AS (
SELECT #StartDate AS Datum
UNION ALL
SELECT DATEADD(day,1,Datum) as Datum
FROM cte
WHERE DATEADD(day,1,Datum) <= #EndDate
)
INSERT INTO #Days
SELECT cte.Datum, DATEPART(DAY, cte.Datum)
FROM cte
OPTION(MAXRECURSION 0);
DECLARE #dPivot NVARCHAR(MAX)
SELECT #dPivot = ''
SELECT #dPivot = #dPivot + '[' + CONVERT(VARCHAR(8),d.Datum,3) + '],' FROM #Days d
SELECT #dPivot = SUBSTRING(#dPivot,1,LEN(#dPivot)-1)
DECLARE #SQLSTR NVARCHAR(MAX);
SET #SQLSTR =
'SELECT p.EmployeeId
,p.LastName
,p.FirstName
,'+ #dPivot + '
FROM (SELECT CONVERT(VARCHAR(8),d.Datum,3) AS DayCol
,e.EmployeeId
,e.LastName
,e.FirstName
,ISNULL((SELECT SUM(w.WorkedHours) FROM dbo.[EmployeeWorkDateTable] w
WHERE w.EmployeeId = e.EmployeeId AND CONVERT(DATE, w.WorkDate) = CONVERT(DATE, d.Datum)),0) AS DayInfo
FROM #Days d
LEFT OUTER JOIN dbo.[EmployeeTable] e ON e.IsActive = 1
) t
PIVOT (
MAX(t.DayInfo)
FOR t.DayCol IN (' + #dPivot + ')
) AS p
ORDER BY p.LastName'
EXECUTE SP_EXECUTESQL #SQLSTR, N'#Days DayTableType READONLY, #StartDate DATE, #EndDate DATE', #Days, #StartDate, #EndDate
END
This should return a DataTable that looks a lot like what you've posted in your screenshot.
To have this display correctly into your GridView you have need to first get this in your ViewModel as followed:
private DataTable _myDataTable;
public DataTable MyDataTable
{
get { return _myDataTable; }
set { SetProperty(ref _myDataTable, value); }
}
public void FillMyDataTable(DateTime startDate, DateTime endDate)
{
using (var conn = new SqlConnection("MyConnectionString"))
using (var cmd = new SqlCommand("MyStoredProcedure", conn) { CommandType = System.Data.CommandType.StoredProcedure })
{
cmd.Parameters.AddWithValue("#d1", startDate);
cmd.Parameters.AddWithValue("#d2", endDate);
var da = new SqlDataAdapter(cmd);
var ds = new DataSet();
da.Fill(ds);
MyDataTable = ds.Tables[0];
}
}
Now you should simply bind your GridView itemsource to the DefaultView property of your DataTable and have the AutoGenerateColumns="True", you can edit the column names and other stuff even more by writing your custom emplementation of AutoGeneratingColumn
<telerik:RadGridView x:Name="MyGridView"
AutoGenerateColumns="True"
ItemsSource="{Binding MyDataTable.DefaultView}"
AutoGeneratingColumn="MyGridView_OnAutoGeneratingColumn">
</telerik:RadGridView>
I hope this helps you out with your problem. Feel free to leave a comment if you have any more questions.
Related
I have been using a Stored Procedure created by our DB guy, who happens to be out of town for the next week. The SP used to work, but before he left, the DB guy edited the SP, causing my code to throw a server error: "Column name or number of supplied values does not match table definition". He claimed before he left the SP should mainly be the same, so I'm at a loss for why it's no longer matching.
Here is the c# code:
System.Data.SqlClient.SqlCommand objCmd = new
System.Data.SqlClient.SqlCommand("dci.webDonorStatistics", objConn);
objCmd.CommandTimeout = 950;
objCmd.CommandType = System.Data.CommandType.StoredProcedure;
objCmd.Parameters.Add(new
System.Data.SqlClient.SqlParameter("#FiscalYear", 2018));
GridView1.DataSource = objCmd.ExecuteReader();
GridView1.DataBind();
Here is the declaration of the SP:
ALTER PROCEDURE [dci].[webDonorStatistics] #FiscalYear INT
AS
BEGIN
--DECLARE #FiscalYear INT = 2018
DECLARE
#StartDate DATE = CONVERT(DATE, '01-OCT-' + CONVERT(CHAR(4), #FiscalYear - 1))
, #EndDate DATE = DATEADD(DAY, -1, GETDATE())
IF #EndDate >= CONVERT(DATE, '30-SEP-' + CONVERT(CHAR(4), #FiscalYear))
SELECT #EndDate = CONVERT(DATE, '30-SEP-' + CONVERT(CHAR(4), #FiscalYear))
ELSE
SELECT #EndDate = DATEADD(DAY, -1, CONVERT(DATE, '01-' + DATENAME(MONTH, GETDATE()) + '-' + CONVERT(CHAR(4), YEAR(GETDATE())))) -- End of previous month
IF DATEDIFF(DAY, #StartDate, #EndDate) < 0
SELECT #EndDate = GETDATE()
BEGIN TRY
DROP TABLE #webDonorStatistics
END TRY
BEGIN CATCH
END CATCH
Any help would be greatly appreciated. Thanks in advance.
First step is to isolate your error. Is your error thrown from your application code or database code?
If you follow 3Dave's suggestion, what do you get? Assuming you are pointed to the correct database server. Try running:
EXEC [dci].[webDonorStatistics] 2018
If the above call does not return any error, I would check the application code.
Your Error indicates that the data which you are trying to insert into the table is not valid, You would have to right-click on the stored procedure and click on execute stored procedure where you can insert the parameter for the fiscal year, The execution should fail. Then create a copy of same stored procedure with a different name and change the data type for the fiscal year and see if that would fix the issue and give you the result when you execute the stored procedure. Also on the Stored Proc could which you have I dont see any insert commands if there are any try checking the data type of what you are trying to insert and the data type of what is present inside the table schema if that doesn't match then you can change accordingly .
I have a table in SQL called EmpLog which contains the 'start' and 'end' data for employees. I'm quite confused as to how I'm going to change the variables within the trigger during update since it has to cater to all different employees. Do I need to alter the trigger via sql parameter in c# for every insertion? or is there an alternative to keep it minimal and efficient? Thank you in advance. Since as far as I'm able to understand triggers are hard coded onto the database, which is changeable through 'ALTER'. What can I do to change existing row's with different IDs via trigger?
create trigger TrgEmpLog on EmpLog
AFTER UPDATE
AS
declare #shiftstart time;
declare #shiftend time;
declare #totalminutes decimal(18,2);
IF EXISTS (SELECT shiftend FROM EmpLog WHERE EmpID = "C# Variable" and CONVERT(date,LogDate) = CONVERT(date, GETDATE()) and shiftend is not null)
BEGIN
IF EXISTS (SELECT EmpLog.TotalMinutes from EmpLog WHERE EmpID = "C# Variable" and CONVERT(date,LogDate) = CONVERT(date, GETDATE()) and TotalMinutes is not null)
BEGIN
ROLLBACK TRANSACTION
END
ELSE
select #shiftstart=EmpLog.ShiftStart from EmpLog where EmpID = "C# Variable" and CONVERT(date,LogDate) = CONVERT(date, GETDATE()) and TotalMinutes IS NULL;
select #shiftend=EmpLog.ShiftEnd from EmpLog where EmpID = "C# Variable" and CONVERT(date,LogDate) = CONVERT(date, GETDATE()) and TotalMinutes IS NULL;
select #totalminutes=DATEDIFF(MINUTE,#shiftstart, #shiftend);
UPDATE EmpLog
SET TotalMinutes=#totalminutes/60.00
WHERE EmpID= "C# Variable"and CONVERT(date,LogDate) = CONVERT(date, GETDATE());
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
And the code that I'm using to prompt the trigger is:
UPDATE EmpLog
SET ShiftEnd = 'current time'
WHERE EmpID='C# variable' and CONVERT(date,LogDate) = CONVERT(date, GETDATE());
This code below ends in a trigger, but it works when I remove the 'and logDate = getdate().
UPDATE EmpLog SET ShiftEnd = '09:00:00' WHERE EmpID = 1 and CONVERT(date, LogDate) = CONVERT(date, GETDATE())
This code below worked for me with some quick testing. Maybe it will work for what you need. The actual trigger part is probably all you really need, the rest I was using to test.
The way this is written, it will only update the current, and relevant record (the one being updated at the time). Joining to the inserted table makes sure that happens. Use the inserted and deleted Tables (MSDN)
CREATE TABLE EmpLog (EmpID int, ShiftStart time, Shiftend time, LogDate date, TotalMinutes decimal(18,2));
GO
-- Trigger starts here
CREATE TRIGGER TrgEmpLog ON EmpLog
AFTER UPDATE
AS
IF EXISTS (SELECT *
FROM EmpLog A JOIN inserted B
ON A.EmpID = B.EmpID
WHERE CONVERT(date, A.LogDate) = CONVERT(date, GETDATE())
AND A.shiftend IS NOT NULL)
BEGIN
IF EXISTS (SELECT *
FROM EmpLog A JOIN inserted B
ON A.EmpID = B.EmpID
WHERE CONVERT(date, A.LogDate) = CONVERT(date, GETDATE())
AND A.TotalMinutes IS NOT NULL)
BEGIN
ROLLBACK TRANSACTION
END
UPDATE EmpLog
SET TotalMinutes=DATEDIFF(MINUTE, A.ShiftStart, A.Shiftend)/60.00
FROM EmpLog A JOIN inserted B
ON A.EmpID = B.EmpID;
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
GO
-- Trigger End here
INSERT INTO EmpLog (EmpID, ShiftStart, Shiftend, LogDate) VALUES (1, '00:00:00', NULL, GETDATE()),
(2, '08:00:00', NULL, GETDATE()),
(3, '16:00:00', NULL, GETDATE());
SELECT * FROM EmpLog;
UPDATE EmpLog SET Shiftend = '08:00:00' WHERE EmpID = 1;
SELECT * FROM EmpLog;
DROP TABLE EmpLog;
You may have to play around with some of the IF EXISTS, if they don't follow you logic exactly.
As #massimiliano mentioned in the comments, triggers can get quite complicated if you want them to. For me, I avoid that. The trigger is always one of the last places I think to look when troubleshooting issues. Personal preference!
Good Luck!
SELECT Col1, Col2, Col3, Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 BETWEEN #startDate AND #endDate
AND Mail LIKE #email
AND def IN (CASE #InvoiceMethod //Problem is Here
WHEN ''
THEN def
ELSE (#InvoiceMethod)
END)
A piece of code from the stored procedure. If am executing this, it's not returning any rows, even though it has some to return. Problem is with the IN clause, if I didn't pass anything to IN clause i.e #InvoiceMethod is null, then I'm getting rows.
If I pass anything to #InvoiceMethod, I'm not getting any rows.
The value in #InvoiceMethod is = 'A','B'
I tried many combinations like 'A','B' or "A","B" without any results.
How to pass values to IN clause please? In which format?
Please help me out of this.
Modified the stored procedure to the following,
Declare #tmpt table (value nvarchar(5) not null)
SET #InvoiceCount=(select COUNT(*) from dbo.fnSplit(#InvoiceMethod, ','))
SET #tempVar=1;
WHILE #tempVar<=(#InvoiceCount)
BEGIN
INSERT INTO #tmpt (value)
VALUES (#InvoiceMethod);//Here i need to insert array of values to temp table.like invoicemethod[0],invoicemethod[1]&invoicemethod[2] depends on #InvoiceCount
SET #tempVar=#tempVar+1;
END
--DECLARE #tmpt TABLE (value NVARCHAR(5) NOT NULL)
--INSERT INTO #tmpt (value) VALUES (#InvoiceMethod);
SELECT Col1,Col2,Col3,Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 between #startDate AND #endDate
AND Mail LIKE #email
AND def IN (SELECT value FROM #tmpt)
But not getting the results as expected :(
IMO this isn't a good way to approach this problem, by passing a list of filter values for a column in a comma separated string, as this is almost encouraging a Dynamic Sql approach to the problem (i.e. where you EXEC a built Sql string which pastes in the #InvoiceMethod as a string).
Instead, Sql 2008 has Table Valued Parameters, (and prior to this, you could use Xml), which allows you to pass structured data into a procedure in a table format.
You then just need to join to this table parameter to effect the 1..N valued IN () filtering.
CREATE TYPE ttInvoiceMethods AS TABLE
(
Method VARCHAR(20)
);
GO
CREATE PROCEDURE dbo.SomeProc
(
#InvoiceMethod ttInvoiceMethods READONLY, -- ... Other Params here
)
AS
begin
SELECT Col1, Col2, ...
FROM Table1
INNER JOIN #InvoiceMethod
ON Table1.def = #InvoiceMethod.Method -- Join here
WHERE User1 = #Owner
... Other Filters here
END
Have a look here for a similar solution with a fiddle.
Edit
The optional parameter (#InvoiceMethod = '') can be handled by changing the JOIN to the TVP with a subquery:
WHERE
-- ... Other filters
AND (Table1.def IN (SELECT Method FROM #InvoiceMethod))
OR #InvoiceMethod IS NULL)
To Initialize a TVP to NULL, just don't bind to it in C# at all.
I think a variable represetning multiple values with comma is not allowed in the in clause. You should either use string fiunctions (split and join) or go with the temp table solution. I prefer the second.
Use a temporary table to store your values and then pass it to your in statement
DECLARE #tmpt TABLE (value NVARCHAR(5) NOT NULL)
INSERT INTO #tmpt .........
...
...
SELECT Col1,Col2,Col3,Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 BETWEEN #startDate AND #endDate
AND Mail LIKE #email
AND def IN (SELECT value FROM #tmpt)
Used Splitfunctions to resolve the issue,Modified SQL Query
SELECT Col1, Col2, Col3, Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 BETWEEN #startDate AND #endDate
AND Mail LIKE #email
AND def IN (SELECT * FROM sptFunction(#InvoiceMethod,',')) //Problem is Here (Solved by using split functions)
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