Oracle - updating a sorted table - sql

I found an old table without a primary key, and in order to add one, I have to add a new column and fill it with sequence values. I have another column which contains the time of when the record was created, so I want to insert the sequence values to the table sorted by the column with the time.
I'm not sure how to do it. I tried using PL\SQL - I created a cursor for a query that returns the table with an ORDER BY, and then update for each record the cursor returns but it didn't work.
Is there a smart working way to do this?
Thanks in advance.

Another option is just to use a correlated subquery, with the wrinkle of a nested subquery to generate the row number. Setting up some sample data:
create table t42 (datefield date);
insert into t42 (datefield) values (sysdate - 7);
insert into t42 (datefield) values (sysdate + 6);
insert into t42 (datefield) values (sysdate - 5);
insert into t42 (datefield) values (sysdate + 4);
insert into t42 (datefield) values (sysdate - 3);
insert into t42 (datefield) values (sysdate + 2);
select * from t42;
DATEFIELD
---------
12-JUL-12
25-JUL-12
14-JUL-12
23-JUL-12
16-JUL-12
21-JUL-12
Then adding and populating the new column:
alter table t42 add (id number);
update t42 t1 set t1.id = (
select rn from (
select rowid, row_number() over (order by datefield) as rn
from t42
) t2
where t2.rowid = t1.rowid
);
select * from t42 order by id;
DATEFIELD ID
--------- ----------
12-JUL-12 1
14-JUL-12 2
16-JUL-12 3
21-JUL-12 4
23-JUL-12 5
25-JUL-12 6
Since this is a synthetic key, making it match the order of another column seems a bit pointless, but I guess doesn't do any harm.
To complete the task:
alter table t42 modify id not null;
alter table t42 add constraint t42_pk primary key (id);

First of all, create new field and allow null values.
Then, update field from other table or query. Best approach is to use merge statement.
Here a sample from documentation:
MERGE INTO bonuses D
USING (SELECT employee_id, salary, department_id FROM employees
WHERE department_id = 80) S
ON (D.employee_id = S.employee_id)
WHEN MATCHED THEN UPDATE SET D.bonus = D.bonus + S.salary*.01
DELETE WHERE (S.salary > 8000)
WHEN NOT MATCHED THEN INSERT (D.employee_id, D.bonus)
VALUES (S.employee_id, S.salary*.01)
WHERE (S.salary <= 8000);
Finally, set as non null this new field and promote it to primary key.
Here sample sentences:
ALTER TABLE
customer
MODIFY
(
your_new_field varchar2(100) not null
)
;
ALTER TABLE
customer
ADD CONSTRAINT customer_pk PRIMARY KEY (your_new_field)
;

