I'm working on a small subsystem for logging user activity. The system is using MS SQL Server as a database, Delphi7 and ADO for building the interface.
The problem I have is that I can't locate a record with specific datetime value.
Below is a sample reproduction of the problem:
1. Database: MS SQL Server 2005 Express Edition.
-- Table creation
CREATE TABLE [tlog] (
[USERN] [numeric](10, 0) NULL,
[USERDATE] [datetime] NULL,
[LOGTEXT] [varchar](250) COLLATE Cyrillic_General_CS_AS NULL
);
-- Insert date/time value
INSERT INTO [tlog] (USERN, USERDATE, LOGTEXT)
VALUES (1, CURRENT_TIMESTAMP, 'Record current activity')
-- Insert date only value
INSERT INTO [tlog] (USERN, USERDATE, LOGTEXT)
VALUES (1, '20180202', 'Record current activity')
-- Table's content
-------------------------------------------------------------
| USERN | USERDATE | LOGTEXT |
-------------------------------------------------------------
| 1 | 26/10/2015 17:13:36.597 | Record current activity |
-------------------------------------------------------------
| 1 | 02/02/2018 00:00:00.000 | Record current activity |
-------------------------------------------------------------
2. Sample code: Delphi 7 and ADO
procedure TfrmMain.btnLocateClick(Sender: TObject);
var
d: TDateTime;
tblLog: TADOTable;
begin
//
ThousandSeparator := ' ';
DecimalSeparator := '.';
DateSeparator := '/';
ShortDateFormat := 'dd/mm/yyyy';
LongDateFormat := 'dd/mm/yyyy';
TimeSeparator := ':';
ShortTimeFormat := 'hh:mm';
LongTimeFormat := 'hh:mm';
TwoDigitYearCenturyWindow := 50;
ListSeparator := ';';
//
tblLog := TADOTable.Create(Application);
try
//
tblLog.ConnectionString :=
'Provider=SQLOLEDB.1;'+
'Password=xxxx;'+
'Persist Security Info=True;'+
'User ID=xxxxxxxx;'+
'Initial Catalog=xxxxxxxxx;'+
'Data Source=127.0.0.1\xxxxxxx,1066';
tblLog.TableName := '[tlog]';
tblLog.Open;
// First try - locate with exact value. NOT WORKING.
d := StrToDateTime('26/10/2015 17:13:36.597');
if tblLog.Locate('USERDATE', d, []) then
ShowMessage('Exact value, no Locate options: Located')
else
ShowMessage('Exact value, no Locate options: Not located');
if tblLog.Locate('USERDATE', d, [loPartialKey]) then
ShowMessage('Exact value, with Locate options: Located')
else
ShowMessage('Exact value, with Locate options: Not located');
// Second try - locate with value that matches format settings. NOT WORKING.
d := StrToDateTime('26/10/2015 17:13');
if tblLog.Locate('USERDATE', d, []) then
ShowMessage('Hours and minutes, no Locate options: Located')
else
ShowMessage('Hours and minutes, no Locate options: Not located');
if tblLog.Locate('USERDATE', d, [loPartialKey]) then
ShowMessage('Hours and minutes, with Locate options: Located')
else
ShowMessage('Hours and minutes, with Locate options: Not located');
// Locate with date only value. WORKING.
d := StrToDateTime('02/02/2018');
if tblLog.Locate('USERDATE', d, []) then
ShowMessage('Located')
else
ShowMessage('Not located');
finally
//
tblLog.Close;
tblLog.Free;
end;
end;
3. Expected result: Locate the record.
4. Actual result: TADOTable.Locate() returns false.
What am I doing wrong and how to pass datetime values to TADOTable.Locate() method?
Thanks in advance!
You have used Locate almost correctly. Almost, because the loPartialKey option you've included is pointless when searching for TDateTime values. In this case you need to search the exact date time value. The problem is in your tests.
Your first test has a wrong date time value. Its millisecond portion is ignored in your conversion so you're actually trying to locate date time 26/10/2015 17:13:36 which is not in your table.
In second case you're trying to locate date time 26/10/2015 17:13 which is not in your table.
I would suggest using e.g. EncodeDateTime function for building date time rather than that string conversion and removing those extra calls with loPartialKey option.
Use type string to locate or where for date with format yyyy-mm-dd
example:
in locate:
if tblLog.Locate('USERDATE', '2015-10-26', []) then ...
in where:
select * from tbllog where userdate = '2015-10-26'
Related
I am trying to create a function that change the day of a football league; if the match is fixed on saturday then the function update the match day to be the previuos friday, and if the match is fixed on sunday the function update the match date to be on monday. Also the function will show how many rows have been update.
The table I use is as follow:
CREATE TABLE "183400_Matches_Details" (
"183400_Stadiums_id" INTEGER NOT NULL,
"183400_Teams_id" INTEGER NOT NULL,
"183400_Teams_id1" INTEGER NOT NULL,
"183400:Referees_id" INTEGER NOT NULL,
"183400_Matches_number" INTEGER NOT NULL,
"date" DATE NOT NULL,
result VARCHAR2(5) NOT NULL
);
I tried the following statements to build the function, but it always gives me an error:
create or replace function updateDay (
v_number "183400_Matches_Details"."183400_Matches_number"%type)
return date
as
v_fecha "183400_Matches_Details"."date"%type;
begin
SELECT TO_CHAR("date", 'DAY', 'NLS_DATE_LANGUAGE=ENGLISH') as day1 into v_fecha FROM
"183400_Matches_Details"
where "183400_Matches_number" = v_number;
if day1 = 'SATURDAY' then
update "183400_Matches_Details"
set "date" = "date"-1
where "183400_Matches_number" = v_number;
elsif day1 = 'SUNDAY' then
update "183400_Matches_Details"
set "date" = "date"+1
where "183400_Matches_number" = v_number;
end if;
return SQL%ROWCOUNT;
end;
/
select * from "183400_Matches_Details"
DECLARE
v_number "183400_Matches_Details"."183400_Matches_number"%type := &number;
v_total_filas number(8);
BEGIN
v_total_filas := actualizaPrecioCoche(v_number);
DBMS_OUTPUT.put_line('There are ' || v_total_filas || ' updated rows');
END;
/
Any ideas to make it run correctly?=)
I changed your function as it should be. Try below.
CREATE TABLE "183400_Matches_Details"
(
"183400_Stadiums_id" INTEGER NOT NULL,
"183400_Teams_id" INTEGER NOT NULL,
"183400_Teams_id1" INTEGER NOT NULL,
"183400:Referees_id" INTEGER NOT NULL,
"183400_Matches_number" INTEGER NOT NULL,
"datee" DATE NOT NULL,
RESULT VARCHAR2 (5) NOT NULL
);
CREATE OR REPLACE FUNCTION updateDay (
v_number "183400_Matches_Details"."183400_Matches_number"%TYPE)
RETURN DATE
AS
v_fecha "183400_Matches_Details"."datee"%TYPE;
sql_qry VARCHAR2 (400 CHAR);
BEGIN
sql_qry :=
'SELECT TO_CHAR(datee, ''DAY'', ''NLS_DATE_LANGUAGE=ENGLISH'') where "183400_Matches_number"='
|| v_number;
EXECUTE IMMEDIATE sql_qry INTO v_fecha;
IF v_fecha = 'SATURDAY'
THEN
UPDATE "183400_Matches_Details"
SET "datee" = "datee" - 1
WHERE "183400_Matches_number" = v_number;
ELSIF v_fecha = 'SUNDAY'
THEN
UPDATE "183400_Matches_Details"
SET "datee" = "datee" + 1
WHERE "183400_Matches_number" = v_number;
END IF;
RETURN to_date('19000101','yyyymmdd') ;
END;
/
After a second look I realized what your asking is actually quite simple: Given a date that is Sat update it to Fri, and that is Sun update to Mon. This can actually be done in a single SQL statement.
I changed it from a function to a procedure as the purpose is to Update the database, and return the number of rows processed. But as a function it makes the purpose to get the row count and updating the database as a side effect. Names and types (IMHO) should always reflect the purpose of the routine. I did 'return' the row count as an OUT parameter - it being an informational side effect. See fiddle for full example.
create or replace
procedure reschedule_sat_sun_match_details(
p_match_number in "183400_Matches_Details"."183400_Matches_number"%type
, p_rows_updated out number)
as
begin
update "183400_Matches_Details"
set "date" = case to_char("date", 'dy')
when 'sat' then "date"-1 -- Sat update to Fri
when 'sun' then "date"+1 -- Sun update to Mon
end
where to_char("date", 'dy') in ('sat','sun')
and "183400_Matches_number" = p_match_number;
p_rows_updated := sql%rowcount;
end reschedule_sat_sun_match_details;
For day of week values I used the format 'dy' rather than 'day'. The difference being 'dy' returns day name abbreviations with a constant length without padding, while 'day' pads the returned values to the length of the longest day name (to get constant length) thus "sunday" is returned as "sunday " to match the length of "wednesday".
A couple other suggestions. Avoid Mixed Case name and names beginning with numbers. These require double quoting (") on every reference. This becomes a pain to just write and your queries much harder to read and understand. a table name Matches_Details_183400 the exact same information without requiring the quotes. (Yes Oracle will make it upper case in messages it it issues but you can still write it in mixed case if you wish - it will still be the same name.) It gives you no benefit but a lot of pain.
As #hotfix mentioned do not use reserved or keywords as object names. Oracle has documented such words and reserves the right to enforce a specific meaning whenever they choose. If/When they do makes an almost untraceable bug to find.
I'm trying to insert date into temporary table, but it's not saving any data. How should I change cursor date parameters?
The procedure is running without any error message but output xx_cdf_output_utl$.log('inside loop'); is not working.
xx_cdf_output_utl$.log('after loop'); log is printing fine.
Right now added three parameters: p_date_from (date), p_date_to (date), p_line_type_lookup_code (varchar2)
procedure gather_data(
-- p_valid_invoices_count out number,
-- p_invalid_invoices_count out number,
p_date_from in date,
p_date_to in date,
p_line_type_lookup_code in varchar2) is
ROUTINE constant varchar2(65) := PACKAGE_NAME||'.GATHER_DATA';
cursor c_inv(
p_date_from in date,
p_date_to in date,
p_line_type_lookup_code in varchar2) is
select
inv.invoice_id,
inv.invoice_num,
inv.invoice_amount,
inv.invoice_date,
inv.amount_paid,
pas.gross_amount,
pas.payment_num,
ven.vendor_id,
ven.vendor_name
from
ap_invoices_all inv, -- invoice table
ap_payment_schedules_all pas, -- payment schedules table
po_vendors ven -- vendors table
where
inv.invoice_date between p_date_from and p_date_to and
inv.wfapproval_status in (
'NOT REQUIRED',
'WFAPPROVED',
'MANUALLY APPROVED') and
pas.amount_remaining != 0 and
nvl(pas.hold_flag, 'N') != 'Y' and
(p_line_type_lookup_code is not null and
exists(
select
1
from
ap_invoice_distributions_all ind -- distribution table
where
ind.amount != 0 and
ind.line_type_lookup_code = p_line_type_lookup_code and
ind.invoice_id = inv.invoice_id) or
p_line_type_lookup_code is null) and
pas.invoice_id = inv.invoice_id and
ven.vendor_id = inv.vendor_id;
l_date_from date := nvl(to_date(p_date_from, 'yyyy-mm-dd'), to_date('2019-01-01', 'YYYY-MM-DD'));
l_date_to date := nvl(to_date(p_date_to, 'yyyy-mm-dd'), trunc(to_date(sysdate, 'YYYY-MM-DD')));
l_line_type_lookup_code varchar2(240) := p_line_type_lookup_code;
begin
for l_inv_rec in c_inv(
l_date_from,
l_date_to,
l_line_type_lookup_code)
loop
xx_cdf_output_utl$.log('inside loop');
insert into xx_zm_invoice_temp(
invoice_id,
invoice_num,
invoice_amount,
invoice_date,
amount_paid,
gross_amount,
payment_num,
vendor_id,
vendor_name)
-- vendor_amount_total,
-- vendor_amount_valid_flg)
values(
l_inv_rec.invoice_id,
l_inv_rec.invoice_num,
l_inv_rec.invoice_amount,
l_inv_rec.invoice_date,
l_inv_rec.amount_paid,
l_inv_rec.gross_amount,
l_inv_rec.payment_num,
l_inv_rec.vendor_id,
l_inv_rec.vendor_name);
end loop;
xx_cdf_output_utl$.log('after loop');
exception
when xx_cdf_error_utl$.e_internal_exception then
xx_cdf_error_utl$.raise_error;
when others then
xx_cdf_error_utl$.output_unexp_exception(
p_routine => ROUTINE);
end;
When you do:
l_date_from date := nvl(to_date(p_date_from, 'yyyy-mm-dd'), to_date('2019-01-01', 'YYYY-MM-DD'));
l_date_to date := nvl(to_date(p_date_to, 'yyyy-mm-dd'), trunc(to_date(sysdate, 'YYYY-MM-DD')));
the
to_date(p_date_to, 'yyyy-mm-dd')
has to convert p_date from a date to a string first, and it will use the session NLS parameters to do that. As it isn't erroring I imagine your date format has a 2-digit year, possibly the kind-of-default DD-MON-RR. You are effectively doing something like:
to_date(to_char(p_date_to, 'dd-mon-rr'), 'yyyy-mm-dd')
which would translate today's date to 0023-03-20, which isn't what you intended at all. You end up looking for a range of dates in year 0023, or maybe 0020, so it's not surprising you do't find any matching data.
You don't need to convert the parameters to or from strings, you can do:
l_date_from date := nvl(p_date_from, date '2019-01-01');
l_date_to date := nvl(p_date_to, trunc(sysdate));
You can truncate the parameter values if you think that might be necessary, but with a trunc() call, not by bouncing through strings.
You don't really need l_date_from or l_date_to though (or l_line_type_lookup_code); you could do the nvl() firstly in the cursor call:
for l_inv_rec in c_inv(
nvl(p_date_from, date '2019-01-01'),
nvl(p_date_to, trunc(sysdate)),
p_line_type_lookup_code)
Having the same name for the procedure parameters and the cursor parameters might be confusing - though the way you're currently using them, the cursor doesn't really need the parameters - it can refer directly to the procedure values, and use nvl() internally:
inv.invoice_date between nvl(p_date_from, date '2019-01-01') and nvl(p_date_to, trunc(sysdate)) and
I'm always a bit wary of using between with dates because it's easy to forget it's inclusive, and you can accidentally pick up the same data - at exactly midnight - in more than one call. It's a bit safer and clearer to use a range:
inv.invoice_date >= nvl(p_date_from, date '2019-01-01') and
inv.invoice_date < nvl(p_date_to, trunc(sysdate)) and
decided explicitly if the last condition should use < or <=. The latter is usually what you mean if the table values have non-midnight times.
And you could use an implicit cursor, but that's partly a matter of taste...
You could even give your procedure arguments default values, but you might be restricted in how it's called.
I am trying to convert a a varchar array into a date array. Currently the date is being accessed from a Java class in the form of a String but it needs to be a date there. Therefore I need to convert the varchar array into a date array. I can only see questions here dealing with converting a string to a date format, which is not what I want. How different is it to convert a varchar array into a date array?
this is the variable which I need the procedure to convert.
P_IN_GRID_EFFECTIVE_DATE IN P_DVP_CONSTS.T_VCHAR10ARRAY,
You can loop around one array and use its contents to populate a second one; in this case with a to_date() call for each entry. As a simple demo:
create procedure convert_array(
P_IN_GRID_EFFECTIVE_DATE IN P_DVP_CONSTS.T_VCHAR10ARRAY,
P_OUT_GRID_EFFECTIVE_DATE OUT P_DVP_CONSTS.T_DATEARRAY
) as
begin
P_OUT_GRID_EFFECTIVE_DATE := new P_DVP_CONSTS.T_DATEARRAY();
P_OUT_GRID_EFFECTIVE_DATE.extend(P_IN_GRID_EFFECTIVE_DATE.count);
for i in 1..P_IN_GRID_EFFECTIVE_DATE.count loop
P_OUT_GRID_EFFECTIVE_DATE(i) :=
to_date(P_IN_GRID_EFFECTIVE_DATE(i), 'YYYY-MM-DD');
-- or whatever format your strings are using
end loop;
end convert_array;
/
And a quick test:
set serveroutput on
declare
l_strings P_DVP_CONSTS.T_VCHAR10ARRAY;
l_dates P_DVP_CONSTS.T_DATEARRAY;
begin
l_strings := P_DVP_CONSTS.T_VCHAR10ARRAY('2015-06-08', '2015-07-09',
'2015-08-10');
convert_array(l_strings, l_dates);
dbms_output.put_line('Number of dates in array: ' || l_dates.count);
for i in 1..l_dates.count loop
dbms_output.put_line('Date ' || i
|| ': ' || to_char(l_dates(i), 'DD/MM/YYYY'));
end loop;
end;
/
PL/SQL procedure successfully completed.
Number of dates in array: 3
Date 1: 08/06/2015
Date 2: 09/07/2015
Date 3: 10/08/2015
Types and function:
create type tv as varray(5) of varchar2(15);
create type td as varray(5) of date;
create or replace function v2d (i_varchars in tv)
return td pipelined is
begin
for i in 1..i_varchars.count
loop
pipe row (to_date(i_varchars(i), 'mm/dd/yyyy'));
end loop;
end;
Test:
select * from table(v2d(tv('09/01/2010', '06/15/2015')));
COLUMN_VALUE
------------
2010-01-01
2015-06-15
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.
I have a problem with a old Delphi system, this system insert data into a SQL Server table.
After 10 years change a field of the table from 100 to 255 chars long.
The system select all the registris of the table, and put them on an other table after a transformation. That works fine.
The problems are when the system update a field.
That show me the error
EDBEngineError with message 'Couldn't perform the edit because another user changed the record.
sConsulta:='SELECT * FROM cuentas WHERE (WALL= 2) AND (SEND_DATE = '01/01/1970')';
m_oQryLeg.Close;
m_oQryLeg.SQL.Clear;
m_oQryLeg.SQL.Add(sConsulta);
m_oQryLeg.Open;
m_oTblNov.Close;
m_oTblNov.TableName:='des_table';
m_oTblNov.Open;
with m_oTblNov do
begin
while (not m_oQryLeg.EOF) do
begin
Insert;
FieldbyName('COD_HOME').AsString:= m_oQryLeg.FieldByName('USR_HOME').AsString;
(...)
Post;
m_oQryLeg.Edit;
m_oQryLeg.FieldByName('SEND_DATE').AsDateTime:= Date; //<-- HERE THE ERROR
m_oQryLeg.Post;
m_oQryLeg.First;
m_oQryLeg.MoveBy(i);
inc(i);
end;
end;
m_oTblNov.Close;
m_oQryLeg.Close;
UpdateMode: upWhereAll
cuentas table:
NUM_SOL nvarchar 6 *PK
WALL tinyint 1
SEND_DATE smalldatetime 4
OBS_CRED nvarchar 255
FLCC real 4
STREET nvarchar 30
You're trying to update a query you're using on a table you're editing at the same time, and it's not going to work.
Since you know you're going to insert every row from the query into m_oTblNov, why not just do it like this instead?
sConsultaSELECT :='SELECT * FROM cuentas';
sConsultaUPDATE := 'UPDATE cuentas SET Send_Date = :New_Date';
// Separate WHERE so you can use it twice. Note the leading space
// between the first ' and WHERE.
sWhere := ' WHERE (WALL = 2) and (SEND_DATE = ''01/01/1970'')';
m_oQryLeg.Close;
m_oQryLeg.SQL.Text := sConsulta + sWhere;
m_oQryLeg.Open;
m_oTblNov.Close;
m_oTblNov.TableName:='des_table';
m_oTblNov.Open;
with m_oTblNov do
begin
while (not m_oQryLeg.EOF) do
begin
Insert;
FieldbyName('COD_HOME').AsString:= m_oQryLeg.FieldByName('USR_HOME').AsString;
(...)
Post;
// Don't run these any more. See below.
// m_oQryLeg.Edit;
// m_oQryLeg.FieldByName('SEND_DATE').AsDateTime:= Date; //<-- HERE THE ERROR
// m_oQryLeg.Post;
m_oQryLeg.First;
m_oQryLeg.MoveBy(i);
inc(i);
end;
end;
m_oTblNov.Close;
m_oQryLeg.Close;
m_oQryLeg.SQL.Text := sConsultaUPDATE + sWHERE;
m_oQryLeg.ParamByName('New_Date').AsDateTime := Date;
try
m_oQryLeg.ExecSQL;
finally
m_oQryLeg.Close;
end;
The problem here is that your object is trying to refresh the row from the database to make sure nothing has changed and more than likely the object truncated or rounded some value that is causing the refresh to return no rows.
This could be caused by either a float value being truncated or some other value being off.
if you don't/can't change that column to fix this issue I would advise changing to either upWhereChanged or upWhereKeyOnly.
Given that dates are treated as doubles in most windows databases, I would think that upWhereKeyOnly would be best.
EDIT:
After looking at your table, it may have to do with the fact you are using a single based smalldatetime. Delphi treats all DateTime data as a double and the conversion back and forth may be causing small rounding issues.