Update Oracle table based on nested select incl. package function - sql

I have a query which calls a function in a package to create a percentage value as a sum of about 30 columns.
What I'd like to do is update each row based on the "sum of counted columns" as a percentage.
The select query is:
SELECT
checklist_id,
row_status,
eba_cm_checklist_std.get_row_percent_complete(pc.id,pc.checklist_id,pc.max_col_num) AS percent_complete
FROM
(
SELECT
(
SELECT
COUNT(id)
FROM
eba_cm_checklist_columns
WHERE
checklist_id = r.checklist_id
) AS max_col_num,
r.*
FROM
eba_cm_checklist_rows r
ORDER BY
r.row_order,
r.name
) pc
and the package.function that creates the percentage is "eba_cm_checklist_std.get_row_percent_complete".
The query outputs the following:
checklist_id row_status percent_complete
97176759931088640236098007249022291412 Red 0
97176759931071715274623402440576404948 Red 0
97176759931071715274623402440576404948 Red 0
97176759931071715274623402440576404948 Red 0
97176759931088640236098007249022291412 Red 0
97176759931088640236098007249022291412 Red 0
97176759931081386681180319473974054356 Grey 100
97176759931051163535689953744606399956 Grey 100
The difficulty I'm having is that the expression is using a nested select statement based on the output of a function and I can't get my head around how to update the physical "eba_cm_checklist_rows" table based on the output of the query.
Basically, I want to do the following:
update set row_status = 'Green' where percent_complete = 100

Another option is to use bulk select/update:
declare
type t_rid_arr is table of rowid index by pls_integer;
type t_row_status_arr is table of eba_cm_checklist_rows.row_status%type index by pls_integer;
type t_percent_complete_arr is table of number index by pls_integer;
l_rid_arr t_rid_arr;
l_row_status_arr t_row_status_arr;
l_percent_complete_arr t_percent_complete_arr;
begin
SELECT
rid,
row_status,
eba_cm_checklist_std.get_row_percent_complete(pc.id,pc.checklist_id,pc.max_col_num) AS percent_complete
bulk collect into
l_rid_arr,
l_row_status_arr,
l_percent_complete_arr
FROM (
SELECT r.rowid as rid,
r.*,
count(*) over(partition by checklist_id) AS max_col_num
FROM eba_cm_checklist_rows r
) pc;
forall i in 1..l_rid_arr.count
update eba_cm_checklist_rows
set row_status = case when l_percent_complete_arr(i) = 100 then 'Green' else row_status end
where rowid = l_rid_arr(i);
end;
/

you could try to use CTE and SUM() OVER() function, hope you find it useful.

Try something like this:
merge into eba_cm_checklist_rows trg
using (
SELECT
rid,
checklist_id,
row_status,
eba_cm_checklist_std.get_row_percent_complete(pc.id,pc.checklist_id,pc.max_col_num) AS percent_complete
FROM
(
SELECT r.rowid as rid,
r.*,
count(*) over(partition by checklist_id) AS max_col_num
FROM eba_cm_checklist_rows r
) pc
) src
on (trg.rowid = src.rid)
when matched then update set row_status = 'Green' where percent_complete = 100;
I don't like the idea to use function inside the SQL - it's probably could be replaced with normal SQL.

Related

need to use update the alternate rows of the data getting by the below query. Not able to use widows function in update statement ms sql server

