Skip to content

Issue-757: added Query update with CASE WHEN capabilities #759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions NewReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# New Update and UpdateAsync upgrade: CASE

## A new feature added to allow developers to programmatically set CASE WHEN when assigning values. Feature includes grouping in sub statements () or
## to allow condition to point to a column variable instead of a direct paramater value. SQL injection friendly

## Original Update Statement for multiple records using anonymous objects:

### foreach (var item in data)

### {

### object obj = new

### {

### MyField = item.Value

### };

### cnt += await QueryFactory.Query(tableName).Where("Id", item.Id).UpdateAsync(value);


### }

### return cnt;





## New Update with select case using multi-level array systems
## version 1 : allows is equal condition only for now
## For the Else it will always fill with name of field itself , self assigning.
## This happens if format is wrong as well.
## The else protects you fro your field to be set back to NULL

### Warning: Limitation is requires , Suggest 200 rows for low number columns,
### 25 for higher number columns or clauses.


var datac = data.Chunk(200); // breaking data up to 200 rows

//each holds for each update set, which allows multiple value setting as older Update
List<object[]> cases = [];

if (datac.Any()) foreach (var d in datac)
{

try
{
foreach (var item in d) //Build case when statement , standard 3
{
cases.Add(["Id", item.Id, item.Value]);
}
object obj = new
{
MyField= cases.ToArray()
};
cases.Clear();

//if data set is smaller than whole table , best to use in statement to reduce cost
cnt += await QueryFactory.Query(tableName)
.WhereIn("Id", d.Select(dd => dd.Id).ToArray())
.UpdateAsync(value);
}
catch { throw; }
finally { cases.Clear(); }
}
else cases.Clear();

return cnt;




##standard: Case WHEN x = A then Y... END:
### In your cases array the flow is [x,A,Y].
### Assignmet value is always last.





## Available Feaure 1 : While its common to do 3 items for basic, when can extend the criteria with AND and OR
## It combine, the array column after the orevioud criteria field must be an AND or OR, unless using , () or * explained later

### Note: Assignmet value is always last. you can use AND,&&,& or OR,||,|, <>. Not case sensitive.

### Case WHEN x = A AND z = B then Y ... END:
### In your cases array the flow is [x,A,"AND",z,B,Y]
### Case WHEN x = A OR z = B then Y ... END:
### Array the flow is [x,A,"OR",z,B,Y]





## Available Feaure 2 : Subset (). This allows seperating your "And" & "Or" blocks
### ex: case when (a = 1 or a = 5) and (b = 7 and c = 2)
### This can be placed anywhere before the assignment column or * assignment column,
### if you forget to add the ) to close, the engine
### will compensate.

### Case WHEN (x = A AND z = B) OR J = C then Y ... END:
### Array the flow is ["(",x,A,"AND",z,B,")","OR",j,c,Y]
### Case WHEN (x = A OR z = B) AND (J = C AND K = D) then Y ... END:
### Array the flow is ["(",x,A,"OR",z,B,")","AND","(",j,c,"AND",k,d,")" Y]


## Available Feaure 3 : To Another Column Field (*). This allows criteria to check if column equals another column (field)
### Case WHEN (colx = colb AND colz = colx) then Y ... END:
### Array the flow is [,colx,*',colb,"AND",colz,colx, Y]
2 changes: 2 additions & 0 deletions QueryBuilder/BaseQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public abstract partial class BaseQuery<Q> : AbstractQuery where Q : BaseQuery<Q
private bool orFlag = false;
private bool notFlag = false;
public string EngineScope = null;
public string ToRaw { get; set; }


public Q SetEngineScope(string engine)
{
Expand Down
159 changes: 158 additions & 1 deletion QueryBuilder/Compilers/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,16 @@ protected virtual SqlResult CompileUpdateQuery(Query query)

for (var i = 0; i < toUpdate.Columns.Count; i++)
{
parts.Add(Wrap(toUpdate.Columns[i]) + " = " + Parameter(ctx, toUpdate.Values[i]));
var values = toUpdate.Values[i];
if (values is object[] v && v.Length > 0)
{
SetUpCaseWhenUpdatePart(ctx, parts, toUpdate.Columns[i], v);
}
else if (values is not Array)
{
SetUpDirectUpdatePart(ctx, parts, toUpdate.Columns[i], values);
}
else throw new MissingMemberException();
}

var sets = string.Join(", ", parts);
Expand All @@ -388,6 +397,150 @@ protected virtual SqlResult CompileUpdateQuery(Query query)
return ctx;
}

