Split One Column into three then delete one column SQL Oracle - sql

I have data where the date and time are in one single column.
Data
What I am looking to achieve from SQL code is to split out into three columns (Date/Time/Milisecs) then delete the third column (Milisecs) leaving the table with new column names - Start_Date & Start_Time - as shown here.
Outcome_Table
I have tried a substr function but can't quite figure out how it should be written
Select *
Select WTR_Date_time
substr (help here)
from time_split_test

Assuming that WRT_DATE_TIME has the data type TIMESTAMP(3):
CREATE TABLE time_split_test (wrt_date_time TIMESTAMP(3));
Then you can add generated columns to get the date, time and milliseconds components:
ALTER TABLE time_split_test
ADD WTR_Date DATE
GENERATED ALWAYS AS (TRUNC(wrt_date_time));
ALTER TABLE time_split_test
ADD WTR_Time INTERVAL DAY(0) TO SECOND(0)
GENERATED ALWAYS AS (
( TRUNC(wrt_date_time, 'MI')
+ TRUNC(EXTRACT(SECOND FROM wrt_date_time)) * INTERVAL '1' SECOND
- TRUNC(wrt_date_time)
) DAY TO SECOND
);
ALTER TABLE time_split_test
ADD WTR_MS NUMBER(3,0)
GENERATED ALWAYS AS (MOD(EXTRACT(SECOND FROM wrt_date_time), 1) * 1000);
Then, for the sample data:
INSERT INTO time_split_test (wrt_date_time) VALUES (SYSTIMESTAMP);
Then:
SELECT *
FROM time_split_test;
Outputs (with default NLS session settings):
WRT_DATE_TIME
WTR_DATE
WTR_TIME
WTR_MS
16-MAY-22 13.35.28.570
16-MAY-22
+0 13:35:28
570
If you want to round the milliseconds to the nearest second then you can use:
UPDATE time_split_test
SET WRT_Date_Time = CAST(WRT_Date_Time AS TIMESTAMP(0));
Then the table would contain:
WRT_DATE_TIME
WTR_DATE
WTR_TIME
WTR_MS
16-MAY-22 13.35.29.000
16-MAY-22
+0 13:35:29
0
If you want to truncate the milliseconds to the start of the current second then you can use:
UPDATE time_split_test
SET WRT_Date_Time = TRUNC(wrt_date_time, 'MI')
+ TRUNC(EXTRACT(SECOND FROM wrt_date_time)) * INTERVAL '1' SECOND;
Then the table would contain:
WRT_DATE_TIME
WTR_DATE
WTR_TIME
WTR_MS
16-MAY-22 13.35.28.000
16-MAY-22
+0 13:35:28
0
db<>fiddle here

Related

How can I get the previous month if the month is kind of number, not a format of time

Now, my case is I have two kinds of variable:
LOG_DTM
LOG_DTM_ID
For the 1): It is store the data about month.
For the 2): It is the data about turning LOG_DTM into number, so it is not an expression of time, just a number.
For example, if the LOG_DTM = OCT 6 2022, then LOG_DTM_ID = 20221006.
The Question is I want to find the last month data from database,
For the LOG_DTM, I am doing in this way(it is working):
select *
from table
where
LOG_DTM between TRUNC(ADD_MONTHS(SYSDATE, -1),'MM')
and LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1))
However, for the LOG_DTM_ID, it cannot work:
select *
from table
where
LOG_DTM_ID between to_number(to_charc(TRUNC(ADD_MONTHS(SYSDATE, -1), 'MM')))
and to_number(to_charc(LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE, 'mm'), -1))))
May I know whats wrong with me? Is my logic flow wrong or syntax wrong? Thanks very much.
Use TO_CHAR and not TO_CHARC; and
Include a format model as the second argument to TO_CHAR.
You can simplify the upper bound to TRUNC(SYSDATE,'mm')-INTERVAL '1' SECOND
Select *
from table_name
Where LOG_DTM_ID between to_number(to_char(TRUNC(ADD_MONTHS(SYSDATE, -1),'MM'), 'YYYYMMDDHH24'))
AND to_number(to_char(TRUNC(SYSDATE,'mm')-INTERVAL '1' SECOND, 'YYYYMMDDHH24'))
Which, for the sample data:
CREATE TABLE table_name (
LOG_DTM DATE,
LOG_DTM_ID NUMBER(10,0)
GENERATED ALWAYS AS (TO_NUMBER(TO_CHAR(log_dtm, 'YYYYMMDDHH24')))
);
INSERT INTO table_name (log_dtm)
SELECT ADD_MONTHS(SYSDATE, -1) FROM DUAL;
Outputs:
LOG_DTM
LOG_DTM_ID
2022-09-06 10:15:39
2022090610
fiddle

