Oracle SQL: using LAG function with user-defined-type returns "inconsistent datatypes" - sql

I have a type MyType defined as follows:
create or replace type MyType as varray(20000) of number(18);
And a table MyTable defined as follows:
create table MyTable (
id number(18) primary key
,widgets MyType
)
I am trying to select the widgets for each row and its logically previous row in MyTable using the following SQL:
select t.id
,lag(t.widgets,1) over (order by t.id) as widgets_previous
from MyTable t
order by t.id;
and I get the response:
ORA-00932: inconsistent datatypes: expected - got MYSCHEMA.MYTYPE
If I run the exact same query using a column of type varchar or number instead of MyType it works fine.
The type of the column in the current row and its previous row must be the same so I can only assume it is something related to the user defined type.
Do I need to do something special to use LAG with a user defined type, or does LAG not support user defined types? If the latter, are there any other utility functions that would provide the same functionality or do I need to do a traditional self join in order to achieve the same?

After reading all the above I've opted for the following as the most effective method for achieving what I need:
select curr.id
,curr.widgets as widgets
,prev.widgets as previous_widgets
from (select a.id
,a.widgets
,lag(a.id,1) over (order by a.id) as previous_id
from mytable a
) curr
left join mytable prev on (prev.id = curr.previous_id)
order by curr.id
ie. a lag / self join hybrid using lag on a number field that it doesn't complain about to identify the join condition. It's fairly tidy I think and I get my collections as desired. Thanks to everyone for the extremely useful input.

You can use lag with UDT. The problem is varray
Does this give you a result?
select t.id
,lag(
(select listagg(column_value, ',') within group (order by column_value)
from table(t.widgets))
,1) over (order by t.id) as widgets_previous
from MyTable t
order by t.id;

You could try something like:
SQL> create or replace type TestType as varray(20000) of number(18);
Type created.
SQL> create table TestTable (
id number(18) primary key
,widgets TestType
)
Table created.
SQL> delete from testtable
0 rows deleted.
SQL> insert into TestTable values (1, TestType(1,2,3,4))
1 row created.
SQL> insert into TestTable values (2, TestType(5,6,7))
1 row created.
SQL> insert into TestTable values (3, TestType())
1 row created.
SQL> insert into TestTable values (4,null)
1 row created.
SQL> commit
Commit complete.
SQL> -- show all data with widgets
SQL> select t.id, w.column_value as widget_ids
from testtable t, table(t.widgets) w
ID WIDGET_IDS
---------- ----------
1 1
1 2
1 3
1 4
2 5
2 6
2 7
7 rows selected.
SQL> -- show with lag function
SQL> select t.id, lag(w.column_value, 1) over (order by t.id) as widgets_previous
from testtable t, table(t.widgets) w
ID WIDGETS_PREVIOUS
---------- ----------------
1
1 1
1 2
1 3
2 4
2 5
2 6
7 rows selected.

Related

How to correct my Snowflake Unique Constraint SQL statement?

