I am working on Delphi7 and SQlserver 2008 R2.
i have a table with some data like below.\
CREATE TABLE dbo.tstTable (
ID int IDENTITY(1,1) PRIMARY KEY,
Name varchar(255) NOT NULL,
Eid int null,
Pid int null
);
insert into tstTable(Name,Eid,Pid) values ('name1',1,null);
insert into tstTable(Name,Eid,Pid) values ('name2',2,null);
insert into tstTable(Name,Eid,Pid) values ('name3',3,null);
insert into tstTable(Name,Eid,Pid) values ('name4',null,4);
insert into tstTable(Name,Eid,Pid) values ('name5',null,5);
insert into tstTable(Name,Eid,Pid) values ('name4',null,6);
insert into tstTable(Name,Eid,Pid) values ('name7',null,null);
now i want to get the record where Eid is equal to 1. in sql server i am able to get the result.
when i try the same in delphi i am not getting any result.
in Delphi, i have taken TADOConnection, TADOQuery, TDataSource and TDBGrid.
SET TADOQuery.Query = 'select Name from tstTable where Eid=:Eid and Pid =:Pid'
with ADOQuery1 do
begin
Parameters.ParamByName('Eid').Value := 1;
Parameters.ParamByName('pid').Value := NULL;//i tried with Unassigned also
Close;
open;
end; //with
when i open the AdoQuery, in grid it is not showing any records.
below code is also not returning any records.
with ADOQuery1 do
begin
Parameters.ParamByName('Eid').Value := NULL;
Parameters.ParamByName('pid').Value := NULL;//i tried with Unassigned also
Close;
open;
end; //with
how to handle this scenario?
I can say that the query you provide in the image return (0) rows if SET ANSI_NULLS is ON:
You can try to use set it to OFF:
procedure TForm1.Button1Click(Sender: TObject);
begin
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SET ANSI_NULLS OFF;');
ADOQuery1.SQL.Add('select Name from tstTable where Eid=:Eid and Pid =:Pid');
ADOQuery1.Parameters.ParamByName('Eid').Value := 1;
ADOQuery1.Parameters.ParamByName('pid').Value := NULL;
ADOQuery1.Open;
end;
Tested in: Delphi V7 (Build 4.453).
You shouldn't assign anything to the parameter but clear it to set it NULL
Parameters.ParamByName('Eid').Clear;
Upd. Unfortunately, this doesn't work with TADOxxx components because TParameter doesn't have Clear() method.
So the solution
ADOQuery1.SQL.Add('SELECT * FROM mytable');
ADOQuery1.SQL.Add('WHERE mycol = :Param1 OR (:Param1 IS NULL AND mycol IS NULL)';
ADOQuery1.Parameters.ParamByName('Param1').Attributes :=
ADOQuery1.Parameters.ParamByName('Param1').Attributes + [paNullable];
ADOQuery1.Parameters.ParamByName('Param1').Value := Null();
ADOQuery1.Open;
Tested OK with Delphi 7/SQL Server 2014
Try do not change any SET ANSI_XXX settings because of compatibility issues and the global session scope. If you really need to change session default settings, do it at the connection start. When SET ANSI_NULLS OFF is executed before the query, this will change the setting for the entire session (connection).
Related
I have two databases in MSSQL and by a program inserting values into first table and i want this procedure to insert the same values into second table.
ALTER PROCEDURE [dbo].[ylk_banka_ekle]
#banka_kod varchar(25)
AS
BEGIN
INSERT INTO MikroDB_V15_TEST2.dbo.BANKALAR
(ban_RECid_DBCno, ban_RECid_RECno, ban_SpecRECNo,
ban_iptal, ban_fileid, ban_hidden, ban_kilitli, ban_degisti, ban_CheckSum,
ban_create_user, ban_create_date, ban_lastup_user, ban_lastup_date,
ban_special1, ban_special2, ban_special3, ban_kod)
SELECT
ban_RECid_DBCno, ban_RECid_RECno, ban_SpecRECNo,
ban_iptal, ban_fileid, ban_hidden, ban_kilitli, ban_degisti, ban_CheckSum,
ban_create_user, ban_create_date, ban_lastup_user, ban_lastup_date,
ban_special1, ban_special2, ban_special3, ban_kod
FROM BANKALAR
WHERE (ban_kod = #banka_kod)
UPDATE BANKALAR SET ban_RECid_RECno = ban_RECno WHERE ban_RECno !=
ban_RECid_RECno
END
GO
In sql when i execute this procedure it says only 'Command is successful' but not adding any rows into second database.
Anybody can give an idea where my mistake is?
For example:
I have a data-table type in SQL:
CREATE TYPE dbo.typTable1 AS TABLE
(
Field1 INT NOT NULL,
Field2 NVARCHAR(20) NULL
)
And a stored procedure in SQL, like this:
CREATE PROCEDRE dbo.prcTest
#XDataTable dbo.typTable1 READONLY
AS
BEGIN
.
.
.
And now problem!!! :
When i send a table as parameter in delphi like this:
FireDACStoredProcedure.Params.ParamByName('#XDataTable').AsDataset :=
FireDACMemTable;
And when i run this, an error accour that say not supported.
Please guide me...thanks to you so much...
I am thinking something like that:
Set the DataType = ftObject;
Set datatypename = <your_procedureName>.<your_parameter>
With FireDACStoredProcedure.Params.ParamByName('#XDataTable') do
begin
DataType := ftObject;
DataTypeName := 'prcTest.#XDataTable';
end;
Inspired in https://delphiaball.co.uk/2016/04/29/interbase-array-fields-firedac/
CREATE PROCEDURE [dbo].[spTest]
#Pozitii varchar(max),
#NrZile int
AS
set #Pozitii = SUBSTRING(#Pozitii,0,LEn(#Pozitii))
CREATE TABLE #Pozitii (part varchar(20) null)
INSERT INTO #Pozitii(part)
SELECT part
FROM dbo.SDF_SplitString(#Pozitii,',')
if exists (SELECT * FROM #Pozitii)
RAISERROR('asdf',16,-1)
else RAISERROR('else',16,-1)
So runing this SP in SQL like this
exec [spTest] '11,12,13,',1
Returns:
(3 row(s) affected)
Msg 50000, Level 16, State 1, Procedure spTest, Line 27
asdf
Now if I run my procedure in delphi ( using an ADO object)
procedure TframePlanificatorPozitieComanda.Button5Click(Sender: TObject);
begin
try
with dm.spTest do
begin
Close;
Parameters.ParambyName('#Pozitii').Value := '11,12,13,';
Parameters.ParambyName('#NrZile').Value := 1;
ExecProc;
end;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
This code is not raising any errors?Any ideas why?
Have you tried adding:
SET NOCOUNT ON;
to your stored procedure? I think the exception is in a second resultset and is getting hidden by the first select result count.
The error is raising in sql-server.. but in delphi is not .. because there the procedure is already executed..
But in Delphi you can check if the procedure is run successfully or not regards what the result of the procedure (some of procedures have no output parameters).
In delphi please check your adostoredprocedure.parameters[0] like:
showmessage(vartostr(self.ADOStoredProc1.Parameters[0].Value))
.
if the result <> 0 that means error.
Using Sql Server 2014, I don't get the behaviour you describe.
I have a stored proc on the server defined as
CREATE PROCEDURE spRaiseError(#AnError int)
AS
BEGIN
declare #Msg Char(20)
if #AnError > 0
begin
Select #Msg = 'MyError ' + convert(Char(8), #AnError)
RaisError(#Msg, 16, -1)
end
else
select 1
END
I have a minimalist D7 Ado project with a TAdoConnection, TAdoQuery, TDataSource and TDBGrid connected up as you'd expect, a TEdit and a TButton.
Using this code
procedure TForm1.Button1Click(Sender: TObject);
var
S : String;
ErrorCount : Integer;
begin
S := 'exec spRaiseError ' + Edit1.Text;
AdoQuery1.SQL.Text := S;
try
AdoQuery1.Open;
except
end;
ErrorCount := AdoConnection1.Errors.Count;
Caption := IntToStr(ErrorCount);
end;
, if the number in Edit1 is > 0 I get only the error number on the form's caption, whereas if it contains 0 I see the value 1 in the DBGrid.
Btw, with "Stop on language exceptions" checked in the D7 debugger settings, without the try/except around AdoQuery1.Open, the debugger sees and catches the exception from the server.
Anyway, the take-home message from this answer is that you can use the TAdoConnection's Errors collection to detect whether there was an error and, if there was, you can get more information from it. See the Delphi OLH amd MS Ado documentation for more info about the Errors collection of TAdoConnection and other Ado-based Delphi classes.
I have a stored procedure that works fine but it has inside it three "select"s.
The selects are not from an inner temporary table.
This is mainly the format of the procedure:
ALTER PROCEDURE [dbo].[STProce]
#param1 int,
#param2 int,
#param3 int,
#param4 int,
#param5 int
AS
select #param1 as p1, #param2 as p2, #param3 as p3
.
.
.
select #param4 as p4
.
.
.
select #param5 as p5
I'm executing the procedure from another procedure and need to catch it there.
I created a table and inserts into it the "exec" from the procedure, like that:
CREATE TABLE #stalledp
(
RowNumber INT,
fldid INT,
fldLastUpdated datetime,
fldCreationDate datetime,
fldName nvarchar(255),
fldPending nvarchar(255)
)
INSERT INTO #stalledp (RowNumber,fldid,fldLastUpdated,fldCreationDate,fldName,fldPending)
EXEC spDebuggerViews_GetStuckWorkflowInstances #workflowSpaceId='00000000-0000-0000-0000-000000000000',#pageNum=1,#pageSize=100000,#orderByColumn=N'fldid',#sortOrder=1,#workflowInstanceId=0,#stuckInstanceType=1,#createdDateFrom='1900-01-01 00:00:00',#createdDateTo='9999-01-01 23:59:59',#updatedDateFrom='1900-01-01 00:00:00',#updatedDateTo='9999-01-01 23:59:59'
Afterwards I receive this error:
Column name or number of supplied values does not match table definition.
The order and name of columns of the table is exactly like the procedure returns.
Is there a possibility to catch only one of the tables that the procedure returns and avoid the other? I cannot change the procedure at all.
I tried declaring a table the same fields as the first select of the procedure and I get an error says that
Thank you in advance!
If all of the result sets returned are of the same structure, then you can dump them to a temp table as you are trying to do. However, that only gets you so far because if the data in the fields cannot be used to determine which result set a particular row came from, then you just have all of the result sets with no way to filter out the ones you don't want.
The only way to interact with multiple result sets individually, regardless of them having the same or differing structures, is through app code (i.e. a client connection). And if you want to do this within the context of another query, then you need to use SQLCLR.
The C# code below shows a SQLCLR stored procedure that will execute a T-SQL stored procedure that returns 4 result sets. It skips the first 2 result sets and only returns the 3rd result set. This allows the SQLCLR stored procedure to be used in an INSERT...EXEC as desired.
The code for the T-SQL stored proc that is called by the following code is shown below the C# code block. The T-SQL test proc executes sp_who2 and only return a subset of the fields being returned by that proc, showing that you don't need to return the exact same result set that you are reading; it can be manipulated in transit.
C# SQLCLR proc:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class TheProc
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void Get3rdResultSetFromGetStuckWorkflowInstances()
{
int _ResultSetsToSkip = 2; // we want the 3rd result set
SqlConnection _Connection = null;
SqlCommand _Command = null;
SqlDataReader _Reader = null;
try
{
_Connection = new SqlConnection("Context Connection = true;");
_Command = _Connection.CreateCommand();
_Command.CommandType = CommandType.StoredProcedure;
_Command.CommandText = "tempdb.dbo.MultiResultSetTest";
// (optional) add parameters (but don't use AddWithValue!)
// The SqlDataRecord will be used to define the result set structure
// and act as a container for each row to be returned
SqlDataRecord _ResultSet = new SqlDataRecord(
new SqlMetaData[]
{
new SqlMetaData("SPID", SqlDbType.Char, 5),
new SqlMetaData("Status", SqlDbType.NVarChar, 30),
new SqlMetaData("Login", SqlDbType.NVarChar, 128),
new SqlMetaData("HostName", SqlDbType.NVarChar, 128),
new SqlMetaData("BlkBy", SqlDbType.VarChar, 5),
new SqlMetaData("DBName", SqlDbType.NVarChar, 128)
});
SqlContext.Pipe.SendResultsStart(_ResultSet); // initialize result set
_Connection.Open();
_Reader = _Command.ExecuteReader();
// Skip a predefined number of result sets
for (int _Index = 0;
_Index < _ResultSetsToSkip && _Reader.NextResult();
_Index++) ;
// Container used to move 1 full row from the result set being read
// to the one being sent, sized to the number of fields being read
Object[] _TempRow = new Object[_Reader.FieldCount];
while (_Reader.Read())
{
_Reader.GetValues(_TempRow); // read all columns
_ResultSet.SetValues(_TempRow); // set all columns
SqlContext.Pipe.SendResultsRow(_ResultSet); // send row
}
}
catch
{
throw;
}
finally
{
if(SqlContext.Pipe.IsSendingResults)
{
SqlContext.Pipe.SendResultsEnd(); // close out result set being sent
}
if(_Reader != null && !_Reader.IsClosed)
{
_Reader.Dispose();
}
_Command.Dispose();
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Dispose();
}
}
return;
}
}
T-SQL test proc:
USE [tempdb]
SET ANSI_NULLS ON;
IF (OBJECT_ID('dbo.MultiResultSetTest') IS NOT NULL)
BEGIN
DROP PROCEDURE dbo.MultiResultSetTest;
END;
GO
CREATE PROCEDURE dbo.MultiResultSetTest
AS
SET NOCOUNT ON;
SELECT 1 AS [ResultSet], 'asa' AS [test];
SELECT 2 AS [ResultSet], NEWID() AS [SomeGUID], GETDATE() AS [RightNow];
EXEC sp_who2;
SELECT 4 AS [ResultSet], CONVERT(MONEY, 131.12) AS [CashYo];
GO
EXEC tempdb.dbo.MultiResultSetTest;
To do:
Adjust _ResultSetsToSkip as appropriate. If you only want the first result set, simply remove both _ResultSetsToSkip and the for loop.
Define _ResultSet as appropriate
Set _Command.CommandText to be "spDebuggerViews_GetStuckWorkflowInstances"
Create the necessary parameters via SqlParameter (i.e. #workflowSpaceId='00000000-0000-0000-0000-000000000000',#pageNum=1,#pageSize=100000,#orderByColumn=N'fldid',#sortOrder=1,#workflowInstanceId=0,#stuckInstanceType=1,#createdDateFrom='1900-01-01 00:00:00',#createdDateTo='9999-01-01 23:59:59',#updatedDateFrom='1900-01-01 00:00:00',#updatedDateTo='9999-01-01 23:59:59')
If needed, add input parameters to the SQLCLR proc so that they can be used to set the values of certain SqlParameters
Then use as follows:
INSERT INTO #stalledp
(RowNumber,fldid,fldLastUpdated,fldCreationDate,fldName,fldPending)
EXEC Get3rdResultSetFromGetStuckWorkflowInstances;
There is a way to get the first record set but the others, I'm afraid, you're out of luck.
EXEC sp_addlinkedserver #server = 'LOCALSERVER', #srvproduct = '',
#provider = 'SQLOLEDB', #datasrc = ##servername
SELECT * FROM OPENQUERY(LOCALSERVER, 'EXEC testproc2')
EDIT: If you only need to check the other result set for columns to be not null you could predefine the expected results sets like so:
EXEC testproc2 WITH RESULT SETS (
(a VARCHAR(MAX) NOT NULL, b VARCHAR(MAX) NOT NULL),
(a VARCHAR(MAX) NOT NULL)
);
If the query within the stored procedure returns null values a exception is raised at that point in procedure. This will only work on sql server 2012 and upwards though.
I am testing out Absolute Database by ComponentAce
I have on my Form a TABSTable, TABSDatabase and a TDataSource and the data is being displayed in a TDBAdvListView, MultiSelect and RowSelect are True. I have only one Table.
When either one or more of the Items in the TDBAdvListView are selected I want to have the Database Delete the selected Records.
I have tried this way in the code below:
procedure TMain.DeleteEntry2Click(Sender: TObject);
var
i: Integer;
begin
with DBAdvListView1.DataSource.DataSet do
begin
for i := DBAdvListView1.Items.Count - 1 downto 0 do begin
if DBAdvListView1.Items[i].Selected then
begin
DBAdvListView1.DataSource.DataSet.GotoBookmark(Pointer(DBAdvListView1.Items[i]));
DBAdvListView1.DataSource.DataSet.Delete;
end;
end;
end;
end;
This always results in an Error Message:
Cannot retrieve record - Native error: 10026
I have very little experience with database programming, what am I doing wrong?
Edit:
I have added a new field into the database named ID as an integer starting from 0 in the hopes that I can reference them with the Locate method and tried with the code below. This produces no error but will only delete the top record in the ListView and if I select more than one it will delete different records than selected.
My new code:
procedure TMain.DeleteEntry2Click(Sender: TObject);
var
i: Integer;
begin
with DBAdvListView1.DataSource.DataSet do
begin
DBAdvListView1.BeginUpdate;
First;
for i := DBAdvListView1.Items.Count - 1 downto 0 do begin
if DBAdvListView1.Items[i].Selected then
begin
if dbTable.Locate('ID',DBAdvListView1.Items[i].Selected,[]) then
dbTable.Delete;
Next;
end;
end;
dbTable.Close;
dbTable.Open;
DBAdvListView1.EndUpdate;
end;
end;
The dbTable has to be Closed and Opened to see changes for some strange reason - I have tried Refresh to no avail...
Edit:
// To include Table Structure as requested...
ID integer 0
Title string 200
Author string 100
Date string 20
Location string 60
Category string 100
ISBN-13 string 20
ISBN-10 string 20
In the Absolute Database Utils directory there is a DatabaseManager.exe which I used to create the actual table with and in here I have also now set a Primary Key of the type:
Type - Primary
Name - ID
The fields for the Index:
ColumnName - ID
CaseInsensitive - False
ASC - True
MaxIndexSize - 20
If you know the primary keys of all the records to be deleted, then you can use one SQL query statement in order to delete all the selected records in one go -
delete from table
where id in (1, 7, 15, 23, 45);
You would have to build this query manually, i.e. create the string which holds the id numbers.
Answer to own question... I remove the selected records with the code below:
procedure TMain.Button5Click(Sender: TObject);
var
i: Integer;
begin
with DBAdvListView1 do
for i := 0 to Items.Count - 1 do
if Items[i].Selected then
begin
Memo1.Lines.Add(Items[i].Caption + ' - Selected!'); //Test for Correct ID's!
if dbTable.Locate('ID', Items[i].Caption, []) then
DBAdvListView1.Datasource.DataSet.Delete;
end;
dbTable.Close;
dbTable.Open;
end;
second code will work if you just remove the Next; from the if section. Since u're already inside a 'for' loop, there's no need to use the Database.Next.