Invalid seconds in timestamp field in SQL

I've a timestamp field in a table where second values SUBSTR(col,13,2) are 60+ in some places.
I want to update invalid second portion of the timestamp field and convert this kind of data into valid timestamp format DDMMYYYYHHMISS.
Sample data:
CREATE VOLATILE TABLE TEST (COL VARCHAR(50)) ON COMMIT PRESERVE ROWS;
INSERT INTO TEST (04012022000010);
INSERT INTO TEST (31012022000066);
INSERT INTO TEST (02012021000067);
COL
1 31012022000066
2 02012021000067
3 04012022000010
That's #Kendle's logic in Teradata SQL:
select
cast(substring(col from 1 for 12) as timestamp(0) format 'ddmmyyyyhhmi') +
cast(substring(col from 13 for 2) as interval second) as TS_correct,
to_char(TS_correct,'ddmmyyyyhhmiss')
from test;
I think that this is what you are needing. We convert the string without the seconds to DATETIME and add the number of seconds.
I give 2 versions because the DATETIME format requested is not the standard ISO format
The first request uses the date format as requested in the question. I give 2 versions because I don't know whether your local settings modify the automatic functions.
DDMMYYYYHHMISS
SELECT CAST(
CONCAT(
SUBSTRING(COL,1,12),'00'
) AS TIMESTAMP)
+ INTERVAL SUBSTRING(COL,11,2) second
FROM TEST;
We convert the input to ISO and then format the result to requested format.
Input: YYYY-MM-DD HH:MI:SS
Output: DDMMYYYYHHMISS
SELECT CAST(
CONCAT(
SUBSTRING(COL,5,4),'-',
SUBSTRING(COL,1,2),'-',
SUBSTRING(COL,3,2),' ',
SUBSTRING(COL,9,2),':',
SUBSTRING(COL,11,2),':00'
) AS TIMESTAMP)
+ INTERVAL SUBSTRING(COL,11,2)
FORMAT 'DDMMYYYYHHMISS'
AS corrected_date
FROM TEST;

How to add a new column to a set of Function records

