我可以在SQL中使用什么作为内部标识符
本文关键字:内部 标识符 什么 SQL 我可以 | 更新日期: 2023-09-27 18:27:21
首先是一点背景:
我开发了一种方法,通过使用IEnumerable
和yield
从SQL中的表中批量或页面检索行。当您需要只读访问时,它非常有效,但当您需要更新底层数据时,它就不那么好了。
因此,我编写了一个方法,它接受一个通用的DataTable
,并构建一个更新语句,然后将其与整个DataTable
一起作为表值参数传递给SQL。
方法如下:
string[] validColumns = SQL_Columns.Split(',');
foreach(DataColumn column in p_UpdatesTable.Columns)
{
if(!validColumns.Contains(column.ColumnName))
{
throw new Exception("Column '" + column.ColumnName + "' is not valid for this table");
}
}
//Establish SQL Connection
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
StringBuilder commandBuilder = new StringBuilder();
commandBuilder.Append("UPDATE Table SET ");
List<string> columnsToUpdate = new List<string>(p_UpdatesTable.Columns.Count);
foreach(DataColumn column in p_UpdatesTable.Columns)
{
if (!column.ColumnName.Equals("UID", StringComparison.InvariantCultureIgnoreCase))
{
StringBuilder columnBuilder = new StringBuilder();
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" = U.");
columnBuilder.Append(column.ColumnName);
columnsToUpdate.Add(columnBuilder.ToString());
}
}
commandBuilder.Append(string.Join(",", columnsToUpdate.ToArray()));
commandBuilder.Append(" FROM @UpdateTable AS U WHERE UID = U.UID");
using (SqlCommand sqlCommand = new SqlCommand(commandBuilder.ToString(), sqlConnection))
{
SqlParameter updateTableParameter = sqlCommand.Parameters.Add("UpdateTable", SqlDbType.Structured);
updateTableParameter.Value = p_UpdatesTable;
int rowsAffected = sqlCommand.ExecuteNonQuery();
if(rowsAffected != p_UpdatesTable.Rows.Count)
{
throw new Exception("Update command affected " + rowsAffected + " rows out of the " + p_UpdatesTable.Rows.Count + " expected.");
}
}
sqlConnection.Dispose();
}
然后我构建了这个方法来填充更新表:
private void AddUpdate(ref DataTable p_UpdateTable, string p_ColumnName, long p_uid, object p_value)
{
if(!StronglyTypedDataset.Columns.Contains(p_ColumnName))
{
throw new ArgumentException("Table '" + p_ColumnName + "' does not exist in table", "p_ColumnName");
}
if(!p_UpdateTable.Columns.Contains(p_ColumnName))
{
DataColumn columnToAdd = p_UpdateTable.Columns.Add(p_ColumnName);
columnToAdd.DataType = StronglyTypedDataset.Columns.Cast<DataColumn>().Where(c => c.ColumnName.Equals(p_ColumnName)).First().DataType;
}
var existingRow = p_UpdateTable.Rows.Cast<DataRow>().Where(r => Convert.ToInt64(r["UID"]) == p_uid).FirstOrDefault();
if(existingRow != null)
{
existingRow[p_ColumnName] = p_value;
}
else
{
DataRow newRow = p_UpdateTable.NewRow();
newRow["UID"] = p_uid;
newRow[p_ColumnName] = p_value;
p_UpdateTable.Rows.Add(newRow);
}
}
有几次我需要调用它,所以这比其他任何方法都更方便。
现在的问题是:我可能会为一个UID添加一堆列和值,但对于另一个UID,我可能会添加更多列或不为现有列添加值。这样做的问题是,更新会删除数据库中已经有空值的内容,除非我明确表示"使其为空",否则我不希望这样做。
我想通过提供一个值作为默认值来解决这个问题,然后我可以在更新语句中检查该值,然后在检查该值的UPDATE
语句中使用CASE
,并使用原始值(所以本质上我可以忽略列名前的"U")。问题是,这个表是通用的,所以里面可能有任何东西,而在实际数据与我的默认值匹配的情况下,事情就会崩溃。
我应该注意,这个更新表将被构建成一个批和一个一次更新的批,而不是逐行更新。
有没有一个值可以保证不会被使用,也许是GUID(我知道仍然可能有冲突)或类似的东西?
示例:
假设我的桌子在一排之后看起来像这样:
| UID | column 1 | column 2 |
row 1 | 1 | x | y |
在第二排,它看起来像这样:
| UID | column 1 | column 2 | column 3 |
row 1 | 1 | x | y | ? |
row 2 | 2 | x | y | z |
第1行第3列的值从未被设置,因此它被默认为null。当我使用update语句时,SQL会将该值设置为null,即使表中已经有内容,但我根本不希望它更新该行的字段,因为我没有为它指定值。
我希望能够用一个值来代替?
,而不是默认为null,这样我就可以将update语句更改为类似UPDATE Table SET Column1 = U.Column1, Column2 = U.Column2, Column3 = CASE WHEN U.Column3 = somevalue THEN Column3 ELSE U.Column3 END FROM @UpdateTable U
的语句。
您可以让自己成为一个小Maybe<T>
,它可以是T
的实际值,在这种情况下,您可以进行更新,也可以是一个特殊的非值。它可能看起来像这样:
public sealed class Maybe<T> {
private readonly T value;
private readonly bool hasValue;
private Maybe() {
hasValue = false;
}
public readonly Maybe<T> Nothing = new Maybe();
public Maybe(T value) {
this.value = value;
hasValue = true;
}
public T Value {
get {
return value;
}
}
public bool HasValue {
get {
return value;
}
}
}
你可以这样使用:
private void AddUpdate<T>(DataTable p_UpdateTable, string p_ColumnName, long p_uid, Maybe<T> p_value) {
// ...
if(existingRow != null) {
if(p_value.HasValue)
existingRow[p_ColumnName] = p_value.Value;
}
else {
DataRow newRow = p_UpdateTable.NewRow();
newRow["UID"] = p_uid;
if(p_value.HasValue)
newRow[p_ColumnName] = p_value.Value;
p_UpdateTable.Rows.Add(newRow);
}
// ...
}
顺便说一下,DataTable
参数不需要ref
。
我最终选择了一条稍微不同的路线,不使用标识符,而是完全使用另一列。
我的AddUpdate
现在是这样的:
private void AddUpdate(DataTable p_UpdateTable, string p_ColumnName, long p_uid, object p_value)
{
if (!StronglyTypedDataSet.Columns.Contains(p_ColumnName))
{
throw new ArgumentException("Table '" + p_ColumnName + "' does not exist in table", "p_ColumnName");
}
if (!p_UpdateTable.Columns.Contains(p_ColumnName))
{
var matchingColumn = StronglyTypedDataSet.Columns.Cast<DataColumn>().Where(c => c.ColumnName.Equals(p_ColumnName)).First();
DataColumn columnToAdd = p_UpdateTable.Columns.Add(p_ColumnName, matchingColumn.DataType);
columnToAdd.MaxLength = matchingColumn.MaxLength;
DataColumn setNullColumn = p_UpdateTable.Columns.Add(p_ColumnName + "_null", typeof(bool));
setNullColumn.DefaultValue = false;
}
var existingRow = p_UpdateTable.Rows.Cast<DataRow>().Where(r => Convert.ToInt64(r["UID"]) == p_uid).FirstOrDefault();
if (existingRow != null)
{
existingRow[p_ColumnName] = p_value;
if (p_value == null || p_value == DBNull.Value)
{
existingRow[p_ColumnName + "_null"] = true;
}
}
else
{
DataRow newRow = p_UpdateTable.NewRow();
newRow["UID"] = p_uid;
newRow[p_ColumnName] = p_value;
if (p_value == null || p_value == DBNull.Value)
{
newRow[p_ColumnName + "_null"] = true;
}
p_UpdateTable.Rows.Add(newRow);
}
}
这样,如果一列默认为null,因为它是在某些行添加到表中之后才添加的,那么我可以在更新语句中进行检查,不更新值。
更新声明现在看起来是这样的:
string[] validColumns = SQL_Columns.Split(',');
var trimmed = validColumns.Select(c => c.Trim());
foreach(DataColumn column in p_UpdatesTable.Columns)
{
if(!column.ColumnName.EndsWith("_null") && !trimmed.Contains(column.ColumnName))
{
throw new Exception("Column '" + column.ColumnName + "' is not valid for table");
}
}
string tableTypeName = "dbo.UpdateSpecific" + Guid.NewGuid().ToString().Replace("-", "").Replace("{", "").Replace("}", "");
StringBuilder tableTypeBuilder = new StringBuilder();
tableTypeBuilder.Append("CREATE TYPE ");
tableTypeBuilder.Append(tableTypeName);
tableTypeBuilder.Append(" AS TABLE (");
List<string> tableTypeColumns = new List<string>(p_UpdatesTable.Columns.Count);
StringBuilder commandBuilder = new StringBuilder();
commandBuilder.Append("UPDATE Table SET ");
List<string> columnsToUpdate = new List<string>(p_UpdatesTable.Columns.Count);
foreach (DataColumn column in p_UpdatesTable.Columns)
{
//build command to create table type
StringBuilder columnTypeBuilder = new StringBuilder();
columnTypeBuilder.Append("[");
columnTypeBuilder.Append(column.ColumnName);
columnTypeBuilder.Append("] ");
if(column.DataType == typeof(int))
{
columnTypeBuilder.Append("INT");
}
else if(column.DataType == typeof(long))
{
columnTypeBuilder.Append("BIGINT");
}
else if(column.DataType == typeof(bool))
{
columnTypeBuilder.Append("BIT");
}
else if(column.DataType == typeof(string))
{
columnTypeBuilder.Append("VARCHAR(");
columnTypeBuilder.Append(column.MaxLength);
columnTypeBuilder.Append(")");
}
else if(column.DataType == typeof(byte[]))
{
columnTypeBuilder.Append("IMAGE");
}
tableTypeColumns.Add(columnTypeBuilder.ToString());
//build actual update statement
if (!column.ColumnName.Equals("UID", StringComparison.InvariantCultureIgnoreCase) && !column.ColumnName.EndsWith("_null"))
{
StringBuilder columnBuilder = new StringBuilder();
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" = (CASE WHEN U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" IS NULL THEN (CASE WHEN ISNULL(U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append("_null, 0) = 1 THEN U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" ELSE C.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" END) ELSE U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" END)");
columnsToUpdate.Add(columnBuilder.ToString());
}
}
tableTypeBuilder.Append(string.Join(",", tableTypeColumns.ToArray()));
tableTypeBuilder.Append(")");
commandBuilder.Append(string.Join(",", columnsToUpdate.ToArray()));
commandBuilder.Append(" FROM Table AS C JOIN @UpdateTable AS U ON C.UID = U.UID");
//Establish SQL Connection
using (SqlConnection sqlConnection = new SqlConnection(context.strContext[(int)eCCE_Context._CONNECTION_STRING]))
{
sqlConnection.Open();
try
{
using (SqlCommand createTableTypeCommand = new SqlCommand(tableTypeBuilder.ToString(), sqlConnection))
{
createTableTypeCommand.ExecuteNonQuery();
}
using (SqlCommand sqlCommand = new SqlCommand(commandBuilder.ToString(), sqlConnection))
{
SqlParameter updateTableParameter = sqlCommand.Parameters.Add("@UpdateTable", SqlDbType.Structured);
updateTableParameter.Value = p_UpdatesTable;
updateTableParameter.TypeName = tableTypeName;
int rowsAffected = sqlCommand.ExecuteNonQuery();
if (rowsAffected != p_UpdatesTable.Rows.Count)
{
throw new Exception("Update command affected " + rowsAffected + " rows out of the " + p_UpdatesTable.Rows.Count + " expected.");
}
}
}
finally
{
string dropStatement = "IF EXISTS (SELECT * FROM sys.types st JOIN sys.schemas ss ON st.schema_id = ss.schema_id WHERE st.name = N'"+ tableTypeName.Substring(tableTypeName.IndexOf(".")+1) +"' AND ss.name = N'dbo') DROP TYPE " + tableTypeName;
using (SqlCommand dropTableTypeCommand = new SqlCommand(dropStatement, sqlConnection))
{
dropTableTypeCommand.ExecuteNonQuery();
}
}
sqlConnection.Dispose();
}
测试和工作:)