I need some help with below query: I want to update every alternate row of a table given some conditions, which includes multiple tables I am not able to use windows function under update how can I modify this query to work
UPDATE loanacct
SET
collection_officer_no =
(
CASE
WHEN
ROW_NUMBER()OVER (ORDER BY acctrefno) %2 = 0
THEN
4
ELSE
7
END
)
WHERE acctrefno in
(
SELECT
[acctrefno]
FROM
[NLS].[dbo].[loanacct] L
INNER JOIN nlsusers U ON U.userno = L.collection_officer_no
WHERE
U.username like 'house' AND
L.loan_group_no in ( '2', '4', '5') AND`enter code here`
L.days_past_due > 25 AND
status_code_no = 0)
You can use a updatable CTE. This is pseudo-SQL, but should get you on the right path:
WITH CTE AS(
SELECT {YourColumns},
ROW_NUMBER() OVER (/* PARTITION BY ??? */ ORDER BY {Column} AS RN
FROM YourTable
WHERE ...
)
UPDATE CTE
SET ...
WHERE RN % 2 = 0;

How do I UPDATE from a SELECT in Informix?

I'm trying to do an update using data from another table. I've tried this answer (the second part), but it is not working for me. I'm receiving a generic error message of syntax error.
I've also tried this solution and received a syntax error message too.
If I try to update just one column, it works:
UPDATE dogs
SET name =
(
SELECT 'Buddy'
FROM systables
WHERE tabid = 1
);
But I need to update multiples columns. Unfortunately, this is not working:
UPDATE dogs
SET (name, breed) =
(
SELECT 'Buddy', 'pug'
FROM systables
WHERE tabid = 1
);
Informix version is 12.10.FC8
You are missing 1 more set of parentheses around the subquery.
From the Informix manual:
The subquery must be enclosed between parentheses. These parentheses
are nested within the parentheses that immediately follow the equal (
= ) sign. If the expression list includes multiple subqueries, each subquery must be enclosed between parentheses, with a comma ( , )
separating successive subqueries:
UPDATE ... SET ... = ((subqueryA),(subqueryB), ... (subqueryN))
The following examples show the use of subqueries in the SET clause:
UPDATE items
SET (stock_num, manu_code, quantity) =
(
(
SELECT stock_num, manu_code
FROM stock
WHERE description = 'baseball'
),
2
)
WHERE item_num = 1 AND order_num = 1001;
UPDATE table1
SET (col1, col2, col3) =
(
(
SELECT MIN (ship_charge), MAX (ship_charge)
FROM orders
),
'07/01/2007'
)
WHERE col4 = 1001;
So in order for your update to be accepted by Informix it has to be:
UPDATE dogs
SET (name, breed) =
(
(
SELECT 'Buddy', 'pug'
FROM systables
WHERE tabid = 1
)
);

Error with SELECT statement in a FOR loop

When I try to execute the code below I receive this error here:
Error(9,4): PLS-00103: Encountered the symbol "SELECT" when expecting
one of the following: ( ) - + case mod new not null table continue avg count current exists max min prior sql
stddev sum variance execute multiset the both leading trailing
forall merge year month day hour minute second timezone_hour
timezone_minute timezone_region timezone_abbr time timestamp
interval date
CREATE OR REPLACE PROCEDURE PROC_LIST_SIMILAR_TVSERIES
(seriesName IN SERIES.NAME%TYPE)
AS
CURSOR series IS (SELECT IDS FROM SERIES WHERE NAME = seriesName);
allSeries SERIES%ROWTYPE;
BEGIN
FOR series IN allSeries
(SELECT 2* ( SELECT COUNT(*)
FROM DICT d
WHERE d.idt IN ( SELECT DISTINCT IDT
FROM POSTING
WHERE IDS = series
INTERSECT
SELECT DISTINCT IDT
FROM POSTING
WHERE IDS = allSeries.IDS
)
)
/ ( ( SELECT DISTINCT COUNT(IDT)
FROM POSTING
WHERE IDS = series
) +
( SELECT DISTINCT COUNT(IDT)
FROM POSTING
WHERE IDS = allSeries.IDS )
)
INTO similarity
FROM SERIES s1
SERIES s2
WHERE s1.IDS = series
AND s2.IDS != series
);
IF similarity > 0.7 THEN
DBMS_OUTPUT.PUT_LINE('ok');
END LOOP;
END;
/
What the code does is take in a name, find it's ID, and compare it to other id's (and avoid comparing it to the same ID). I'm trying to print out "ok" whenever the similarity calculation is over 0.7 . No idea why this doesn't work.
First, if you have a cursor named the same as the table, what is the series%rowtype going to look like? The cursor or the table? Bad idea.
Second, you never execute the cursor to get the ID, so your subsequent cursor loop is looking for records that match allSeries.IDS which is null because you haven't populated it.
Try this as a starting point, although I'm guessing that you still will have work to do on your cursor query. Still, at least it points you to the right code structures...
CREATE OR REPLACE PROCEDURE PROC_LIST_SIMILAR_TVSERIES
(seriesName IN SERIES.NAME%TYPE)
AS
CURSOR seriesCur IS (SELECT IDS FROM SERIES WHERE NAME = seriesName);
allSeries seriesCur%ROWTYPE;
BEGIN
OPEN seriesCur;
FETCH seriesCur INTO allSeries;
IF seriesCur%NOTFOUND
THEN
CLOSE seriesCur;
raise_application_error(-20001,'Your SeriesName does not exist');
END IF;
CLOSE seriesCur;
FOR seriesRec IN
-- this query is a mess! Tried to fix up some aspects of it according to what I THINK you're trying to do.
(SELECT 2*
(SELECT COUNT(*) FROM DICT d WHERE d.idt IN (
SELECT DISTINCT IDT FROM POSTING WHERE IDS = allSeries.IDS
INTERSECT
SELECT DISTINCT IDT FROM POSTING WHERE IDS = allSeries.IDS))
/ ((SELECT DISTINCT COUNT(IDT) FROM POSTING WHERE IDS = allSeries.IDS) +
(SELECT DISTINCT COUNT(IDT) FROM POSTING WHERE IDS = allSeries.IDS) ) similarity
FROM SERIES s1, SERIES s2
WHERE s1.IDS = allSeries.IDS
AND s2.IDS != allSeries.IDS)
LOOP
IF seriesRec.similarity > 0.7 THEN
DBMS_OUTPUT.PUT_LINE('ok');
END IF;
END LOOP;
END;
/
I am still trying to understand the logic in the SQL statement. But i
have hopefully tried to remove the syntactical error. Hope it helps.
CREATE OR REPLACE PROCEDURE PROC_LIST_SIMILAR_TVSERIES(
seriesName IN SERIES.NAME%TYPE)
AS
similarity PLS_INTEGER;
BEGIN
FOR i IN
(SELECT IDS FROM SERIES WHERE NAME = seriesName
)
LOOP
--The logic i am still not able to understand
SELECT *,
(SELECT COUNT(*)
FROM DICT d
WHERE d.idt IN
( SELECT DISTINCT IDT FROM POSTING WHERE IDS = I.IDS
INTERSECT
SELECT DISTINCT IDT
FROM POSTING
WHERE IDS = allSeries.IDS
) / (
(SELECT DISTINCT COUNT(IDT) FROM POSTING WHERE IDS = i.IDS
) +
(SELECT DISTINCT COUNT(IDT) FROM POSTING WHERE IDS = I.IDS
) )
)
INTO similarity
FROM SERIES s1,
SERIES s2
WHERE s1.IDS = s2.IDS
AND s2.IDS != I.IDS;
IF similarity > 0.7 THEN
DBMS_OUTPUT.PUT_LINE('ok');
END IF;
END LOOP;
END;
/

SQL: I want a row to be return with NULL even if there is no match to my IN clause

I would like my SQL query to return a row even if there is no row matching in my IN clause.
For exemple this query:
SELECT id, foo
FROM table
WHERE id IN (0, 1, 2, 3)
would return:
id|foo
0|bar
1|bar
2|bar
3|null
But instead I have (because no row with id 3):
id|foo
0|bar
1|bar
2|bar
I have been able to find this trick:
SELECT tmpTable.id, table.bar
FROM (
SELECT 0 as id
UNION SELECT 1
UNION SELECT 2
UNION SELECT 3
) tmpTable
LEFT JOIN
(
SELECT table.foo, table.id
FROM table
WHERE table.id IN (0, 1, 2, 3)
) table
on table.id = tmpTable.id
Is there a better way?
Bonus: How to make it work with myBatis's list variable?
overslacked is right. Most SQL developers use an auxiliary table that stores integers (and one that stores dates). This is outlined in an entire chapter of Joe Celko's "SQL for Smarties".
Example:
CREATE TABLE numeri ( numero INTEGER PRIMARY KEY )
DECLARE #x INTEGER
SET #x = 0
WHILE #x < 1000
BEGIN
INSERT INTO numeri ( numero ) VALUES ( #x )
SET #x = #x + 1
END
SELECT
numero AS id,
foo
FROM
numeri
LEFT OUTER JOIN my_table
ON my_table.id = numero
WHERE
numero BETWEEN 0 AND 3
Main Goal of Programming minimal code high performance no need this things just remove id 3 from in clause
What about just saying:
SELECT id, foo
FROM table
WHERE id >= 0 AND <= 3

Delete older from a duplicate select

I have been working on a query to search and delete duplicate column values. Currently I have this query (returns duplicates):
SELECT NUIP, FECHA_REGISTRO
FROM registros_civiles_nacimiento
WHERE NUIP IN (
SELECT NUIP
FROM registros_civiles_nacimiento
GROUP BY NUIP
HAVING (COUNT(NUIP) > 1)
) order by NUIP
This work returning a table like this:
NUIP FECHA_REGISTRO
38120100138 1975-05-30
38120100138 1977-08-31
40051800275 1980-09-24
40051800275 1999-11-29
42110700118 1972-10-26
42110700118 1982-04-22
44030700535 1982-10-19
44030700535 1993-05-05
46072300777 1991-01-17
46072300777 1979-03-30
The thing is that I need to delete the rows with duplicate column values. But I need to delete the row with the oldest date, for example, for the given result, once the needed query is performed, this is the list of result that must be kept:
NUIP FECHA_REGISTRO
38120100138 1977-08-31
40051800275 1999-11-29
42110700118 1982-04-22
44030700535 1993-05-05
46072300777 1991-01-17
How can I do this using plain SQL?
--PULL YOUR SELECT OF RECS WITH DUPES INTO A TEMP TABLE
--(OR CREATE A NEW TABLE SO THAT YOU CAN KEEP THEM AROUND FOR LATER IN CASE)
SELECT NUIP,FECHA_REGISTRO
INTO #NUIP
FROM SO_NUIP
WHERE NUIP IN (
SELECT NUIP
FROM SO_NUIP
GROUP BY NUIP
HAVING (COUNT(NUIP) > 1)
)
--CREATE FLAG FOR DETERMINIG DUPES
ALTER TABLE #NUIP ADD DUPLICATETOREMOVE bit
--USE `RANK()` TO SET FLAG
UPDATE #NUIP
SET DUPLICATETOREMOVE = CASE X.RANK
WHEN 1 THEN 1
ELSE 0
END
--SELECT *
FROM #NUIP A
INNER JOIN (SELECT NUIP,FECHA_REGISTRO,RANK() OVER (PARTITION BY [NUIP] ORDER BY FECHA_REGISTRO ASC) AS RANK
FROM #NUIP) X ON X.NUIP = A.NUIP AND X.FECHA_REGISTRO = A.FECHA_REGISTRO
--HERE IS YOUR DELETE LIST
SELECT *
FROM so_registros_civiles_nacimiento R
JOIN #NUIP N ON N.NUIP = R.NUIP AND N.FECHA_REGISTRO = R.FECHA_REGISTRO
WHERE N.DUPLICATETOREMOVE = 1
--HERE IS YOUR KEEP LIST
SELECT *
FROM so_registros_civiles_nacimiento R
JOIN #NUIP N ON N.NUIP = R.NUIP AND N.FECHA_REGISTRO = R.FECHA_REGISTRO
WHERE N.DUPLICATETOREMOVE = 0
--ZAP THEM AND COMMIT YOUR TRANSACTION, YOU'VE STILL GOT A REC OF THE DELETEDS FOR AS LONG AS THE SCOPE OF YOUR #NUIP
BEGIN TRAN --COMMIT --ROLLBACK
DELETE FROM so_registros_civiles_nacimiento
JOIN #NUIP N ON N.NUIP = R.NUIP AND N.FECHA_REGISTRO = R.FECHA_REGISTRO
WHERE N.DUPLICATETOREMOVE = 1
You can use analytical functions for this:
;WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY NUIP ORDER BY FECHA_REGISTRO DESC) RN
FROM registros_civiles_nacimiento
)
DELETE FROM CTE
WHERE RN > 1;
Use RANK() to create the result set ordered by date
Use WHERE EXISTS to delete from the source.
(Note: if you run the rank function over your duplicates, you should get your results. I've just referred to the whole table below)
This statement works in Oracle (replace the select * with delete if it works for you:
SELECT *
FROM registros_civiles_nacimiento ALL_
WHERE EXISTS
(SELECT * FROM
(SELECT * FROM
(SELECT NUIP,
FECHA_REGISTRO,
RANK() OVER (PARTITION BY NUIP ORDER BY FECHA_REGISTRO) AS ORDER_
FROM registros_civiles_nacimiento)
WHERE ORDER_ = 1) OLDEST
WHERE ALL_.NUIP = OLDEST.NUIP
AND ALL_.FECHA_REGISTRO = OLDEST.FECHA_REGISTRO);