I'm creating a function that returns a set of records from a table titled "Sequence". As part of this table, I am also analyzing another table ("Log_Alpha") and inserting appropriate information into the record. To date what I have done is overwrite an existing columns data, and then rename the column afterwards in my select call to the function (IE SELECT * FROM Production1(parameters) as tbl(xxx,xxx,xxx,NewColumnName Real,xxx,xxx...)
This works very well, however I need to retain the original data and therefore would prefer to rather add a new column to the record. But how do I do this, as the record is grabbing its column names directly from the table "Sequence".
My code shows rc."seqLogsIn" being the column where (average daily SED) data is being captured. This then gets called from the database by the SELECT query seen at the bottom of this code, where the column is renamed to "AvLogSED".
Here is the complete function, however you really only need to focus on the SELECT AVG("logSED") section.
To clarify, I want to create a new column in the record (called AvLogSED, as part of the function) and inject the data into that new column, instead of "misappropriating" seqLogsIn. How do I do that?
I am running PostgreSQL 9.2.9, compiled by Visual C++ build 1600, 64-bit on a Windows 7 desktop PC (local server copy also running on desktop).
-- Function: production1(timestamp without time zone, timestamp without time zone)
-- DROP FUNCTION production1(timestamp without time zone, timestamp without time zone);
CREATE OR REPLACE FUNCTION production1(tme1 timestamp without time zone, tme2 timestamp without time zone)
RETURNS SETOF record AS
$BODY$
DECLARE
rc Record;
tmeA timestamp without time zone;
tmeB timestamp without time zone;
AverageSED Real;
BEGIN
tmeA := tme1 + '1 day'::interval;
tmeB := tme2 + '1 day'::interval;
-- get average SED for all logs in time/date range
SELECT AVG("logSED")::Real
FROM "Log_Alpha"
WHERE "logTime" >= DATE_TRUNC('DAY', tme1) AND "logTime" < DATE_TRUNC('DAY', tme2 + '1 day'::interval) -- Calculate average for each day from 00:00 hours from first day thru to 23:59:59.99 for last day
INTO averageSED;
FOR rc IN
SELECT *, averageSED
FROM "Sequence"
WHERE "Sequence"."seqMinute" = 150 AND "Sequence"."seqTime" >= tmeA AND "Sequence"."seqTime" <= tmeB
ORDER BY "Sequence"."seqTime"
LOOP
rc."seqTime" = rc."seqTime" - '1 day'::interval;
-- Use a subquery to calculate the average SED for all logs for the day pertaining to this record date field
SELECT AVG("logSED")::Real into rc."seqLogsIn" FROM "Log_Alpha" -- Replace column seqLogsIn data from sequence table with Daily Average SED data. The column title can be renamed in function (SELECT) call as required
WHERE "Log_Alpha"."logTime" >= DATE_TRUNC('DAY', rc."seqTime") and "Log_Alpha"."logTime" < (DATE_TRUNC('DAY', rc."seqTime") + '1 day'::interval); -- Date truncated (always start at 00:00 hours of the day) to counter offset induced by mn variable
RETURN NEXT rc;
END LOOP;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100
ROWS 1000;
ALTER FUNCTION production1(timestamp without time zone, timestamp without time zone)
OWNER TO postgres;
This is the select function call that "renames" the appropriate column to AvLogSED.
select * from production1('2016-02-27 00:00:00','2016-03-11 00:00:00') as tbl(seqTime timestamp without time zone,seqMinute integer,AvLogSED Real,seqLogVolIn Real,seqFinishedVol Real,seqChipTonnes Real,seqSawdustTonnes Real,seqBinSortVol Real,seqTraySortVol Real,seqFlitchSortVol Real,seqBinSortPieces Real,seqTraySortPieces Real,seqFlitchSortPieces Real,seqBinSortRejects Real,seqBinSortSlash Real,seqTraySortRejects Real,seqTraySortSlash Real,seqCanterCants Real,seqSecBandCants Real,seqSecBandRecycled Real,seqCS1Cants Real,seqCS3Cants Real,seqE1Pieces Real,seqE1ManualRejects Real,seqE1OperatorRejects Real,seqE1ThicknessRejects Real,seqE1NoSortRejects Real,seqE1VolumeIn Real,seqE1VolumeOut Real,seqE2Pieces Real,seqE2ManualRejects Real,seqE2OperatorRejects Real,seqE2ThicknessRejects Real,seqE2NoSortRejects Real,seqE2VolumeIn Real,seqE2VolumeOut Real,seqE1Bypass Real,seqE2Bypass Real,seqBSVolumeIn Real,seqPrimaryRuntime Real,seqBinLugSpeed Real,seqBinLugFill Real,seqTrayLugSpeed Real,seqTrayLugFill Real,seqCompressor1kWh Real,seqCompressor2kWh Real,seqCompressor3kWh Real,seqCompressor4kWh Real,seqKiln1and2GJ Real,seqKiln3GJ Real,seqKiln4GJ Real,seqKiln5GJ Real,seqKiln6GJ Real,seqKiln7GJ Real,seqKiln8GJ Real,seqKiln9GJ Real,seqKiln10GJ Real,seqKiln11GJ Real,seqKiln12GJ Real,seqFlitchTray10 Real,seqFlitchReturn Real,seqBSLiftOut Real,seqSecCantRuntime Real,seqSecBandRuntime Real,seqSecCS1Runtime Real,seqSecCS3Runtime Real,seqEdger1Runtime Real,seqEdger2Runtime Real,seqBinSorterRuntime Real,seqTraySorterRuntime Real,seqFlitchSorterRuntime Real,seqReEntryE1Minutes Real,seqReEntryChipMinutes Real,seqAwaitingLogs Real,seqBSUtilisation Real,seqTSUtilisation Real,seqFSUtilisation Real,seqDebarkerRuntime Real,seqReEntryChipCount Real,seqReEntryE1Count Real,Reserved2 Real,Reserved3 Real,Reserved4 Real,Reserved5 Real,Reserved6 Real,AverageSED Real)
You can return an arbitrary record like this:
RETURN NEXT ROW(val1, val2, ...);
That way you are not limited by an existing type.
The solution was found to be as follows:
FOR rc IN
SELECT *, AverageSED, avDailySED
FROM "Sequence"
WHERE "Sequence"."seqMinute" = mn AND "Sequence"."seqTime" >= tmeA AND "Sequence"."seqTime" <= tmeB
ORDER BY "Sequence"."seqTime"
LOOP
By adding AverageSED and avDailySED to SELECT, these columns were automatically added to the rc record. One hook that I discovered was that I had to also explicitly push data into avDailySED in record rc. By this I mean my existing line of code:
SELECT AVG("logSED")::Real into avDailySED FROM "Log_Alpha" -- Store average SED value in new column
Would result in a blank avDailySED column. Changing to this solved the issue:
SELECT AVG("logSED")::Real into rc.avDailySED FROM "Log_Alpha" -- Store average SED value in new column