private void SetUpCaseWhenUpdatePart(SqlResult ctx, List<string> parts, string columnName, object[] value)
{
StringBuilder casewrap = new StringBuilder($"{Wrap(columnName)} = ");
bool hasOne = false;

foreach (var item in value)
{
if (item is object[] i && i.Length >= 3)
{
int indent = 0;

object val = i.Last();
var subparts = i.Take(i.Length - 1).ToArray();

int pointer = 0;
bool substart = true;
bool start = true;
bool setasfield = false;
bool criteriaValue = false;
var field = string.Empty;

while (pointer <= (subparts.Length - 1))
{
var piece = subparts[pointer]?.ToString().ToUpperInvariant().Trim();
if (pointer > 0 && !substart && piece != null)
{
if (!VERB.SpecialChar.Any(s => s == piece) && criteriaValue)
{
pointer = subparts.Length;
break;
}
else if (VERB.AndOpertors.Any(s => s == piece))
{
casewrap.Append(" ").Append(VERB.And).Append(" ");
pointer++;
substart = true;
continue;
}
else if (VERB.OrOpertors.Any(s => s == piece))
{
casewrap.Append(" ").Append(VERB.Or).Append(" ");
pointer++;
substart = true;
continue;
}
}


if (!criteriaValue && (piece is null || VERB.AndOrOpertors.Any(s => s == piece)))
{
pointer = subparts.Length;
break;
}
else if (piece == VERB.StartParenth)
{
indent++;
pointer++; casewrap.Append(VERB.StartParenth);
continue;
}
else if (piece == VERB.EndParenth)
{
if (indent > 0)
{
indent--;
casewrap.Append(VERB.EndParenth);
pointer++;
continue;
}
}

if (substart && !string.IsNullOrEmpty(field))
{
criteriaValue = true;
}
if (piece == VERB.PushField && criteriaValue)
{
setasfield = true;
pointer++;
continue;
}

else if (string.IsNullOrEmpty(field))
{
field = piece;
}

if (substart && criteriaValue && !string.IsNullOrEmpty(field))
{

if (!hasOne && start)
{
casewrap.Append(VERB.CaseWhen);
hasOne = true;
}
else if (start)
{
casewrap.Append(" ").Append(VERB.When);
}

if (subparts[pointer] is not null)
{
casewrap.Append($" {field} = {(setasfield ? subparts[pointer] : Parameter(ctx, subparts[pointer]))}");
}
else
{
casewrap.Append($" {field} is NULL");
}
substart = false;
setasfield = false;
start = false;
criteriaValue = false;
field = string.Empty;
}
pointer++;

}
if (indent > 0 && hasOne)
{
casewrap.Append("".PadLeft(indent, ')'));
}

if (hasOne)
{
casewrap.Append($" {VERB.Then} {Parameter(ctx, val)}");
}
}
}
if (!hasOne)
{
casewrap.Append($"{Wrap(columnName)}");
}
else
{
casewrap.Append($" {VERB.Else} {Wrap(columnName)} {VERB.End}");
}
parts.Add(casewrap.ToString());
casewrap.Length = 0;
}

private void SetUpDirectUpdatePart(SqlResult ctx, List<string> parts, string columnName, object value)
{
parts.Add($"{Wrap(columnName)} = {Parameter(ctx, value)}");
}

protected virtual SqlResult CompileInsertQuery(Query query)
{
var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter)
Expand Down Expand Up @@ -1021,6 +1174,10 @@ public virtual string Parameter(SqlResult ctx, object parameter)
ctx.Bindings.Add(value);
return parameterPlaceholder;
}
else if (parameter == DBNull.Value)
{
parameter = null;
}

ctx.Bindings.Add(parameter);
return parameterPlaceholder;
Expand Down
27 changes: 27 additions & 0 deletions QueryBuilder/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace SqlKata
{
public static class VERB
{
public const string PushField = "*";
public const string StartParenth = "(";
public const string EndParenth = ")";
public const string And = "AND";
public const string And2 = "&&";
public const string And3 = "&";
public const string Or = "OR";
public const string Or2 = "||";
public const string Or3 = "|";
public const string Or4 = "<>";
public const string CaseWhen = "CASE WHEN";
public const string When = "WHEN";
public const string Else = "ELSE";
public const string Then = "THEN";
public const string End = "END";


public static string[] SpecialChar = [StartParenth, EndParenth, PushField];
public static string[] AndOrOpertors = [And, And2, And3, Or, Or2, Or3, Or4];
public static string[] AndOpertors = [And, And2, And3];
public static string[] OrOpertors = [Or, Or2, Or3, Or4];
}
}
3 changes: 1 addition & 2 deletions SqlKata.Execution/QueryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -859,9 +859,8 @@ private static async Task<IEnumerable<T>> handleIncludesAsync<T>(Query query, IE
internal SqlResult CompileAndLog(Query query)
{
var compiled = this.Compiler.Compile(query);

query.ToRaw = compiled.RawSql;
this.Logger(compiled);

return compiled;
}

Expand Down