I have a table that looks like:
ID|CREATED |VALUE
1 |1649122158|200
1 |1649122158|200
1 |1649122158|200
That I'd like to look like:
ID|CREATED |VALUE
1 |1649122158|200
And I run the following query:
DELETE FROM MY_TABLE T USING (SELECT ID,CREATED,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CREATED DESC) AS RANK_IN_KEY FROM MY_TABLE T) X WHERE X.RANK_IN_KEY <> 1 AND T.ID = X.ID AND T.CREATED = X.CREATED
But it removes everything from MY_TABLE and not just other rows with the same value. This is more than just selecting distinct records, I'd like to enforce a unique constraint to get the latest value of ID and keep just one record for it, even if there were duplicates.
So
ID|CREATED |VALUE
1 |1649122158|200
1 |1649122159|300
2 |1649122158|200
2 |1649122158|200
3 |1649122170|500
3 |1649122160|200
Would become (using the same final unique constraint statement):
ID|CREATED |VALUE
1 |1649122159|300
2 |1649122158|200
3 |1649122170|500
How can I improve my logic to properly handle these unique constraint modifications?
Check out this post: https://community.snowflake.com/s/question/0D50Z00008EJgemSAD/how-to-delete-duplicate-records-
If all columns make up a unique records, the recommended solution is the insert all the records into a new table with SELECT DISTINCT * and do a swap. You could also do a INSERT OVERWRITE INTO the same table.
Something like INSERT OVERWRITE INTO tableA SELECT DISTINCT * FROM tableA;
The following setup should leave rows with id of 1 and 3. And not delete all rows as you say.
Schema
create table t (
id int,
created int ,
value int
);
insert into t values(1, 1649122158, 200);
insert into t values(1 ,1649122159, 300);
insert into t values(2 ,1649122158, 200);
insert into t values(2 ,1649122158, 200);
insert into t values(3 ,1649122170, 500);
insert into t values(3 ,1649122160, 200);
Delete statement
with x as (
SELECT
id, created,
row_number() over(partition by id) as r
FROM t
)
delete from t
using x
where x.id = t.id and x.r <> 1 and x.created = t.created
;
Output
select * from t;
1 1649122158 200
3 1649122170 500
The logic is such, that the table in the using clause is joined with the operated on table. Following the join logic, it just matches by some key. In your case, you have key as {id,created}. This key is duplicated for rows with id of 2. So the whole group is deleted.
I'm no savvy in database schemas. But as a thought, you may add a row with a rank to existing table. And after that you can proceed with deletion. This way you do not need to create other table and insert values to that. Be warned that data may become fragmented(physically, on disks). So you will need to run some kind of tune up later.
Update
You may find this almost one-liner interesting:
SO answer
I will duplicate code here, as it is so small and well written.
WITH
u AS (SELECT DISTINCT * FROM your_table),
x AS (DELETE FROM your_table)
INSERT INTO your_table SELECT * FROM u;

Datatype in sql developer is opaque

So I am debugging a part of my code. I created datatype called array2D. which is a table of number_array which is table of number.
create or replace type NUMBER_ARR is table of NUMBER;
create or replace TYPE ARRAY2D AS TABLE OF NUMBER_ARR;
I compile both types for debug and after doing so i am able to see each row of the table. But not individual elements stored in it. Element's type is listed as opaque. I had this for the whole table before I compiled datatype for debug.
create or replace type NUMBER_ARR is table of NUMBER;
/
create or replace TYPE ARRAY2D AS TABLE OF NUMBER_ARR;
/
create table t (id integer primary key, n array2d)
nested table n store as n_a(nested table column_value store as n_c);
insert into t values(1, array2d(number_arr(1,2), number_arr(3,4)));
insert into t values(2, array2d(number_arr(1,4)));
Select * from t displays:
ID N
----------------------------
1 [unsupported data type]
2 [unsupported data type]
Now, to display the values, you have to convert each element into table and join like so:
select t1.id, t3.column_value value
from t t1, table(t1.column_value) t2, table(t2.column_value) t3;
ID value
----------------
1 1
1 2
1 3
1 4
2 1
2 4
To display all the values per ID, you may use listagg
select t1.id,
listagg(t3.column_value, ', ')
within group (order by t3.column_value) value
from t t1, table(t1.column_value) t2, table(t2.column_value) t3
group by t1.id;
ID value
----------------
1 1, 2, 3, 4
2 1, 4
Ofcourse, it's displaying all the element of 2d array as single string of numbers.
The proper display (i.e. each array inside its own brackets like ((1,2,3,4),(4,5,6))) is not possible as per your current type definitions.
This doesn't work:
select id, '(' || listagg(value, ',')
within group (order by value) || ')' from
(select t1.id, t2.column_value c, '(' || listagg(t3.column_value, ',')
within group (order by t3.column_value) ||')' value
from t t1, table(t1.column_value) t2, table(t2.column_value) t3
group by t1.id, c)
group by id;
To make query like above work, you have to define object type and define map and order functions.
Lastly, I want to say you should stick with regular datatypes as they much more efficient.
P.S. - If there is no unique key, then you have to use rownum or rowid.

In SQL, how to get MERGE to update relevant row(s) with a single row from a grouped set of returned results