Comparision of the timestamp in procedure not working

I have written a following query in my procedure which is not inserting the records in the TEST table. The KPI definition table has the following record:
KPI_DEF_ID KPI_FREQUENCY KPI_FREQ_TIME_UNIT EVENT_ID
1000136 30 MINUTE 10028
1000137 50 MINUTE 10028
I have a application in which user want to get the records depending on the timestamp. So user can enter in the application to get the records for example older than 30 min and newer than 24 hour. And the timestamp is changable. The older than timestamp comes from the KPI DEFINITION table and which is stored in the column KPI_FREQUENCY and KPI_FREQUENCY_UNIT and it can be changabler. And the newer than timestamp is fixed and i stored it in varaible LAST_OLDER_VAL and LAST_OLDER_VAL_UNIT. I used the insert using select query to store the records in table but its not working.
create or replace PROCEDURE "EXT_TEST" AS
LAST_WF_ID Number := 0;
--LAST_UNIT NUMBER:=10;
--LAST_UNIT_VAL VARCHAR2(20);
LAST_OLDER_VAL NUMBER := 24;
LAST_OLDER_VAL_UNIT VARCHAR2(10) := 'HOUR';
CURSOR WF_WORKFLOW_CUR IS
Select KPI_DEF_ID,KPI_FREQUENCY,KPI_FREQ_TIME_UNIT,EVENT_ID,BUSINESS_CHECK_PERIOD_ID FROM RATOR_MONITORING_CONFIGURATION.KPI_DEFINITION where EVENT_ID=10028;
BEGIN
--DBMS_OUTPUT.PUT_LINE('LAST_UNIT - ' || LAST_UNIT);
--DBMS_OUTPUT.PUT_LINE('LAST_UNIT_VAL - ' || LAST_UNIT_VAL);
-- removed, retrieve a new START_ID from source first, don't use the last id.
--SELECT LAST_TASK_ID INTO LAST_WF_ID FROM CAPTURING where DB_TABLE='TEMP_WF_WORKFLOW';
FOR WF_WORKFLOW_ROW IN WF_WORKFLOW_CUR
LOOP
--select MIN(ID) INTO LAST_WF_ID from WF_WORKFLOW#FONIC_RETAIL WF where WF.START_DATE > sysdate - numtodsinterval ( WF_WORKFLOW_ROW.KPI_FREQUENCY, WF_WORKFLOW_ROW.KPI_FREQ_TIME_UNIT );
Insert into TEST(ID,NAME,SUBSCRIPTION_ID,START_DATE,STATUS_ID,ACCOUNT_ID,END_DATE)
Select DISTINCT(WF.ID),WF.NAME,WF.SUBSCRIPTION_ID,WF.START_DATE,WF.STATUS_ID,WF.ACCOUNT_ID,WF.END_DATE
from WF_WORKFLOW#FONIC_RETAIL WF where WF.STATUS_ID = 0 and WF.NAME = 'SIGNUP_MOBILE_PRE_PAID'
and WF.START_DATE > SYSDATE - numtodsinterval ( LAST_OLDER_VAL, LAST_OLDER_VAL_UNIT
and WF.START_DATE < SYSDATE - numtodsinterval ( WF_WORKFLOW_ROW.KPI_FREQUENCY, WF_WORKFLOW_ROW.KPI_FREQ_TIME_UNIT ));
DBMS_OUTPUT.PUT_LINE('WF_WORKFLOW_ROW.KPI_FREQUENCY - ' || WF_WORKFLOW_ROW.KPI_FREQUENCY);
DBMS_OUTPUT.PUT_LINE('WF_WORKFLOW_ROW.KPI_FREQ_TIME_UNIT - ' || WF_WORKFLOW_ROW.KPI_FREQ_TIME_UNIT);
END LOOP;
END EXT_TEST;
You're currently looking for a start date that is older than 24 hours and newer than 30 minutes. Which is impossible, and not what you stated you needed, so that isn't what you mean really. Looks like you just have your < and > comparisons the wrong way around:
...
and WF.START_DATE > SYSDATE - numtodsinterval ( LAST_OLDER_VAL, LAST_OLDER_VAL_UNIT )
and WF.START_DATE < SYSDATE - numtodsinterval ( WF_WORKFLOW_ROW.KPI_FREQUENCY, WF_WORKFLOW_ROW.KPI_FREQ_TIME_UNIT );
Not directly relevant, but I'm not sure why you're using a loop for this, rather than a single insert ... select which joins WF_WORKFLOW_CUR and WF_WORKFLOW#FONIC_RETAIL. Or really why you'd use a stored procedure at all.