One simple way is to create a new table, with new column an all other columns:
create table newt (
newtID int primary key not null,
. . .
)
Then insert all the old data into it:
insert into newt
select row_number() over (order by <CreatedAt>), t.*
from t
(You can substitute all the columns in, instead of using "*". Having the columns by name is the better practice. This is shorter, plus, I don't know the column names.)
If you alter the table to add the column, then the column will appear at the end. I find that quite awkward for the primary key. If you do that, though, you can update it as:
with t as (select row_number() over (order by <CreatedAt>) as seqnum, t.*
from t
)
update t
set newtID = seqnum

Related

How to insert a query into SQLite with an autoincrementing value for each row

Suppose I am inserting the following queryset into a new table in SQLite:
CREATE TABLE queryset_cache AS
SELECT ROW_NUMBER() over () AS rowid, * FROM mytable ORDER BY product;
Is it possible to either:
Set the rowid as auto-incrementing PK in sqlite from the insert, or;
Exclude the rowid and have SQLite auto-add in an autoincrementing primary key for each inserted record.
How would this be done?
Currently, without that when I do the insert, the rowid is not indexed.
rowid is already there. You can just do:
CREATE TABLE queryset_cache AS
SELECT t.*
FROM mytable t
ORDER BY product;
You will see it if you do:
SELECT rowid, t.*
FROM queryset_cache;
Here is a db<>fiddle
Auo increment should solve this. Documentation here:
https://www.sqlite.org/autoinc.html
Create source table:
create table sourceTable (oldID integer, data TEXT);
Add source data:
insert into sourceTable values(7, "x");
insert into sourceTable values(8, "y");
insert into sourceTable values(9, "z");
Create target table with auto-increment:
create table target(newID INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT);
Move data from source to target:
insert into target select null, data from sourceTable
If we have a table like:
create table employee (empID integer, name text , address text);
insert data into this table.
create a table in which you want to insert employee table data:
create table newEmployee (newempID integer PRIMARY KEY, name text , address text);
copy data to newEmployee table:
insert into newEmployee select * from employee
(select * from employee) to copy all the columns

How to UPDATE column with ROW_NUMBER() in Teradata?

I'm having a table like this
Create table test1(emp_id decimal(5,0), emp_name varchar(20));
Insert into test1(2015,'XYZ');
Insert into test1(2016,'XYZ2');
Now I want to update emp_id to row_number()
or
add new column into same table like (emp_no integer) to row_number().
can anyone please tell me the query for this?
You need to use UPDATE FROM:
UPDATE test1
FROM
( SELECT ROW_NUMBER() OVER (ORDER BY emp_id) AS rn,
emp_id
FROM test1
) AS src
SET emp_id = src.rn
WHERE test1.emp_id = src.emp_id -- must be unique column(s)
Btw, instead of updating all rows of a table it might be better to INSERT/SELECT or MERGE the SELECT into a new table. You must do it if there's no unique column, you should if emp_id is the PI of your table (otherwise performance will be horrible).
Create table test1(
emp_id decimal(5,0),
emp_name varchar(20),
emp_no INTEGER GENERATED ALWAYS AS IDENTITY
(START WITH 1
INCREMENT BY 1
)
);
Insert into test1(2015,'XYZ1',2);
Insert into test1(2016,'XYZ2',2);
Insert into test1(2015,'XYZ3',null);
Insert into test1(2016,'XYZ4',null);

Insert into first row when the table has a primary key set

I spent all day creating then manually inserting into some tables I created today that look like this:
ID - Int (Primary Key set to Auto Increment)
Value - Varchar
But then I realized I had forgotten to insert a value of "--" into the first row of each table.
Is it possible to maybe add 1 to the ID no for each of the values currently in the table then insert the "--" value into the first row?
One of the ways to fix it is to update the record with the ID=1 to '--':
update yourTable set Value = '--' where id = 1
Then you will be required to re-insert the first record into the table:
INSERT INTO yourTable (Value)
VALUES('the value that was originally inserted as 1')
However, if the order of the already inserted records is important then you can insert the '--' value as the ID = 0. In this case you need to disable the IDENTITY column using the SET IDENTITY_INSERT:
SET IDENTITY_INSERT yourTable ON
INSERT INTO yourTable (ID, Value)
VALUES(0, '--')
SET IDENTITY_INSERT yourTable OFF
This way the order of inserted records will be preserved and the '--' will be inserted with the ID of 0
BTW, for mySQL you can insert into the IDENTITY column by default
Because Your ID column auto increment (IDENTITY), when you insert, you mustn't insert ID column. To insert your table, you just insert other columns.
Code insert like this:
INSERT INTO Your_Table (ColA, ColB, ...) -- `except identity colums`
VALUES (A, B, ...)
INSERT INTO Your_Table
VALUES (A, B, ...) -- except identity colums
INSERT INTO Your_Table
SELECT ColA, ColB, ... FROM Other_Table -- except identity colums
If Your_Table empty:
INSERT INTO Your_Table (Value)
VALUES ('--')
INSERT INTO Your_Table
SELECT '--'
If not:
UPDATE Your_Table
SET Value = '--'
WHERE ID = 1
------------------------ More Infor -------------------------------------------
If you want to first row have ID = 1. You can set ID column have IDENTITY(1,1). Like this:
CREATE TABLE Your_Table
(
ID INT IDENTITY(1, 1) PRIMARY KEY,
Value VARCHAR(50) NULL
)
Or, After you create Your_Table, you can set right-click Your_Table, select Design, select ID column. Look Column Properties below, expand Identity Specification, Double click (Is identity), then set value Identity Increment and Identity Seed

Constraining Child Record Based on Parent Record

In a timesheets data model, suppose I have the following parent table:
CREATE TABLE EmployeeInRole (
employeeInRoleId PRIMARY KEY,
employeeId,
roleId,
rate,
effectiveFrom DATE, --from when can this employee assume this role
effectiveTo DATE
);
and the following child table:
CREATE TABLE TimesheetEntry (
startTime DATETIME,
endTime DATETIME,
employeeInRoleId,
CONSTRAINT fk FOREIGN KEY (employeeInRoleId) REFERENCES EmployeeInRole (employeeInRoleId)
);
When I insert into TimesheetEntry, I'd like to make sure that time period falls within the boundaries of the parent record's effectiveFrom/To.
Is it possible to build this constraint into the DDL without use of a trigger, or do I have to maintain this constraint via a trigger or at the application level?
(Here is some info about Oracle only)
It's not possible in Oracle with clear DDL but you can do something like this:
create table t1 (id number primary key, date_from date, date_to date);
create table t2 (id number primary key, date_from date, date_to date, parent_id number references t1(id));
create view v as
select t2.* from t2
where exists (select 1 from t1 where t1.id = t2.parent_id
and t2.date_from between t1.date_from and t1.date_to
and t2.date_to between t1.date_from and t1.date_to)
with check option constraint chk_v;
insert into t1 values (1, sysdate - 5, sysdate); -- OK
insert into v values (1, sysdate - 4, sysdate - 3, 1); -- OK
insert into v values (1, sysdate - 6, sysdate - 3, 1); -- ERROR (WITH CHECK OPTION where-clause violation)
V is updatable view created with CHECK OPTION
"Is it possible to build this constraint into the DDL without use of a trigger,"
It is possible in some RDBMS systems, but it is not possible in SQL.

SQLite: mass updating a field without cursor

I have the following table:
CREATE TABLE Records (
RecordIndex INTEGER NOT NULL,
...
Some other fields
...
Status1 INTEGER NOT NULL,
Status2 INTEGER NOT NULL,
UpdateDate DATETIME NOT NULL,
CONSTRAINT PK_Records PRIMARY KEY (RecordIndex ASC))
And an Index:
CREATE INDEX IDX_Records_Status ON ClientRecords
(Status1 ASC, Status2 ASC, RecordIndex ASC)
I need to fetch the records of a certain status one by one, so i used this statement:
SELECT *
FROM RECORDS
WHERE RecordIndex > #PreviousIndex
AND Status1 = #Status1
AND Status2 = #Status2
LIMIT 1
But now I need to fetch the records sorted by another field, but this field is not unique for each record, so I can not use it in the same way. So I decided to add a new SortIndex field to my table.
As there are no cursors in SQLite, I am doing the following to initialize the values for SortIndex.
First I create a temporary table:
CREATE TEMP TABLE Sort (
SortIdx INTEGER PRIMARY KEY AUTOINCREMENT,
RecordIdx INTEGER )
Then I fill this table in the correct sort order:
INSERT INTO Sort
SELECT NULL, RecordIndex
FROM Records
ORDER BY SomeField ASC, RecordIndex ASC
Then I create an index on the temporary table:
CREATE INDEX IDX_Sort_RecordIdx ON Sort (RecordIdx ASC)
Then I update the SortIndex field in my Records table:
UPDATE Records
SET SortIndex =
(SELECT SortIdx
FROM Sort
WHERE RecordIdx = RecordIndex)
Then I drop the temporary table:
DROP TABLE Sort
And finaly I create a new index on my Records table
CREATE INDEX IDX_Records_Sort ON Records
(Status1 ASC, Status2 ASC, SortIndex ASC)
This allows me to do the following select
SELECT *
FROM Records
WHERE SortIndex > #PreviousSortIndex
AND Status1 = #Status1
AND Status2 = #Status2
LIMIT 1
The problem is, as the table contains around 500K records the whole thing takes around 2 minutes. It would probably have been a lot faster to initialize SortIndex with a cursor, but SQLite lacks this feature :(
Is there a faster way to do this ?
Thanks in advance !
Instead of doing an UPDATE with a correlated subquery, you should consider the INSERT OR REPLACE feature of SQLite, which will perform an UPDATE of a whole row when the primary key is a duplicate:
UPDATE Records
SET SortIndex =
(SELECT SortIdx
FROM Sort
WHERE RecordIdx = RecordIndex)
becomes
INSERT OR REPLACE INTO Records (RecordIndex, SortIndex, ...)
SELECT RecordIndex, SortIdx, ... FROM another_temporary_table_containing_all_columns.
Instead of using a temporary table containing all columns you can of course use a SELECT which joins the old table and the new one: try this inside the SQLite shell
CREATE TABLE original (id INTEGER PRIMARY KEY, content TEXT);
BEGIN TRANSACTION;
INSERT INTO original(id, content) VALUES(1, 'foo');
INSERT INTO original(id, content) VALUES(2, 'bar');
INSERT INTO original(id, content) VALUES(3, 'baz');
COMMIT TRANSACTION;
CREATE TABLE id_remap(old_id INTEGER, new_id INTEGER);
BEGIN TRANSACTION;
INSERT INTO id_remap(old_id, new_id) VALUES(2,3);
INSERT INTO id_remap(old_id, new_id) VALUES(3,2);
COMMIT TRANSACTION;
INSERT OR REPLACE INTO original (id, content)
SELECT b.new_id, a.content
FROM original a
INNER JOIN id_remap b
ON b.old_id = a.id;
SELECT * FROM original;
Result:
1|foo
2|baz
3|bar
Another option if you need to do mass updates but do not want a correlated subquery is to perform the join in a view, and to create a trigger INSTEAD OF UPDATE on that view. A problem is that you cannot have constraints that fail during the process. I suppose that the constraints are checked for each row so that might be very slow.
In the SQLite shell:
CREATE TABLE original (id INTEGER PRIMARY KEY, content TEXT);
BEGIN TRANSACTION;
INSERT INTO original(id, content) VALUES(1, 'foo');
INSERT INTO original(id, content) VALUES(2, 'bar');
INSERT INTO original(id, content) VALUES(3, 'baz');
COMMIT TRANSACTION;
CREATE TABLE id_remap(old_id INTEGER, new_id INTEGER);
BEGIN TRANSACTION;
INSERT INTO id_remap(old_id, new_id) VALUES(3,6);
COMMIT TRANSACTION;
CREATE TEMPORARY VIEW tmp_id_mapping
AS
SELECT a.content, b.old_id, b.new_id
FROM original a
INNER JOIN id_remap b
ON b.old_id = a.id;
CREATE TEMPORARY TRIGGER IF NOT EXISTS tmp_trig_id_remap
INSTEAD OF UPDATE OF content ON tmp_id_mapping
FOR EACH ROW
BEGIN
UPDATE original
SET id = new.new_id
WHERE id = new.old_id;
END;
UPDATE tmp_id_mapping
SET content = 'hello';
SELECT * FROM original;
Result:
1|foo
2|bar
6|baz
Main answer
I think, that this impossible to have quick insert in SQLlite ~500k records with indexes (and many indexes in future).
I hope someone will invent new wheel here.
Mark, I think you should avoid this type of dynamically added indexes and just add other classic indexes, no matter how much do you need them.
Also cursors not always good idea in any DMBS -- only if we need complex logic, but here in simple order by, I think it`s over-ing.
Just add classic indexes -- even if they non unique.
Or post here more details about why exactly you fill, that you should choose some dynamic way.
Also sqlite, as I see, supports offset.
SQL for tests
-- init
CREATE TABLE IF NOT EXISTS `records` (
`RecordID` int(10) default NULL,
`Status` int(10) default NULL,
`SomeField` char(50) default NULL,
`RecordIndex` int(11) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
truncate `records`;
INSERT INTO `records` (`RecordID`, `Status`, `SomeField`, `RecordIndex`) VALUES
(1, 1, 'a', 35),
(2, 1, 'b', 20),
(3, 1, 'c', 42);
-- 1st select
SELECT * FROM records WHERE Status = 1 ORDER BY SomeField ASC, RecordIndex ASC LIMIT 1 OFFSET 0;
-- update
update records set `Status` = 2 where RecordID = 1;
-- select next
SELECT * FROM records WHERE Status = 1 ORDER BY SomeField ASC, RecordIndex ASC LIMIT 1 OFFSET 1;