I am using MERGE (Oracle) to do updates to records that match on criteria specified in the ON clause joined on a virtual table create by a subquery. Form of the statement is:
MERGE INTO table1 t1 USING SELECT (t2.f21, MAX(t2.f22), t3.f31, t3.f32
from
table2 t2, table3 t3
where
{... various join/filter criteria ...}
group by t2.f21, t3.f31, t3.f32) MATCHDATA
ON (t1.f11 = MATCHDATA.f21)
where t1.f12 = 'something';
Now the rub: MATCHDATA will return multiple rows because the "... criteria ..." will by nature return multiple groups of matching records. So the 'group by' along with the use of 'MAX()' is not buying me any guarantees; on the contrary, if I added:
where rownum = 1
after MATCHDATA after wrapping the MATCHDATA result in a another SELECT statement that simply repeated the returned field names, I would then be limiting myself to being able to update only the one record in the one group of records that needs updating that has the highest value as determined by MAX(). Instead, I need to have the records in table1 that match on the join field in each MAX() record for their group of records updated. I started on Fri. down the PARTITION BY path and am new to that one, so didn't make much headway. But it looks promising and maybe tomorrow will yield better results. As it is, when I try to use it without for example limiting the returned recordset in MATCHDATA to one record via use of "rownum = 1", I get that familiar "could not return a stable set of records" error that MERGE proponents (like myself) must smile sheepishly at when their colleagues come to them for advice on this "better-than-correlated-subqueries"-evangelized nouveau SQL command as they face this same error.
As you can see, I am treating MERGE as the more successful brother of the correlated subquery. But is this a case where I should be looking back to the lesser of two weevils (i.e., use a correlated subquery instead) to get the job done? Or is the fix to be found in PARTITION BY or another modification to the above?
Thanks to all who take the time to offer their advice, I appreciate it.
I get that familiar "could not return a stable set of records" error
Because the join key you have used in the ON clause is not enough to make the row unique to perform the WHEN MATCHED THEN UPDATE statement.
You must include more keys in the ON clause until the matched rows are unique and thus returning a stable set of records.
Let's see a test case:
Set up
SQL> CREATE TABLE source_table (
2 col1 NUMBER,
3 col2 VARCHAR2(10),
4 col3 VARCHAR2(10)
5 );
Table created.
SQL>
SQL> INSERT INTO source_table (col1, col2, col3) VALUES (1, 'a', 'p');
1 row created.
SQL> INSERT INTO source_table (col1, col2, col3) VALUES (1, 'b', 'q');
1 row created.
SQL> INSERT INTO source_table (col1, col2, col3) VALUES (2, 'c', 'r');
1 row created.
SQL> INSERT INTO source_table (col1, col2, col3) VALUES (3, 'c', 's');
1 row created.
SQL>
SQL> COMMIT;
Commit complete.
SQL>
SQL> CREATE TABLE target_table (
2 col1 NUMBER,
3 col2 VARCHAR2(10),
4 col3 VARCHAR2(10)
5 );
Table created.
SQL>
SQL> INSERT INTO target_table (col1, col2, col3) VALUES (1, 'b', 'p');
1 row created.
SQL> INSERT INTO target_table (col1, col2, col3) VALUES (3, 'd', 'q');
1 row created.
SQL>
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT * FROM source_table;
COL1 COL2 COL3
---------- ---------- ----------
1 a p
1 b q
2 c r
3 c s
SQL> SELECT * FROM target_table;
COL1 COL2 COL3
---------- ---------- ----------
1 b p
3 d q
SQL>
Error reproduce
SQL> MERGE INTO target_table trg
2 USING source_table src
3 ON (trg.col1 = src.col1) -- Not Unique
4 WHEN MATCHED THEN UPDATE SET
5 trg.col2 = src.col2,
6 trg.col3 = src.col3
7 WHEN NOT MATCHED THEN INSERT
8 (
9 col1,
10 col2,
11 col3
12 )
13 VALUES
14 (
15 src.col1,
16 src.col2,
17 src.col3
18 );
USING source_table src
*
ERROR at line 2:
ORA-30926: unable to get a stable set of rows in the source tables
SQL>
So, as expected we get the error ORA-30926: unable to get a stable set of rows in the source tables
Let's make the ON clause unique.
SQL> MERGE INTO target_table trg
2 USING source_table src
3 ON (trg.col1 = src.col1
4 AND
5 trg.col2 = src.col2) -- Unique
6 WHEN MATCHED THEN UPDATE SET
7 trg.col3 = src.col3
8 WHEN NOT MATCHED THEN INSERT
9 (
10 col1,
11 col2,
12 col3
13 )
14 VALUES
15 (
16 src.col1,
17 src.col2,
18 src.col3
19 );
4 rows merged.
SQL> SELECT * FROM target_table;
COL1 COL2 COL3
---------- ---------- ----------
1 b q
3 d q
2 c r
3 c s
1 a p
SQL>
Problem solved!
Remember, you cannot update the columns which are referenced in the ON clause.
Let's say we have this table T2:
C1 C2 AMOUNT UF
-- -- ---------- ----------
A X 12 101
A Y 3 102
A Y 12 103
B X 7 104
B Y 9 105
I need to have the records in table1 that match on the join field in
each MAX() record for their group of records updated. I started on
Fri. down the PARTITION BY path and am new to that one, so didnt make
much headway.
This is good path and you can do this using function rank():
select * from (
select t2.*, rank() over (partition by c1 order by amount desc) rn from t2 )
where rn=1
C1 C2 AMOUNT UF RN
-- -- ---------- ---------- --
A X 12 101 1
A Y 12 103 1
B Y 9 105 1
But if your joining field for merge is only 'C1' then this set of records is not stable, because for C1='A'
we have two rows and Oracle looks sheepishly, it does not know which one interests you.
To resolve this you can use row_number()
instead of rank() - if it's all the same. But if this matters you need something more in order clause, for instance:
select * from (
select t2.*, rank() over (partition by c1 order by amount desc, c2) rn from t2 )
where rn = 1
C1 C2 AMOUNT UF RN
-- -- ---------- ---------- --
A X 12 101 1
B Y 9 105 1
This set of rows is stable, because for C1 there are no duplicates and you can use it in your merge.
merge into t1
using (
select * from (
select t2.*, rank() over (partition by c1 order by amount desc, c2) rn from t2 )
where rn=1) md
on (md.c1 = t1.c1)
when matched then update set t1.uf = md.uf
when not matched then insert (t1.c1, t1.uf)
values (md.c1, md.uf)

