Return String with Stored Procedure - string

I have another post with more detailed code, but I think that my problem lies in the following logic. Is it possible to have a stored procedure, call another stored procedure, and the called procedure return a string to be used in the first stored procedure?
Example:
ALTER PROCEDURE [dbo].[SP1]
-- Add the parameters for the stored procedure here
#output nvarchar(30)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #SP2Input nvarchar(30) = 'Input';
Declare #SP2Output nvarchar(30);
Execute #SP2Output = SP2 #SP2Input, #SP2Output;
If #SP2Output = 'Success'
Begin
Set #output = 'This worked';
End
Else
Begin
Set #output = 'This did not work';
End
Select #output;
END
Here is the next stored procedure:
ALTER PROCEDURE [dbo].[SP2]
-- Add the parameters for the stored procedure here
#input nvarchar(30),
#output nvarchar(30) out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
If #input = 'Input'
Begin
Set #output = 'Success';
End
Else
Begin
Set #output = 'Fail';
End
Select #output;
END
So, what would get returned to whatever calls stored procedure SP1?
Declare #output nvarchar(30);
Execute SP1 #output;
I would assume that 'This worked' would get returned? However, two results are returned:
1) Success
2) This did not work
What am I doing wrong?

There are two ways to get values back from a sub-procedure: OUTPUT parameters and the return value.
OUTPUT parameters are what other languages call reference parameters. If you pass in a #variable its value can be used but also changed within the called procedure. For this to work you need to mark every parameter that you want to be an OUTPUT parameter as such in the definition of the procedure like you did:
ALTER PROCEDURE [dbo].[SP2]
#input nvarchar(30),
#output nvarchar(30) OUT
AS
BEGIN
....
But you also need to mark it as output on every call:
EXECUTE dbo.SP2 #SP2Input, #SP2Output OUT;
Changing the call of dbo.SP2 in you example to this should make it work.
The actual return value of a procedure is always an integer. You specify its value with the return statement in the procedure:
CREATE PROCEDURE dbo.SP3
AS
BEGIN
RETURN 42;
END;
You then can use it this way:
DECLARE #ret INT;
EXEC #ret = dbo.SP3;
PRINT #ret; -- will print 42

Related

BEGIN ... END and DECLARE in MEMSQL 6 beta

In databses like SQL Server or Hana it is possible to put statements in block using
BEGIN
DECLARE start TIMESTAMP;
DECLARE end TIMESTAMP;
start = now();
select some_udf(X_ID) from X;
end = now();
select DATEDIFF(start, end);
END;
How is this possible in MEMSQL? If I understood it correctly BEGIN ... END can only be used with procedures or functions?!
Yes, you can write code like that in a stored procedure: http://docs.memsql.com/v6.0-beta/docs/create-procedure.

Delphi - Unable to change Function in Excel PivotField

