Plain sql query doesn't insert - c#

I wanna Insert values into my DB using a plain SQL query.
I don't get an error message, just nothing gets inserted.
I even checked to see if the values were in the variables, but they were fine.
excelDate is a Datetime
excelDatePlusTimeSpan is a DateTime
formattedDate is a string
object[] paramItems = new object[]
{
new SqlParameter("#ExcelDate", excelDate),
new SqlParameter("#ExcelDatePlusTimeSpan", excelDatePlusTimeSpan),
new SqlParameter("#FormattedDate", formattedDate),
};
int items = db.Database.ExecuteSqlRaw(#$"
INSERT INTO DarsMissingSale (ProductNr, MissingSystem)
SELECT Product_NR Product, 'Dars' AS MissingSystem
From PRODUCTSALE PS
JOIN VARIATION V ON PS.VARIATION_CODE = V.VARIATION_CODE
LEFT JOIN DarsSales DS
ON PS.PRODUCT_NR = DS.IdentificationNumber
WHERE V.PRODUCT_TYPE = 3 and (DS.IdentificationNumber is Null) AND (ORDER_DATE LIKE '#FormattedDate' OR (PurchaseDate BETWEEN #ExcelDate AND #ExcelDatePlusTimeSpan))
", paramItems);
I don't know what I am doing wrong. I followed the documentation on this website.
Maybe there is a problem with the # placeholders. But I don't think so.

INSERT INTO...SELECT will silently insert zero rows if the SELECT portion returns no data. You have a few places that could cause no data in the SELECT such as the first JOIN and the WHERE clause.
Run just the SELECT portion of your SQL by itself in a SQL client/IDE, find out why that is returning zero rows, then fix it and put it back into your INSERT INTO ... SELECT code.

It didn't work because the ' ' on the '#FormattedDate' placeholder weren't needed.
object[] paramItems = new object[]
{
new SqlParameter("#ExcelDate", excelDate),
new SqlParameter("#ExcelDatePlusTimeSpan", excelDatePlusTimeSpan),
new SqlParameter("#FormattedDate", formattedDate),
};
int items = db.Database.ExecuteSqlRaw(#$"
INSERT INTO DarsMissingSale (ProductNr, MissingSystem)
SELECT Product_NR Product, 'Dars' AS MissingSystem
From PRODUCTSALE PS
JOIN VARIATION V ON PS.VARIATION_CODE = V.VARIATION_CODE
LEFT JOIN DarsSales DS
ON PS.PRODUCT_NR = DS.IdentificationNumber
WHERE V.PRODUCT_TYPE = 3 and (DS.IdentificationNumber is Null) AND (ORDER_DATE LIKE #FormattedDate OR (PurchaseDate BETWEEN #ExcelDate AND #ExcelDatePlusTimeSpan))
", paramItems);

Related

Passing a list of int to dapper with mySQL

I have a current working solution that is doing a new db call for each project Id in a list and I am trying to do a single call instead that returns data from multiple projects.
To do this I am trying to pass a list of project Id's into a Dapper Query that hits a MySQL database. I either get an error of operand should contain 1 column(s) or I get the first result back and not one per projectId that is in the database.
The current c# code I am using is
public List<ProjectPortalManager> GetPPTech(IEnumerable<int> projIds)
{
string sql = #"SELECT tProject.ProjectID,
tProject.ProjectName,
tProject.PMUserID,
if(cast(tproject.dateinit as char) = '0000-00-00 00:00:00',null,tproject.dateinit) as DateInit,
tproject.comments,
tproject.ProjectNumber,
c.LName,
c.FName,
c.orgid,
c.orgname as organization,
c.Email,
c.Phone
From tProject left Join tContacts c on tProject.PMUserID = c.UserId
Where tProject.ProjectID in (#ProjIds);";
try
{
List<ProjectManager> pms = Conn.Query<ProjectManager>(sql, new { ProjIds = new[] { projIds } }).ToList();
return pms;
}
catch (Exception ex)
{
ErrorReport.ReportError(ex);
}
return new List<ProjectPortalManager>();
}
This does not error out but returns 0 results. When running the query in MySQL Workbench I do get one result back. However I am expecting several results. The SQL I run in workbench is:
SET #projIds = ('28, 99, 9');
SELECT tProject.ProjectID,
tProject.ProjectName,
tProject.PMUserID,
if(cast(tproject.dateinit as char) = '0000-00-00 00:00:00',null,tproject.dateinit) as DateInit,
tproject.comments, tproject.ProjectNumber,
c.LName,
c.FName,
c.orgid,
c.orgname as organization,
c.Email,
c.Phone
From tProject left Join tContacts c on tProject.PMUserID = c.UserId
where tProject.ProjectID IN (#projIds);
I have verified that all the Id numbers used do exists in the database.
There seems to be conflicting information online about how to do this but I have not found a solution that seems to work.
Don't put parentheses around the IN if you want Dapper to expand it to a list of parameters and populate them
where tProject.ProjectID IN #PIDs
Suppose you'd passed an array of size 3 in new { PIDs = projIds.ToArray() } - Dapper would effectively transform your SQL to:
where tProject.ProjectID IN (#PIDs1, #PIDs2, #PIDs3)
then behave as if you'd passed new { PIDs1 = projIds[0], PIDs2 = projIds[1], PIDs3 = projIds[2] }

Insert large array into a SQL Server table variable with Dapper

I have an integer list and I'm trying to insert those values into a table variable, declared with DECLARE statement, using Dapper. I've tried several combinations, but ultimately it leads to Incorrect syntax near ',' error.
Can Dapper even differentiate between a local variable and a Dapper query param, both being with # prefix?
Fiddle
List<int> output = null;
List<int> input = new List<int>
{
1, 2, 3
};
var sql = #"DECLARE #tempTable TABLE (Id INT)
INSERT INTO #tempTable VALUES (#Ids);
SELECT * FROM #tempTable;";
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServer()))
{
output = connection.Query<int>(sql, new { Ids = input }).ToList();
}
Note that the input list can be bigger than 1000.
Dapper will transform the list into seperate parameters, suitable for inclusion in an IN predicate, but not an INSERT ... VALUES query. Instead pass the values as JSON, which is also much, much cheaper for large lists than using separate parameters. EG
List<int> output = null;
List<int> input = new List<int>
{
1, 2, 3
};
var sql = #"
DECLARE #tempTable TABLE (Id INT)
INSERT INTO #tempTable(id) select value from openjson( #Ids );
SELECT * FROM #tempTable;";
var inputJson = System.Text.Json.JsonSerializer.Serialize(input);
using (var con = new SqlConnection("server=localhost;database=tempdb;integrated security=true;trust server certificate=true"))
{
output = con.Query<int>(sql, new { Ids = inputJson }).ToList();
}

SQL query returns value in SSMS, value is DBNull in DataTable

I have this query that I crafted in SQL Server Management Studio to get the definition of a stored procedure in a database.
Select [definition], [uses_ansi_nulls], [uses_quoted_identifier],
[is_schema_bound], [uses_database_collation], [is_recompiled],
[null_on_null_input], [execute_as_principal_id],
[uses_native_compilation]
From [sys].[sql_modules]
Where [object_id] = #object_Id
In SSMS, if I Declare #object_Id int = <storedproc's object_id> and run it, I get back the definition value (the SQL code) and the various other attributes.
In my app, I have this code. The above SQL query is a resource for the project.
static void RenderDefinition(int objectId, XmlElement parentElement)
{
var dt = new DataTable();
using (var da = new SqlDataAdapter(Resources.Commands.GetDefinition, connectionString))
{
da.SelectCommand.Parameters.Add("#object_Id", SqlDbType.Int).Value = objectId;
da.Fill(dt);
}
var row = dt.Rows[0];
// Create object element
var e = parentElement.AppendChild(parentElement.OwnerDocument.CreateElement("definition")) as XmlElement;
foreach (DataColumn col in dt.Columns)
{
var value = row.ToXmlString(col);
if (string.IsNullOrWhiteSpace(value)) continue;
if (col.ColumnName == "definition")
{
// The SQL code
e.InnerText = value; // <-- Value is always DBNull, never the actual value
}
else
{
// Other defining attributes
e.SetAttribute(col.ColumnName, value);
}
}
}
This generates the following XML:
<definition uses_ansi_nulls="true" uses_quoted_identifier="true" is_schema_bound="false" uses_database_collation="false" is_recompiled="false" null_on_null_input="false" uses_native_compilation="false"></definition>
I've tried the query as is. I've tried converting the definition column to a VARCHAR(MAX) and to a VARCHAR(8000).
Any thoughts?
try selecting from different sys table. E.g. :
SELECT DISTINCT SCHEMA_NAME(o.schema_id),o.name,c.[text]
FROM syscomments AS c
INNER JOIN sys.objects AS o ON c.id = o.[object_id]
INNER JOIN sys.schemas AS s ON o.schema_id = s.schema_id
WHERE object_id = <your id>
another query that might help you:
SELECT so.[name], m.[definition]
FROM sys.objects as so
inner join sys.sql_modules as m on m.object_id = so.object_id
WHERE so.[type] = 'P' AND so.object_id = <your id>;
here m.[definition] doesn't have length meaning it is as big as possible. In case it will return several rows for the same SP you will need to add another loop to your application and concatenate strings.
Let me know if it works
It turns out that #DaveBrown had the right answer: It was a permissions issue, though it wasn't behaving in the way I thought it would.
I added LEN([definition]) to the query. The thought being that if it was problem in ADO.Net pulling down a 24,000 character sproc definition, I'd still get the character count of the definition. If it returned a NULL value, then it was something going on in SQL Server.
Select [definition], LEN([definition]) [definition_length], [uses_ansi_nulls], [uses_quoted_identifier],
[is_schema_bound], [uses_database_collation], [is_recompiled],
[null_on_null_input], [execute_as_principal_id],
[uses_native_compilation]
From [sys].[sql_modules]
Where [object_id] = #object_Id
When I stopped at a break point, LEN([definition]) was null.
Turns out, someone who remain nameless to protect the guilty, does not have the same permissions in the environment we want to document as she has in the initial environment. The code works as written in one of the environments where I have authorial permissions for stored procedures and other other modules.
This ultimately comes down to an unexpected behavior due to permissions. I expected for SQL Server to raise an error when trying to query the definition rather than return null.

Loop over and run SQL query several thousand times

I've read several dozen posts, many dating back years, and cannot come up with a modern, safe and reliable way to update a special value in several thousand records as a single query.
I loop over all the records in the table, determine a DateTime value based on some special logic and then run this simple query to update that value... over 3500 times. That's a lot of trips over the wire.
UPDATE ScheduleTickets
SET ScheduledStartUTC = #ScheduledStartUTC
WHERE ScheduleId = #ScheduleId AND PatchSessionId = #PatchSessionId
I've seen comments to not waste memory by saving to and using a DataTable. I've seen solutions that use a StringBuilder to dynamically create an update query but that feels insecure/dirty. Sure, the entire process takes less than a minute but there must be a better way.
So, after figuring out the DateTime value, I call...
UpdateScheduleTicketStart(ScheduleId, PatchSessionId, scheduledDateTime);
Which looks like this...
private static void UpdateScheduleTicketStart(long scheduleId, long patchSessionId, DateTime scheduledStartUTC)
{
using (SqlConnection c = ConnectVRS())
{
SqlCommand cmd = new SqlCommand(#"
UPDATE ScheduleTickets
SET ScheduledStartUTC = #ScheduledStartUTC
WHERE ScheduleId = #ScheduleId AND PatchSessionId = #PatchSessionId
", c);
cmd.Parameters.Add("#ScheduleId", SqlDbType.BigInt).Value = scheduleId;
cmd.Parameters.Add("#PatchSessionId", SqlDbType.BigInt).Value = patchSessionId;
cmd.Parameters.Add("#ScheduledStartUTC", SqlDbType.VarChar).Value = scheduledStartUTC;
cmd.ExecuteNonQuery();
}
}
How can I pass all the values to SQL Server in one call or how can I create a single SQL query to do the updates in one fell swoop?
Many people have suggested using a TableValueParameter, and I agree it would be a good method. Here is an example of how you could do that:
First Create a TVP and Stored Proc in SQL Server
CREATE TYPE [dbo].[SchdeuleTicketsType] As Table
(
ScheduledStartUTC DATETIME NOT NULL
, ScheduleId INT NOT NULL
, PatchSessionId INT NOT NULL
)
CREATE PROCEDURE [dbo].[usp_UpdateTickets]
(
#ScheduleUpdates As [dbo].[SchdeuleTicketsType] Readonly
)
AS
Begin
UPDATE t1
SET t1.ScheduledStartUTC = t2.ScheduledStartUTC
FROM ScheduleTickets AS t1
INNER JOIN #ScheduleUpdates AS t2
ON t1.ScheduleId = t2.ScheduleId AND
t1.PatchSessionId = t2.PatchSessionId
End
)
Next Modify your code to populate a table and pass that as a parameter to the stored proc:
private void Populate()
{
DataTable dataTable = new DataTable("SchdeuleTicketUpdates");
//we create column names as per the type in DB
dataTable.Columns.Add("ScheduledStartUTC", typeof(DateTime));
dataTable.Columns.Add("ScheduleId", typeof(Int32));
dataTable.Columns.Add("PatchSessionId", typeof(Int32));
//write you loop to populate here
//call the stored proc
using (var conn = new SqlConnection(connString))
{
var command = new SqlCommand("[usp_UpdateTickets]");
command.CommandType = CommandType.StoredProcedure;
var parameter = new SqlParameter();
//The parameter for the SP must be of SqlDbType.Structured
parameter.ParameterName = "#ScheduleUpdates";
parameter.SqlDbType = System.Data.SqlDbType.Structured;
parameter.Value = dataTable;
command.Parameters.Add(parameter);
command.ExecuteNonQuery();
}
}
If the values are in another table, use a join:
UPDATE st
SET ScheduledStartUTC = ot.ScheduledStartUTC
FROM ScheduleTickets st JOIN
OtherTable ot
ON st.ScheduleId = ot.ScheduleId AND st.PatchSessionId = ot.PatchSessionId;
You don't specify the special logic but you can probably express it in SQL.

LINQ to DataSet Query Help

I'm really new to LINQ so I'm hoping someone can help me. I've got a database which I need to run a large query from but it's a really old ODBC driver and takes a long time to respond (30+min for even a simple query). It only takes about 2-3min to dump all the data into a dataset however so I figured this was best and then I could run a LINQ to Dataset query. I can't seem to get the query to work and I'm a little confused. I put all the data into an SQL Express database to test the LINQ to SQL query to make sure I was going down the right path. I don't have this option where the application is going to be run as the environment will always be different.
SQL:
SELECT Invoice_detail.Code, Invoice_detail.Description, Product_master.Comment AS Packing, Invoice_detail.QtyInv AS INV, Invoice_detail.QtyBackOrder AS BO, Alternate_product_codes.MasterBarCode AS BarCode, Invoice_detail.PriceAmt AS Price, Invoice_detail.DiscPerc AS Disc, ROUND(Invoice_detail.TaxableAmt/Invoice_detail.QtyInv,2) AS Nett FROM ((Invoice_detail INNER JOIN Product_master ON Invoice_detail.Code = Product_master.Code) INNER JOIN Invoice_header ON Invoice_detail.InternalDocNum = Invoice_header.InternalDocNum AND Invoice_detail.DocType = Invoice_header.DocType) LEFT JOIN Alternate_product_codes ON Invoice_detail.Code = Alternate_product_codes.Code WHERE Invoice_header.DocNum = '{0}' AND Invoice_header.DocType = 1 AND Invoice_detail.LineType = 1 AND Invoice_detail.QtyInv > 0
LINQ to SQL:
from detail in INVOICE_DETAILs
join prodmast in PRODUCT_MASTERs on detail.Code equals prodmast.Code
join header in INVOICE_HEADERs on new { detail.InternalDocNum, detail.DocType } equals new { header.InternalDocNum, header.DocType}
join prodcodes in ALTERNATE_PRODUCT_CODES on detail.Code equals prodcodes.Code into alt_invd
from prodcodes in alt_invd.DefaultIfEmpty()
where
header.DocType == 1 &&
detail.LineType == 1 &&
detail.QtyInv > 0 &&
header.Date > DateTime.Parse("17/07/2011").Date &&
header.DocNum.Trim() == "119674"
select new {
detail.Code,
detail.Description,
Packing = prodmast.Comment,
INV = detail.QtyInv,
BO = detail.QtyBackOrder,
Barcode = prodcodes.MasterBarCode,
Price = detail.PriceAmt,
Disc = detail.DiscPerc,
Nett = Math.Round(Convert.ToDecimal(detail.TaxableAmt/detail.QtyInv),2,MidpointRounding.AwayFromZero)
}
LINQ to Dataset:
var query = from detail in ds.Tables["Invoice_detail"].AsEnumerable()
join prodmast in ds.Tables["Product_master"].AsEnumerable() on detail["Code"] equals prodmast["Code"]
join header in ds.Tables["Invoice_header"].AsEnumerable() on new { docnum = detail["InternalDocNum"], doctype = detail["DocType"] } equals new { docnum = header["InternalDocNum"], doctype = header["DocType"] }
join prodcodes in ds.Tables["Alternate_product_codes"].AsEnumerable() on detail["Code"] equals prodcodes["Code"] into alt_invd
from prodcodes in alt_invd.DefaultIfEmpty()
where
(int)header["DocType"] == 1 &&
(int)detail["LineType"] == 1 &&
(int)detail["QtyInv"] > 0 &&
//header.Field<DateTime>("Date") > DateTime.Parse("17/07/2011").Date &&
header.Field<DateTime>("Date") > DateTime.Now.Date.AddDays(-7) &&
header.Field<string>("DocNum").Trim() == "119674"
select new
{
Code = detail["Code"],
Description = detail["Description"],
Packing = prodmast["Comment"],
INV = detail["QtyInv"],
BO = detail["QtyBackOrder"],
Barcode = prodcodes["MasterBarCode"],
Price = detail["PriceAmt"],
Disc = detail["DiscPerc"],
Nett = Math.Round(Convert.ToDecimal((double)detail["TaxableAmt"] / (int)detail["QtyInv"]), 2, MidpointRounding.AwayFromZero)
};
I need to run the LINQ to DataSet query and then put the results into a DataTable so that I can export to CSV. The query will return many rows so I can see the CopyToDataTable method however that doesn't seem to work unless it is a typed dataset. I'm using the ODBC data adapter fill method so not specifically setting the data types on the Datatables I'm filling. The reason for this is that there is a lot of columns in those tables and setting them all up would be time consuming.
Is LINQ the best option? Am I close? Do I have to set the DataTables up for all the columns and data types? The only other way I can think of is to dump the data into an access database every time and query from there. I'm more curious to get LINQ to work though as I think it's going to be more beneficial for me going forward.
Any help or pointers is appreciated.
Thanks.
Pete.
Consider using POCO objects instead of a DataSet.
Blogs # MSDN
If I understand you correctly, the Linq To Dataset query retrieves the correct information, but you are not able to export the information to csv.
If this is just one csv file you need creating using just the nine fields in your example, you may be able to use a csv library (for example FileHelpers) to export the information.
To give you an example of the extra work involved, you need to define a class eg
[DelimitedRecord(",")]
public class Info
{
[FieldQuoted()]
public string Code ;
[FieldQuoted()]
public string Description ;
[FieldQuoted()]
public string Packing ;
public decimal INV ;
public decimal BO ;
[FieldQuoted()]
public string Barcode ;
public decimal Price ;
public decimal Disc ;
public decimal Nett ;
}
(Note, I'm guessing some of the field types)
You then change your query to use Info , ie
select new Info {
Code = detail["Code"],
...
and finally
FileHelperEngine engine = new FileHelperEngine(typeof(Info));
engine.WriteFile(".\\outputfile.csv", query);
and you are done.

Categories