GORM Fetch native query results into a slice of uints - go-gorm

I have a raw query to be used with gorm and I would like to store the results in a slice of uints.
taskIds := make([]uint64, 0)
tx := tx.WithContext(ctx).Debug()
if err := tx.Raw("select task_id from group_tasks where group_id in ?", intIds).Scan(&taskIds).Error; err != nil {...}
Gorm seems to be having a problem doing that. What's the cause and is there a workaround?

Related

Filling a dataset from a sql select string in Delphi

I am currently VERY new to delphi. It is older code, but I need to know if why my code is failing is what I think it is. all the data I need to read is in the tables I'm reading from, I know that much. but I'm getting the following error back on my SOAP call:
There is no row at position 0.
This tells me that although the data is in the DB, I know I'm not able to read it out. am I using the correct way to read sql data from a db table, in delphi, using the following code:
FXMLDocument := XMLDocument.Create;
FXMLDocument.LoadXML(strXML);
// Get WebService URL - needed for saving COG Assessments
strUrl := GetURL;
// Fill Applicant and Configuration DataSets
FApplicant := TApplicant.Create;
FConfiguration := TConfiguration.Create;
FApplicant.Username := FXMLDocument.GetElementsByTagName('appid').item(0).FirstChild.Value;
strFormID := FXMLDocument.GetElementsByTagName('formid').item(0).FirstChild.Value;
SqlConDB.Close;
selApp.CommandText := 'SELECT AppID, FirstName, LastName, Age, Gender, UserPassword, ProgToRun, ReportLang, CompanyID FROM Users WHERE AppID = '''+ FApplicant.Username +'''';
selConfig.CommandText := 'SELECT * FROM Preferences';
SqlConDB.Open;
daApp.Fill(dsApp, 'Applicant');
daConfig.Fill(dsConfig, 'Configuration');
SqlConDB.Close;
FConfiguration := CASConfigRoutines.FillConfigurationFromDB(dsConfig);
// Get Program to Run from Data
FApplicant.ProgToRun := dsApp.Tables[0].Rows[0].Item['ProgToRun'].ToString;
FApplicant.Gender := dsApp.Tables[0].Rows[0].Item['Gender'].ToString;
FApplicant.ReportLang := dsApp.Tables[0].Rows[0].Item['ReportLang'].ToString;
// Get Company ID from Data for FNB check
FApplicant.Company := dsApp.Tables[0].Rows[0].Item['CompanyID'].ToString;
Should these two lines:
daApp.Fill(dsApp, 'Applicant');
daConfig.Fill(dsConfig, 'Configuration');
Not rather be:
daApp.Fill(selApp, 'Applicant');
daConfig.Fill(selConfig, 'Configuration');
as it is selApp that is the "select" statement? I don't know enough about Delphi to say if it is right or wrong, but can someone maybe assist in telling me why I'm getting a record count of 0 if I know for a fact, my data is in the DB?
Thanks!

Working in ampl with sets containing the same number various times

I am doing an optimization problem. For that, some param have to take different values. I am using a Smolyak reduced grid. I have 9 realizations. I have constructed a code which includes in each set the values that the param has to take in each realization. Unfortunately, these param take the same values in various realizations, therefore making my code useless. I need a way to make ampl accept said sets.
I haven't found anything useful in ampl's book in the chapters dedicated to the sets
https://ampl.com/BOOK/CHAPTERS/08-sets1.pdf
https://ampl.com/BOOK/CHAPTERS/09-sets2.pdf
However I found this question, AMPL error, duplicate number for set, in which something similar happens. However, if I copy the code, the error remains.
This is where I define the sets, and the error occurs
param Level=9;
set slM1Set ordered := {45.4236, 45.3191, 45.5438,45.4236, 45.4236, 45.4236, 45.4236, 45.4236, 45.4236};
set slM2Set ordered := {3.8222, 3.8222, 3.8222, 3.8134, 3.8324, 3.8222, 3.8222, 3.8222, 3.8222};
set slP1Set ordered := {-37040,-37040,-37040,-37040,-37040,-47040,-27040, 37040,-37040};
set slP2Set ordered := {0, 0, 0, 0, 0, 0, 0, -577.350, 577.350};
param w_x;
param w_y;
param x1M;
param x2M;
and this is where I use them
for {a in 1..Level}
{
let w_x := member(a, slM1Set);
let w_y := member(a, slM2Set);
let x1M := member(a, slP1Set);
let x2M := member(a, slP2Set);
solve;
I need to know if I can change my code to work as intended, meaning that the loop will be executed 9 times, the first, the variable w_x will take the first value from slM1Set, w_y from slM2Set, x1M from slP1Set, x2M from slP2Set, in the second execution the variable w_x will take the second value from slM1Set, w_y from slM2Set, x1M from slP1Set, x2M from slP2Set and so on.
Unless there is some reason that you specifically need these to be sets, you're almost certainly better off handling them as indexed params.
param Level=9;
param slM1{1..Level} := ...
param slM2{1..Level} := ...
param slP1{1..Level} := ...
param slP2{1..Level} := ...
...
for {a in 1..Level}
{
let w_x := slM1[a];
let w_y := slM2[a];
let x1m := slP1[a];
let x2m := slP2[a];
solve;
}

How to bulk copy the values from a TSQLQuery record to a TdxMemData record?

Just to make it clear, I do not want to copy the entire TSQLQuery into the TdxMemData, as I would use memds.CopyFromDataSet(qry) for that.
I am interating through each record from the TSQLQuery, and I may or may not be adding a record(s) to the TdxMemData. Generally the record in memds matches that in qry, but sometimes the values are altered and sometimes additional records are added to memds. My example did not make this clear since all it seemed to do was copy over each record.
So given an active record in the TSQLQuery, I want to copy over the values into an active editable record in the TdxMemData.
The following code works in so far as it creates a copy of the record:
qry := TSQLQuery.Create(nil);
memds := TdxMemData.Create(nil);
try
qry.SQLConnection := cn;
qry.Text := 'SELECT Field1, Field2, Field3 FROM Table1';
qry.Open
memds.CreateFieldsFromDataSet(qry);
memds.Open;
while not qry.Eof do
begin
if {some condition} then
begin
memds.Append;
for i := 0 to qry.FieldCount-1 do
memds.Fields[i+1].Value := qry.Fields[i].Value; //First field is RecID
//Do something with the current memds record
end
else if {some other condition} then
begin
memds.Append;
//change values
memds.Append;
//change values
memds.Append;
//change values
end
else if {a third condition} then
; //Skip any work on memds
qry.next;
end;
qry.Close;
//Do something with memds
memds.Close;
finally
memds.Free;
qry.Free;
end;
Is there a better way? I had looked at AppendRecord but creating the array of TVarRec doesn't seem to be straightforward.
EDIT:
Let's use these examples with very simplified criteria. Note that the actual conditions that determine how many records to append and the changes to the field values in the destination are complex and not in any database.
Method 1:
While not tblSource.Eof do
Begin
If (iCondition = 1) Then
Begin
// Add one record
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bSomethingCondition then
tblDestination.FieldByName('Field4').Value := 'Something';
End
Else If (iCondition = 2) Then
Begin
// Add two records
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bAnotherThingCondition then
tblDestination.FieldByName('Field4').Value := 'Another thing';
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bSomethingElseCondition then
tblDestination.FieldByName('Field4').Value := 'Something else';
End
Else If (iCondition = 0) Then
Begin
// Add no records
End;
tblSource.Next;
End;
Since the number of fields in the source and destination tables can vary, hard-coding field names as in Method 1 is not suitable.
Method 2:
While not tblSource.Eof do
Begin
If (iCondition = 1) Then
Begin
// Add one record
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
End
Else If (iCondition = 2) Then
Begin
// Add two records
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bAnotherThingCondition then
tblDestination.Fields(iAnotherThingConditionFieldINdex).Value := 'Another thing';
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingElseCondition then
tblDestination.Fields(iSomethingElseConditionFieldIndex).Value := 'Something else';
End
Else If (iCondition = 0) Then
Begin
// Add no records
End;
tblSource.Next;
End;
While method 2 above does work, and is the way it is currently done, this question is whether there is a way to pass the variant array of field values from tblSource to tblDestination using AppendRecord.
Instead of this:
// Add one record
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
Do this:
tblDestination.AppendRecord({tblSource fields var array);
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
Of course, it might be that there is no answer, and that the method I currently employ is the best solution.
Try the following
Query1.Open();
dxMemData1.AddFieldsFromDataSet(Query1);
dxMemData1.Open;
dxMemData1.LoadFromDataSet(Query1);
Creating an array of TVarRec isn't difficult, and AppendRecord may indeed help. The following code adds a record to a TClientDataSet (named CDS for brevity) that has 4 fields of type string, float, boolean, and string in that order:
CDS.AppendRecord(['Smith', 123.45, False, 'Test text']);
Note that you have to create a value for every single field (column) in the dataset, in the order that they exist in the FieldDefs collection, or you'll get an exception.
(Of course, the real question is why you're returning extra rows and iterating through them, instead of testing the conditions in your SQL statement WHERE clause and only returning the rows you actually need. This can almost always be done using parameters.)
If you don't want to use AddFieldsFromDataSet -> LoadFromDataSet , and want to load fields and records manually, you also can do it. You can create fields programmatically and append records by iterations. Look example:
MD.Fields.Clear;
MD.FieldDefs.Clear;
MD.Close;
with MD.FieldDefs.AddFieldDef do
begin
Name := 'UserID';
DataType := TFieldType.ftInteger;
CreateField(MD);
Name := 'GridName';
DataType := TFieldType.ftString;
Size := 255;
CreateField(MD);
Name := 'TemplateGrid';
DataType := TFieldType.ftBlob;
CreateField(MD);
end;
MD.Close;
MD.Open;
MD.Append;
MD.FieldByName('UserID').AsInteger := 1;
MD.FieldByName('GridName').AsString := Self.Name
TBlobField(MD.FieldByName('TemplateGrid')).LoadFromStream(LStream);
MD.Post;
You can easily modify it to load fields with names and types from your dataset as they are.

SQL Query fails on empty result

I have a function that performs a query on a SQL database with an ADO connection, it is simply designed to provide a single result for a database entry that can only have one match for a SELECT type of query (i.e. get me the x value from ID 45, where there is and can only be one ID 45 entry).
The function works fine, until I hit a query that returns no results. The query just hangs, and the application cannot continue. Here is an example query string:
'SELECT Cost FROM MaterialCost ' +
'WHERE MaterialType = ''' + 'MS' +
''' AND Thickness = ''' + '0.250' + '''';
Again this exact string will work fine until I maybe query for something that I know before hand doesn't exist, which should return null or an empty string. Here is the function:
function SelectOneQuery(AQueryString : String; AADOConnectionString : String) : String;
var
ADOQuery: TADOQuery;
begin
//Create empty ADO Query, then connect to connection string
ADOQuery := TADOQuery.Create(nil);
ADOQuery.ConnectionString:=AADOConnectionString;
ADOQuery.SQL.Clear;
ADOQuery.SQL.Add(AQueryString);
ADOQuery.ExecSQL;
ADOQuery.Open;
//Set first query result and return first result
ADOQuery.First;
if(ADOQuery.Fields.Count > 0) then begin
result:=ADOQuery.Fields[0].Value;
end
else begin
result := '';
end;
end;
I added the fields count thing, but I'm not sure if that's helping at all. Basically, if there are no result, i want result := ''
You have a few problems in your code snippet:
The main problem is that you are checking FieldCount. FieldCount will always be nonzero because it contains the number of columns your query returns, regardless of the fact your query returned records or not. One option is to check RecordCount which represents the number of rows returned but a better option is to check EOF flag.
you are leaking ADOQuery. Always use try/finally blocks to create and cleanup objects.
ExecSQL is used for queries that don't return recordsets (like INSERT and DELETE),
use Open instead
No need to use First after Open
If you use the same query over and over you are better off using parameters, as a bonus your code will be more readable.
Example:
ADOQuery.SQL.Text := 'SELECT Cost FROM MaterialCost WHERE MaterialType = :MaterialType AND Thickness = :Thickness';
ADOQuery.Parameters.ParamByname('MaterialType').Value := 'MS';
ADOQuery.Parameters.ParamByname('Thickness').Value := 0.25;
Your function code should be something like this:
function SelectOneQuery(const AQueryString, AADOConnectionString: string): string;
var
ADOQuery: TADOQuery;
begin
Result := '';
ADOQuery := TADOQuery.Create(nil);
try
ADOQuery.ConnectionString := AADOConnectionString;
ADOQuery.SQL.Text := AQueryString;
ADOQuery.Open;
if not ADOQuery.EOF then
Result := ADOQuery.Fields[0].AsString;
finally
ADOQuery.Free;
end;
end;

Variable structure for database results

A lot of times when we query the database, we just need one column with varchar.
So I've made a nice function for querying the database and putting the results in a stringlist:
function Getdatatostringlist(sqlcomponent, sqlquery: string): TStringlist;
What I'm looking for now is basically the same function but for results with multiple columns where you don't know in advance what type the data is, be it varchar, int, datetime.
What kind of datastructure would be good to use here.
The reason I want this is that I try not to work on open datasets. I like much more to fetch all results into a temporary structure, close the dataset and work on the results.
After Kobiks reply about using in Memory datasets I came up with the following, it's fast put together to test the concept:
procedure TForm1.Button2Click(Sender: TObject);
var
MyDataSet : TAdoDataSet;
begin
MyDataSet := GetDataToDataSet('SELECT naam FROM user WHERE userid = 1', ADOConnection1);
try
Form1.Caption := MyDataSet.FieldByName('naam').AsString;
finally
MyDataSet.free;
end;
end;
function TForm1.GetDataToDataSet(sSql: string; AdoConnection: TADOConnection): TAdoDataSet;
begin
Result := TAdoDataSet.Create(nil);
Result.LockType := ltBatchOptimistic;
Result.Connection := AdoConnection;
Result.CommandText := sSql;
Result.Open;
Result.Connection := nil;
end;
I think this is something to build on.
You should use any disconnected in-memory TDataSet descendant, such as TClientDataSet.
Do not attempt to re-invent the wheel by storing a record-set in some new "Variant" structure. A TClientDataSet already contains all features you need to manipulate a "temporary" data structure.
Here is how you create a TClientDataSet structure:
cds.FieldDefs.Add('id', ftInteger);
cds.FieldDefs.Add('name', ftString, 100);
// ...
// create it
cds.CreateDataSet;
// add some data records
cds.AppendRecord([1, 'Foo']);
cds.AppendRecord([2, 'Bar']);
Many TDataSets has an ability to be used as an in-memory (client) datasets depending on the provider and LockType, for example a TADODataSet with LockType=ltBatchOptimistic could fetch results-set from the server, and then remain disconnected.
For exchanging Data with Excel this structure is usefull, might be useful for other purposes.
Function GetDatasetasDynArray(Ads: TDataset; WithHeader: Boolean = true): Variant;
// 20130118 by Thomas Wassermann
var
i, x, y: Integer;
Fields: Array of Integer;
begin
x := 0;
y := Ads.RecordCount;
if WithHeader then
inc(y);
SetLength(Fields, Ads.FieldCount);
for i := 0 to Ads.FieldCount - 1 do
if Ads.Fields[i].Visible then
begin
Fields[x] := i;
inc(x);
end;
SetLength(Fields, x);
Result := VarArrayCreate([0, y - 1 , 0, length(Fields) - 1], VarVariant);
y := 0;
if WithHeader then
begin
for i := Low(Fields) to High(Fields) do
begin
Result[y, i] := Ads.Fields[Fields[i]].DisplayLabel;
end;
inc(y);
end;
try
Ads.DisableControls;
Ads.First;
while not Ads.EOF do
begin
for i := Low(Fields) to High(Fields) do
begin
Result[y, i] := Ads.Fields[Fields[i]].Value;
end;
Ads.Next;
inc(y);
end;
finally
Ads.EnableControls;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
DynArray:Variant;
begin
DynArray := GetDatasetasDynArray(Adodataset1,true);
//DynArray[0,x] Header or First row
//DynArray[1,x] First row or SecondRow
Excel.Range.Value := DynArray;
end;
Why don't you like working with open datasets? They usually do not block the server. Copying the data from the dataset to whatever you want is extra overhead which is most likely not necessary.
A dataset provides exactly the functionality you want: A matrix with variable columns and rows.
EDIT: However, if you have iterate through the dataset often, you should consider creating a class holding the relevant information and then copy the data into a generic list, dictionary, tree or whatever you need as fast lookup structure.
Of course you could think of building something smart which can be as flexible as a dataset but: The more general things get, the poorer the performance (usually).