Assigning a unique id to the first instance of a string in PL/SQL

I have a set of data that has multiple rows with the same unique_string_identifier. I want to assign a new unique ID from a sequence to the first instance of a row with that unique_string_identifier then give any following rows with the same unique_string_identifier the ID times -1. I've tried it three different ways, but I always get
ORA-30483: window functions are not allowed here
Here are my attempts:
UPDATE my_table
set my_id =
CASE WHEN LAG(unique_string_identifier, 1, '-') OVER (order by unique_string_identifier) <> unique_string_identifier THEN my_id_seq.nextval
ELSE LAG(-1 * my_id, 1, '-') OVER (order by unique_string_identifier) END CASE
where import_run_id = a_run_id;
I've also tried this:
UPDATE my_table
set my_id = my_id_seq.nextval
where row_number() over (partition by unique_string_identifier order by line_id) = 1;
//another update statement to make the rows with null ID's equal to the negative id joined on unique_string_identifier
And this:
UPDATE my_Table
set my_id =
decode(unique_string_identifier, LAG(unique_string_identifier, 1, '-') OVER (order by unique_string_identifier), LAG( my_id, 1, '-') OVER (order by unique_string_identifier), my_id_seq.nextval)
where import_run_id = a_run_id;
How can I make this work?
EDIT: Also for my own enrichment, if anyone can explain why these 3 statements (which all seem pretty different to me) end up getting the exact same ORA error, I'd appreciate it.
I couldn't work out a simple MERGE or set of UPDATEs, but here is a potential solution that might work fine, tested on Oracle 11g, using PL/SQL:
Test scenario:
create table my_table (unique_string varchar2(100));
insert into my_table values ('aaa');
insert into my_table values ('aaa');
insert into my_table values ('aaa');
insert into my_table values ('bbb');
insert into my_table values ('bbb');
insert into my_table values ('ccc');
alter table my_table add (id number);
create sequence my_seq;
Here's the PL/SQL to do the update:
declare
cursor c is
select unique_string
,row_number()
over (partition by unique_string order by 1)
as rn
from my_table
order by unique_string
for update of id;
r c%rowtype;
begin
open c;
loop
fetch c into r;
exit when c%notfound;
if r.rn = 1 then
update my_table
set id = my_seq.nextval
where current of c;
else
update my_table
set id = my_seq.currval * -1
where current of c;
end if;
end loop;
close c;
end;
/
Results from my test (note that the sequence had advanced a little by this stage):
select * from my_table;
UNIQUE_STRING ID
============= ==
aaa 7
aaa -7
aaa -7
bbb 8
bbb -8
ccc 9
P.S. I've been a bit sneaky and taken advantage of Oracle's tendency to return ROW_NUMBER in the order that the rows are returned; to be more robust and correct, I'd put the query in a subquery and ORDER BY unique_string, rn.