Delphi 10 / Seattle, Excel 2013. I am using Delphi to build an Excel plugin. This plugin will create a Pivot table. My challenge is setting the specific FUNCTION I want (Count vs Average vs Sum, etc). If I do NOT set a function, the default (COUNT) is used, and my code works fine, the pivot table is like it should be. If I try to change the function on a PivotField, then I get an error "Unable to set the Function property of the PivotField class." In searching the error, I have found 2 common causes. (1) The Pivot table has to have a default version of xlPivotTableVersion15, and (2) the PivotField orientation has to be set to xlDataField. I have done both, and when moused-over, the IDE shows the Integer values for both, so I know they are defined.
I have tried getting (as opposed to setting) the value of the PivotField.function. I get a similar error "Unable to get the Function property of the PivotField class". The column I am wanting to Sum is called '#Clicks'. The offending line of code is
myPivotTable.PivotFields('#Clicks').function := xlSum;
If I comment this line out, my routine runs fine, although I get the default COUNT function, instead of the SUM function that I want.
Any ideas appreciated. Note that when the line is commented out, and I run the code, I can then go into Excel, and go into the PivotField listings, and change the function from Count to Sum. Here is my full code.
procedure T_ZTemplateForm.TestPivotTable;
var
myPivotCache: OleVariant;
myPivotTable : OleVariant;
myActive_WorkBook : OleVariant;
TabDestination : string;
f1: OleVariant;
begin
// Add the new Sheet
XLApp.Connect; // XLApp is a global variable, pointing to the Excel instance
myActive_WorkBook := XLApp.ActiveWorkbook;
XLApp.Worksheets.Add(EmptyParam, EmptyParam,1, xlWorksheet, LOCALE_USER_DEFAULT );
// Get a handle to the new sheet and set the Sheet Name
sheet_graph1 := XLApp.ActiveSheet;
sheet_graph1.Name := 'Graph1';
// Create a Pivot Cache
myPivotCache := myActive_WorkBook.PivotCaches.Create(xlDatabase,'Raw Data!R1C1:R1048576C36',xlPivotTableVersion15);
// Create a Pivot table within the PivotCache
TabDestination := 'Graph1!R3C1';
myPivotTable := myPivotCache.CreatePivotTable(TabDestination, 'PivotTable1',xlPivotTableVersion15);
// Now start adding the fields...
f1 := myPivotTable.PivotFields('Fiscal Quarter');
f1.Orientation := xlRowField;
f1.Position := 1;
myPivotTable.PivotFields('#Clicks').orientation := xlDataField;
// myPivotTable.PivotFields('#Clicks').function := xlSum;
end;
Update I can reproduce your problems with Set_Function and GetFunction using Seattle and Excel 2007, so please disregard the original version of my answer.
However, I have found a way to use CreateDataField to create a PivotField with a Function of xlCount, and it is very simple.
Given local variables
var
DataField : OleVariant;
Value : OleVariant;
the following code executes without complaint and correctly
Value := xlCount;
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
vPivotField,
'Count',
Value
);
whereas
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
vPivotField,
'Count',
xlCount
);
fails with the error message you quoted. So I can only guess that when AddDataField is called with the "raw" value xlCount, the Function_ argument the compiler generates is somehow incorrectly "packaged" whereas when the argument is an OleVariant containing the xlCount value, it is correctly packaged.
I'll leave you to try out the other XlConsolidationFunction values - I've had enough of this problem for now!
Original Answer: Judging by my experiments, you can change the Excel 'function' you want to use without specifying the final parameter (in your case xlSum) of AddDataField. In fact with Seattle and Excel2007, I can't get AddDataField to execute without getting a 'The parameter is incorrect' exception for any value of the final, 'function' parameter.
I have a WorkBook with a table of Company names, dividend payment dates and amounts. The table headers are Company, PaymentDate and Amount.
The code below works for me and allows me to choose the function to be applied to the Amount column, simply by specifying the name of the function as the Caption parameter of AddDataField. I've used late binding mainly for ease of set-up, and so that I can easily omit arguments for parameters I don't want to specify.
Code:
procedure TForm1.TestPivotTable;
var
vXLApp : OleVariant;
PivotCache: OleVariant;
PivotTable : OleVariant;
ActiveWorkBook : OleVariant;
DestinationSheet : OleVariant;
FN : String;
Destination : OleVariant;
PivotField : OleVariant;
DataField : OleVariant;
begin
vXLApp := CreateOleObject('Excel.Application');
vXLApp.Visible := True;
FN := 'D:\aaad7\officeauto\MAPivot.xlsm';
Assert(FileExists(FN));
vXLApp.Workbooks.Open(FN);
ActiveWorkBook := vXLApp.ActiveWorkbook;
PivotCache := ActiveWorkbook.PivotCaches.Create(SourceType := xlDatabase,
SourceData := 'Table1', //'R2C1R30C3',
Version:=xlPivotTableVersion2000);
DestinationSheet := ActiveWorkBook.Sheets['Sheet3'];
Destination := DestinationSheet.Name + '!R3C1';
PivotTable := PivotCache.CreatePivotTable(TableDestination := Destination,
TableName := 'APivotTable'
);
DestinationSheet.Select;
DestinationSheet.Cells[3, 1].Select;
DestinationSheet.PivotTables('APivotTable').PivotFields('Company').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('Company').Position := 1;
DestinationSheet.PivotTables('APivotTable').PivotFields('PayDate').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('PayDate').Position := 2;
DestinationSheet.PivotTables('APivotTable').PivotFields('Amount').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('Amount').Position := 3;
PivotField := DestinationSheet.PivotTables('APivotTable').PivotFields('Amount');
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
Field := PivotField,
Caption := 'Sum');
end;

{sysuserinfoorg} constant is empty

