I'm using Delphi 7 with ADO components, and MS Access 2003. The SQL sentence
SELECT CMCB.Name,
(SELECT SUM(amount) FROM movement MCB
WHERE MCB.movement_classification_id=CMCB.movement_classification_id
AND MCB.operation_date >= #01/01/2013#
AND MCB.operation_date < #01/01/2014#
) AS MyYear
FROM movement_classification CMCB
is working fine in MS Access console but through a Delphi application launches the following error when I am opening the DataSet (TADOQuery):
Data provider or other service returned an E_FAIL status
Any idea why it happens? Is it related with the ADO component (TADOQuery in this case)
I tried a similar query from the database dbdemos.mdb (Program Files\Common Files\Borland Shared\Data) and it works
SELECT CustNo,
(SELECT SUM(AmountPaid) FROM orders O
WHERE O.CustNo = C.CustNo
AND O.SaleDate >= #01/01/1994#
AND O.SaleDate < #01/01/1995#
) AS AmountPaid
FROM customer C
The code I used in Delphi is the following:
procedure TForm1.Button1Click(Sender: TObject);
begin
ADOConnection1.Connected := False;
ADOConnection1.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\Xiber\Delphi\StackOverflow\Subquerys\dbdemos.mdb';
ADOConnection1.Connected := True;
ADOQuery1.SQL.Text := 'SELECT CustNo, (SELECT SUM(AmountPaid) FROM orders O WHERE O.CustNo = C.CustNo AND O.SaleDate >= #01/01/1994# AND O.SaleDate < #01/01/1995#) AS AmountPaid FROM customer C';
ADOQuery1.Open;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
sSQL: string;
begin
ADOConnection1.Connected := False;
ADOConnection1.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\Xiber\Delphi\StackOverflow\Subquerys\XiGest-CASA.mdb';
ADOConnection1.Connected := True;
sSQL := ' SELECT CMCB.Name, ' +
' (SELECT SUM(amount) FROM movement MCB ' +
' WHERE MCB.movement_classification_id=CMCB.movement_classification_id ' +
' AND MCB.operation_date >= #01/01/2013# ' +
' AND MCB.operation_date < #01/01/2014# ' +
' ) AS MyYear ' +
' FROM movement_classification CMCB ';
ADOQuery1.SQL.Text := sSQL;
ADOQuery1.Open;
end;
As it stands, your query should return the same value for sum(qty) for each row of employee. If Access is "hiding" the relationship between the two tables, that could explain why it works in Access.
I would expect a query something like:
select e.name, sum(i.qty)
from
employee e,
items i
where
i.employeeid = e.employeeid
would be something more like what you're after. This, of course, assumes a direct relationship (the foreign key of employeeid in the items table) between the two tables, which is not very realistic.
As others have commented, more information would permit a more precise answer!
Is there any reason you couldn't simplify the query (assuming it is an ADO bug) such as:
select CMCB.Name, SUM(MCB.amount)
from
movement_classification CMCB,
movement MCB
where
MCB.movement_classification_id=CMCB.movement_classification_id
AND MCB.operation_date >= #01/01/2013#
AND MCB.operation_date < #01/01/2014#
Finally I realised the difference between the two sums was that in dbdemos the field AmountPaid.mdb is double and in my case is decimal(8,2).
It seems to be an ADO bug. You can reproduce by yourself.
So, If you change the field AmountPaid in dbdemos.mdb (provided by Borland, you can found at Program Files\Common Files\Borland Shared\Data) to decimal(8,2) and execute the query through Delphi 7 (with an ADOConnection and an ADOQuery), you'll get the error above mentioned.
SELECT CustNo,
(SELECT SUM(AmountPaid) FROM orders O
WHERE O.CustNo = C.CustNo
AND O.SaleDate >= #01/01/1994#
AND O.SaleDate < #01/01/1995#
) AS AmountPaid
FROM customer C
But if you execute this query inside MS Access, it works fine.
Related
I have a grid that connected to AdoDataset. i want read records from two table with join SQL, but save record in a table.
Read data:
adodataset.commandtext := 'select * from Table1 left join Table2 on Table1.ID = Table2.ID';
adodataset.Open;
I want save Table1 Fields only when post
You can use ReadOnly property of DBGrid.Columns. For example if you have a query like this:
ADODataSet1.CommandText := 'SELECT * FROM Table1 JOIN Table2 ON Table1.ID = Table2.ID';
Then your DBGrid will be like:
You can then make 3rd and 4th columns read only at design time or at run time by these codes:
DBGrid1.Columns[2].ReadOnly := True;
DBGrid1.Columns[3].ReadOnly := True;
Also note that if you want to delete records only from Table1 then you should run this code when ADODataSet1 is active:
ADODataSet1.Properties['Unique Table'].Value := 'Table1';
Update:
As suggested in comments it's a good idea to set desired fields ReadOnly at DataSet level:
ADODataSet1.FieldByName('ID_1').ReadOnly := True;
ADODataSet1.FieldByName('Table2_Value').ReadOnly := True;
First of all the query example in the commandtext is wrong / ambiguous. I don't know what you want to perform but I'm guessing you want to change/save data in a table. In this case, why don't you perform the update in sql? Let's say you have the primary key field called "id", and you want to save a field called "name" with another value.
var
id, NewName: string;
//...
begin
//...
id := adodataset.fieldbyname('id').AsString;
adodataset.connection.execute(
'UPDATE table1 SET name = ' + quotedstr(NewName) + ' WHERE id = ' + quotedstr(id)
);
// refresh the records by closing and reopening the adodataset
adodataset.close;
adodataset.open;
// move to the wanted record
adodataset.Locate('id', id, []);
Why not use TAdoQuery ?
Qry.Close;
Qry.SQL.Clear;
Qry.SQL.Add("select * from Table1 left join Table2 on Table1.ID = Table2.ID");
Qry.Open;
while not Qry.EOF do
begin
[do stuff]
Qry.Next;
end;
I have a procedure to update the balance from start date to end date and
also I want to keep a track of number of records being inserted . I am using dbms_output.put_line to get the number of records inserted but it does not give any output , when the execution completes then the output of the count is being displayed. The code of procedure is as follows :
create or replace function updatebal(start_date IN DATE, end_date IN DATE)
RETURN NUMBER
IS
difference number;
curr_r number;
BEGIN
difference := end_date - start_date;
curr_r := 0;
while curr_r <= difference LOOP
curr_r := curr_r + 10;
for curr_in in 1..10 LOOP
date_value := date_value +1 ;
insertAvailBal(date_value);
commit;
select count(*) into totalCount from avail_bal;
dbms_output.put_line('total count' || totalCount);
end loop;
END LOOP;
RETURN 1;
END;
Now I am trying to print the totalCount from this procedure to get the number of rows inserted in this table avail_bal. But getting no output.
Please help me, Thanks in Advance
As Tony has already answered: you can't change the behaviour of dbms_output.
The recommended way of signaling progress to the outside of a stored procedure is to use the dbms_application_info package to manage information in v$session_longops
You can even manage separate progress indicators for the outer and the inner loop. v$session_longops will even display an estimate on how long the process will take based on the average duration over time. Those estimate are pretty accurate if the runtime for each (reported) step is fairly constant.
You can enhance your function like this:
create or replace function updatebal(start_date IN DATE, end_date IN DATE)
RETURN NUMBER
IS
difference number;
curr_r number;
main_index binary_integer;
sub_index binary_integer;
main_slno binary_integer;
sub_slno binary_integer;
BEGIN
difference := end_date - start_date;
curr_r := 0;
-- initialize the module information
dbms_application_info.set_module('updatebal', 'Calculate Balance');
-- initialize two different "handles" for the inner and outer loop
main_index := dbms_application_info.set_session_longops_nohint;
sub_index := dbms_application_info.set_session_longops_nohint;
while curr_r <= difference LOOP
curr_r := curr_r + 10;
-- report each outer step
dbms_application_info.set_session_longops(rindex => main_index,
slno => main_slno,
op_name => 'main loop',
sofar => curr_r,
totalwork => difference);
for curr_in in 1..10 LOOP
date_value := date_value +1;
insertAvailBal(date_value);
commit;
select count(*) into totalCount from avail_bal;
-- report each inner step with the totalcount
dbms_application_info.set_session_longops(
rindex => sub_index,
slno => sub_slno,
op_name => 'Sub Loop, totalcount'||totalcount,
sofar => curr_in, totalwork => 10);
end loop;
END LOOP;
RETURN 1;
dbms_application_info.set_module(null,null);
END;
/
See the manual for more details:
https://docs.oracle.com/database/121/ARPLS/d_appinf.htm#ARPLS003
That is how dbms_output works, it displays all its output after the run completes, you cannot monitor it in real time.
If you really need this real-time monitoring of progress, you could use a procedure with an autonomous transaction to insert the messages into a special table, and then from another session you could view the contents of that table while the process is still running.
Example of such a procedure:
procedure log_message (p_message varchar2) is
pragma autonomous_transaction;
begin
insert into message_table (message) values (p_message);
commit;
end;
I'm using a TDataSet where the CommandText property is set to an SQL query. I have also made the following function which creates part of an SQL query based on the fields of TDataSet. It is however incomplete. As you can see I still need to get the name of the table that a TField is from. How do I achieve this?
function GetDataSetFieldsMSSQL(Dataset: TDataSet): String;
var
I, L: Integer;
TableName: String;
begin
Result := '';
L := Dataset.Fields.Count;
if (L > 0) then
begin
TableName := ... // Name of the table for the Dataset.Fields[0] field.
Result := '[' + TableName + '].[' + Dataset.Fields[0].FieldName + ']';
I := 1;
while (I < L) do
begin
TableName := ... // Name of the table for the Dataset.Fields[I] field.
Result := Result + ',[' + TableName + '].[' + Dataset.Fields[I].FieldName + ']';
Inc(I);
end;
end;
end;
You can use the Delphi Function GetTableNameFromQuery(SQL : String):String; from the DBCommon unit. Just Add The DBCommon on the uses. =)
Maybe there is no solution at all for a simple TDataSet?
I believe not. Because an TDataset can source its' data not only from RDBMS' tables.
It can be:
an RSS feed
An XML file. Example: TCliendataset is an TDataset descendant that can read XML from its'
own format or using an XMLTransformProvider.
It can be an SQL for reading an Excel spreadsheet or a text file if you have an ODBC driver for
that and configured the datasource.
Sky (and the imagination of Delphi's programmers around the world) is the limit for what a field can represent in an TDataset.
You have some alternatives, since you are using an ADODataset:
Parsing the commandText of ADOCommand
Using the BASETABLENAME property of ADORecordSet (as in kobik's comment)
Guessing by convention ( Abelisto's answer )
As I know there is no any way to get the name of the table from the SQL query component.
However you can give aliases for fields, for example: "select foo_field as foo_dot_foo_field from foo" and then replace them to the correct syntax: "Result := '[' + StringReplace(DataSet.Fields[0].FieldName, 'dot', '].[', [rfReplaceAll]) + ']'"
What you are trying to do is impossible if you have no knowledge or control over the SQL used in the query.
The query could contain calculated/computed fields or could be returning fields from a view etc. Furthermore the database might have several tables that contain the same field names.
If possible you can query the SQL server view INFORMATION_SCHEMA.COLUMNS and that way try to figure out what table a fieldname is from. However if the field names are not unique this might also prove impossible.
I have a long SQL text that I want to assign to a query SQL. I do this the following way:
SQL.Text:= 'SELECT T1.COLUMN1,T2.COLUMN2,T1COLUMN3..........,'+
' T1.COLUMNn FROM TABLE1 T1 INNER JOIN '+
' TABLE2 T2 ON T1.ID=T2.ID'+
' WHERE T1.COLUMN10=100'
The actual SQL is 20 times longer than this. My problem is with the line breaks. When I format the source code (Ctrl+D) it sometimes leaves the lines as I typed, but other times it deletes the line breaks and I get something like this:
'SELECT T1.COLUMN1,T2.COLUMN2,T1COLUMN3 ' + 'FROM TABLE1 T1 INNER JOIN '+ 'TABLE2 T2 ON T1.ID=T2.ID'
And this leads to a "line too long (more than 1023 charactes)" error. What's interesting is this does not happen with all lines. I can't catch the difference between the lines which will be affected and those that won't. I need a line break after or before the "+" sign. How do I do this?
You can also use the Add function.
SQL.Clear;
SQL.ADD('SELECT T1.COLUMN1,T2.COLUMN2,T1COLUMN3..........,');
SQL.ADD(' T1.COLUMNn FROM TABLE1 T1 INNER JOIN');
SQL.ADD(' TABLE2 T2 ON T1.ID=T2.ID');
SQL.ADD(' WHERE T1.COLUMN10=100');
Try to assign the value in every line:
SQL.Text := 'SELECT T1.COLUMN1,T2.COLUMN2,T1COLUMN3..........,';
SQL.Text := SQL.Text + ' T1.COLUMNn FROM TABLE1 T1 INNER JOIN ';
SQL.Text := SQL.Text + ' TABLE2 T2 ON T1.ID=T2.ID';
SQL.Text := SQL.Text + ' WHERE T1.COLUMN10=100';
I know it looks ugly, but I think it solves your problem.
I am preparing stored procedure with oracle .I m using multiple inner joins with the same table where an input parameter value is checked within each inner join. I want to eliminate particular inner join if the input parameter is null
You would need to use dynamic SQL to construct the appropriate query for the parameters like this:
PROCEDURE myproc (p1 VARCHAR2) IS
l_sql LONG;
l_cursor SYS_REFCURSOR;
BEGIN
l_sql := 'SELECT a, b, c FROM table1';
IF p1 IS NOT NULL THEN
l_sql := l_sql || ' JOIN table2 ON table2.x = table1.x';
END IF;
l_sql := l_sql || ' WHERE table1.y = :bind1';
OPEN l_cursor FOR l_sql USING 123;
...
END;