Update only date without modifying time

I have data in my database table as
last_updated_product
01/Jan/1899 6:25:01 AM
01/Jan/1899 6:25:02 AM
How can I update only the date part with sysdate without modifying the time part?
Expected outout
21/Aug/2013 6:25:01 AM
21/Aug/2013 6:25:02 AM
last_updated_product column data type is defined as date
Thanks
You need to use midnight on the current day, and add on the time part from the original value:
trunc(sysdate) + (last_updated_product - trunc(last_updated_product))
trunc() gives you the date with the time component set to 00:00:00, so date - trunc(date) gives you just the original time component, as a number (fraction of a day) as per the datetime arithmetic rules. That number can then be added on to midnight today.
Not sure if you're actually updating the table or just doing this in a query, but it's the same calculation either way.
You can either work out the time portion and add in the date you want, for instance:
update my_table
set last_updated_product =
to_date('21/Aug/2013', 'dd/Mon/yyyy')
-- difference between the start of the day and the time
+ (last_updated_product - trunc(last_updated_product))
The extra brackets are to ensure the query works according to the operator order of preference as you can't add a date to a date but you can add an interval. The brackets ensure that last_updated_product - trunc(last_updated_product) is evaluated before the addition takes place.
or convert it to a character, concatenate it to the date and then convert it back to a date.
update my_table
set last_updated_product =
to_date('21/Aug/2013' || to_char(last_updated_product, 'hh24:mi:ss')
, 'dd/Mon/yyyyhh24:mi:ss')
e.g.
create table my_table ( last_updated_product date );
Table created.
insert into my_table values (sysdate - 100);
1 row created.
update my_table
set last_updated_product =
to_date('21/Aug/2013', 'dd/Mon/yyyy')
+ (last_updated_product - trunc(last_updated_product))
;
1 row updated.
select * from my_table;
LAST_UPDATED_PRODUC
-------------------
2013/08/21 08:13:57
try
update <table>
set last_updated_product =
last_updated_product
- trunc(last_updated_product )
+ trunc(sysdate)
where <condition>
;