I am using the following but the value of {sysuserinfoorg} is an empty string:
Data := 'UNINSTALL=' {sysuserinfoorg};
The above line returns 'UNINSTALL'.
I also tried ExpandConstant('{sysuserinfoorg}') with the same result.
I am using this in the DeinitializeUninstall procedure and sending the Data via HTTP post to my server which writes it to a log file. It all works but the {sysuserinfoorg} is empty. What I am after is some information that identifies the user and/or their organization. Inno Setup doc indicates {sysuserinfoorg} includes data from the registry on who the machine is registered to.
The ExpandConstant is the correct way:
Data := 'UNINSTALL=' + ExpandConstant('{sysuserinfoorg}');
The problem can be that there's actually no registered company.
Check the value of RegisteredOrganization value in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion key.
On my machine, it's empty, hence the {sysuserinfoorg} is empty too.
The mere {sysuserinfoorg} is nonsense in Pascal code.
Data := 'UNINSTALL=' {sysuserinfoorg};
The {...} is a comment in Pascal, as you can see with enabled syntax-highlighting.
Hence the code compiles as:
Data := 'UNINSTALL=';
You can also try to retrieve the value explicitly by reading registry:
if RegQueryStringValue(
HKEY_LOCAL_MACHINE_64, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion',
'RegisteredOrganization', S) then
begin
Log('64-bit RegisteredOrganization = ' + S)
end
else
begin
Log('64-bit RegisteredOrganization is not defined')
end;
if RegQueryStringValue(
HKEY_LOCAL_MACHINE_32, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion',
'RegisteredOrganization', S) then
begin
Log('32-bit RegisteredOrganization = ' + S)
end
else
begin
Log('32-bit RegisteredOrganization is not defined')
end;
Log('sysuserinfoname = ' + ExpandConstant('{sysuserinfoname}'));

How to have responsive UI (Form) when performing long-running export task?