Counting a cell up per Objects

i got a problem once again :D
a little info first:
im trying to copy data from one table to an other table(structure is the same).
now one cell needs to be incremented, beginns per group at 1 (just like a histroy).
i have this table:
create table My_Test/My_Test2 (
my_Id Number(8,0),
my_Num Number(6,0),
my_Data Varchar2(100));
(my_Id, my_Num is a nested PK)
if i want to insert a new row, i need to check if the value in my_id already exists.
if this is true then i need to use the next my_Num for this Id.
i have this in my Table:
My_Id My_Num My_Data
1 1 'test1'
1 2 'test2'
2 1 'test3'
if i add now a row for my_Id 1, the row would look like this:
i have this in my Table:
My_Id My_Num My_Data
1 3 'test4'
this sounds pretty easy ,now i need to make it in a SQL
and on SQL Server i had the same problem and i used this:
Insert Into My_Test (My_Id,My_Num,My_Data)
SELECT my_Id,
(
SELECT
CASE (
CASE MAX(a.my_Num)
WHEN NULL
THEN 0
Else Max(A.My_Num)
END) + b.My_Num
WHEN NULL
THEN 1
ELSE (
CASE MAX(a.My_Num)
WHEN NULL
THEN 0
Else Max(A.My_Num)
END) + b.My_Num
END
From My_Test A
where my_id = 1
)
,My_Data
From My_Test2 B
where my_id = 1;
this Select gives null back if no Rows are found in the subselect
is there a way so i could use max in the case? and if it give null back it should use 0 or 1?
Edit:
Im usung now this:
Insert INTO My_Test ( My_Id,My_Num,My_Data )
SELECT B.My_Id,
(
SELECT COALESCE(MAX(a.My_Num),0) + b.my_Num
FROM My_Test A
Where a.My_Id = b.My_Id)
,b.My_Data
FROM My_Test2 B
WHERE My_Id = 1
THX to Bharat and OMG Ponies
greets
Auro
Try this one
Insert Into My_Test (My_Id,My_Num,My_Data)
SELECT my_Id,(
SELECT MAX(NVL(My_Num,0)) + 1
From My_Test
where my_id = b.my_id
)
,My_Data
From My_Test2 B
where my_id = <your id>;
Insert Into My_Test (My_Id,My_Num,My_Data)
select My_id,coalesce(max(My_num),0),'test4' from My_Test
where My_id=1
group by My_id
All solutions have a problem in that they don't work in a multi user environment. If two sessions issue that insert statement at the same time, they would both get the same (my_id,my_num) combination, and one of them will fail with a ORA-00001 unique constraint violation. Therefore, if you need this to work in a multi user environment, the best advice is to use only one primary key column and populate it with a sequence. Keep your my_id column as well, as that is a sort-of-grouping column or foreign key column. If your end users really like to see the "my_num" column in their (web) application, you can use the row_number analytic function.
You can read more about this scenario in this blogpost of mine: http://rwijk.blogspot.com/2008/01/sequence-within-parent.html
Regards,
Rob.