Good day people. First off, I'm not an native English speaker I might have some grammar mistakes or such.
I need an advice from people who has done something or an application alike mine, well, the thing is that I'm using a TProgressBar in my delphi form, another component called "TExcelApplication" and a TDBGrid.
When I export the DBGrid's content, the application "freezes", so I basically put that ProgressBar for the user to see how much the process is completed. I've realized that when the TDBGrid is retrieving and exporting each row to the new Excel workbook, you can't move the actual form, so you have to wait until the process is completed to move that form.
So, is it possible to do something (I thought about threads but I'm not sure if they could help) so the user could move the window if he wanted?
Thank you so much for taking your time in reading and giving me an advice. I'm using Delphi XE.
Here's the code I use to export the rows:
with ZQDetalles do
begin
First;
while not EOF do
begin
i := i + 1;
workSheet.Cells.Item[i,2] := DBGridDetalles.Fields[0].AsString;
workSheet.Cells.Item[i,3] := DBGridDetalles.Fields[1].AsString;
workSheet.Cells.Item[i,4] := DBGridDetalles.Fields[2].AsString;
workSheet.Cells.Item[i,5] := DBGridDetalles.Fields[3].AsString;
workSheet.Cells.Item[i,6] := DBGridDetalles.Fields[4].AsString;
workSheet.Cells.Item[i,7] := DBGridDetalles.Fields[5].AsString;
workSheet.Cells.Item[i,8] := DBGridDetalles.Fields[6].AsString;
workSheet.Cells.Item[i,9] := DBGridDetalles.Fields[7].AsString;
Next;
barraProgreso.StepIt;
end;
end;
If you want to see the whole code for the "Export" button, then feel free to see this link: http://pastebin.com/FFWAPdey
Whenever you're doing stuff that takes a significant amount of time in an application with GUI you want to put it in a seperate thread so the user can still operate the form. You can declare a simple thread as such:
TWorkingThread = class(TThread)
protected
procedure Execute; override;
procedure UpdateGui;
procedure TerminateNotify(Sender: TObject);
end;
procedure TWorkingThread.Execute;
begin
// do whatever you want to do
// make sure to use synchronize whenever you want to update gui:
Synchronize(UpdateGui);
end;
procedure TWorkingThread.UpdateGui;
begin
// e.g. updating the progress bar
end;
procedure TWorkingThread.TerminateNotify(Sender: TObject);
begin
// this gets executed when the work is done
// usually you want to give some kind of feedback to the user
end;
// ...
// calling the thread:
procedure TSettingsForm.Button1Click(Sender: TObject);
var WorkingThread: TWorkingThread;
begin
WorkingThread := TWorkingThread.Create(true);
WorkingThread.OnTerminate := TerminateNotify;
WorkingThread.FreeOnTerminate := true;
WorkingThread.Start;
end;
It's pretty straight forward, remember to always use Synchronize when you want to update visual elements from a thread. Usually, you also want to take care that the user can't invoke the thread again while it's still doing work as he's now able to use the GUI.
If the number of rows is small (and you know how many you'll have), you can transfer the data much more quickly (and all at once) using a variant array of variants, something like this:
var
xls, wb, Range: OLEVariant;
arrData: Variant;
RowCount, ColCount, i, j: Integer;
Bookmark: TBookmark;
begin
// Create variant array where we'll copy our data
// Note that getting RowCount can be slow on large datasets; if
// that's the case, it's better to do a separate query first to
// ask for COUNT(*) of rows matching your WHERE clause, and use
// that instead; then run the query that returns the actual rows,
// and use them in the loop itself
RowCount := DataSet1.RecordCount;
ColCount := DataSet1.FieldCount;
arrData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);
// Disconnect from visual controls
DataSet1.DisableControls;
try
// Save starting row so we can come back to it after
Bookmark := DataSet1.GetBookmark;
try
{fill array}
i := 1;
while not DataSet1.Eof do
begin
for j := 1 to ColCount do
arrData[i, j] := DataSet1.Fields[j-1, i-1].Value;
DataSet1.Next;
Inc(i);
// If we have a lot of rows, we can allow the UI to
// refresh every so often (here every 100 rows)
if (i mod 100) = 0 then
Application.ProcessMessages;
end;
finally
// Reset record pointer to start, and clean up
DataSet1.GotoBookmark;
DataSet1.FreeBookmark;
finally
// Reconnect GUI controls
DataSet1.EnableControls;
end;
// Initialize an instance of Excel - if you have one
// already, of course the next couple of lines aren't
// needed
xls := CreateOLEObject('Excel.Application');
// Create workbook - again, not needed if you have it.
// Just use ActiveWorkbook instead
wb := xls.Workbooks.Add;
// Retrieve the range where data must be placed. Again, your
// own WorkSheet and start of range instead of using 1,1 when
// needed.
Range := wb.WorkSheets[1].Range[wb.WorkSheets[1].Cells[1, 1],
wb.WorkSheets[1].Cells[RowCount, ColCount]];
// Copy data from allocated variant array to Excel in single shot
Range.Value := arrData;
// Show Excel with our data}
xls.Visible := True;
end;
It still takes the same amount of time to loop through the rows and columns of the data, but the time taken to actually transfer that data to Excel is drastically reduced, particularly if there's a good amount of data.

Why I can't insert commas plus spaces in a RichEdit Control (Delphi)

I’m trying to replace a "comma" with "comma + space" using the following code in a procedure called by the OnChange Event on a RichEdit control in Delphi 2010.
SomeString := RichEdit.Lines.Strings[RichEdit.Lines.Count-1];
Position := Pos(',', SomeString);
if Position > 0 then
begin
Delete(SomeString, Position, 1);
Insert(', ', SomeString, Position);
RichEdit.Lines.Strings[RichEdit.Lines.Count-1] := SomeString;
end;
It works perfectly, but I can’t use BACKSPACE and DEL via keyboard anymore (on the RichEdit Control), because the inserted characters act like barriers. It doesn’t happen with another set of inserted characters, only with "comma + space".
Can someone tell me what am I doing wrong here ?
Just try this code
//get the string
SomeString := RichEdit.Lines.Strings[RichEdit.Lines.Count-1];
//replace the 'comma' with 'comma+space'
SomeString :=StringReplace(SomeString ,',',', ',[rfReplaceAll,rfIgnoreCase]);
//now delete the extra space which gets added each time you call this event
SomeString :=StringReplace(SomeString ,', ',', ',[rfReplaceAll,rfIgnoreCase]);
//your desired result
RichEdit.Lines.Strings[RichEdit.Lines.Count-1] := SomeString ;
Remember the BACKSPACE and DEL are working fine. But in case of your code there was an extra space added each time you try and change the contents of RichEdit. Hence giving an impression of the 2 keys not working.
Since you are having problems deleting the space in front of comma I will provide with another solution
Add a Boolean variable to the class isComma: Boolean.
Later on OnKeyPress event of RichEdit inset this code
isComma := False;
if key = ',' then
isComma := True;
OnChange event code goes here
var
CurPoint: TPoint;
cText: string;
cLine: string;
begin
if isComma then
begin
cLine:=' '; //character to be added after comma, ' ' in here
CurPoint:=RichEdit1.CaretPos;
cText:=RichEdit1.Lines.Strings[CurPoint.y];
//add ' ' where the cursor is i.e. after the comma
cText:=Copy(cText,0,CurPoint.x)+cLine+Copy(cText,CurPoint.x+1,Length(cText));
RichEdit1.Lines.Strings[CurPoint.y]:=cText;
end;
end;

Resources