incrementing i with condition - sql

I want to increment by i by 1 whenever the value is different for v_id.
i = 1;
for i in 1..10 loop
SELECT subject_details.NEXTVAL
INTO v_id
FROM dual;
INSERT INTO employee (id, subject)
VALUES (i,v_id);
i = i + 1;
end loop;
what this does is
id subject
1 3647
2 3647
3 5678
4 5678
5 5678
but what I want is to increment the value of "i" by 1 whenever there is a change in value for "v_id"
id subject
1 3647
1 3647
2 5678
2 5678
2 5678

I think you can just use dense_rank():
select t.*, dense_rank() over (order by subject)
from t;
If the values can be interleaved, then you can use lag() and a cumulative sum:
select t.*,
sum(case when prev_subject = subject then 0 else 1 end) over (order by id)
from (select t.*,
lag(subject) over (order by id) as prev_subject
from t
) t;

While there are good suggestions offered, I think they mostly miss your biggest mistake. I believe that is a miss understanding of pl/sql scoping rules. Somewhere in tour code (before what you posted) you defined a variable i then created i as the index variable of a FOR loop. The problem you face is that due to scoping rules these are not the same variable. According to documentation: (also see demonstration here)
for index name Name for the implicitly declared integer variable that is
local to the FOR LOOP statement. Statements outside the loop cannot
reference index. Statements inside the loop can reference index, but
cannot change its value. After the FOR LOOP statement runs, index is
undefined.
There are a couple other issues with you process:
In Oracle the assignment operator is := not =; (I assume this is just
a typo).
In each loop iteration you run "subject_details.NEXTVAL", and
subsequently insert that result into your table. Doing so will
increment the value every time. Thus you cannot produce your desired
output - nor your claimed output.

try
j = 1;
v_id_copy = 0;
for i in 1..10 loop
SELECT subject_details.NEXTVAL INTO v_id FROM dual;
if(v_id_copy==0) then
v_id_copy = v_id;
elseif (v_id_copy != v_id) then
j=j+1;
v_copy_id=v_id;
endif
insert into employee (id,subject) values (j,v_id)
end loop;

Related

MS SQL Server: Operate current select values in other selected columns

I want to take a value from a selected column to operate the next column. For example:
SELECT CASE
WHEN ID < 4 THEN ID
ELSE 10
END
AS MY_ID,
MY_ID + 5 AS EXTRA_ID
FROM FOO
That would output for IDs 1,2,3,4,5:
MY_ID EXTRA_ID
1 6
2 7
3 8
10 15
10 15
If I do MY_ID + 5 it will complain about MY_ID not existing (it's an alias, so it makes sense) and ID + 5 will read 1+5, 2+5, 3+5, 4+5, 5+5 instead of 1+5, 2+5, 3+5, 4+10, 5+10 when it goes through the ELSE. Is it even possible to do this? I'm doing it in SSRS - Report builder, and need to operate a result that might be set to a defualt value depending on the CASE clause.
You can repeat the same CASE expression with +5 in the end for the extra_id column
SELECT CASE
WHEN ID < 4 THEN ID
ELSE 10
END
AS MY_ID,
CASE
WHEN ID < 4 THEN ID
ELSE 10
END + 5 AS EXTRA_ID
FROM FOO
An alternative is to create the extra_id column value inside SSRS using an expression
= Fields!my_id.value + 5
you cannot reuse the calculation in the same level. Using my_id in the where clause will fail as well. Either you have to calucate it multiple times, place another select around your statement or use a with statement (CTE).
Simply wrap it with another select:
SELECT t.*,
t.my_id + 5 as extra_id
FROM(Your Query) t
Derived columns are not available in the same layer they're being created. By wrapping them with another select, you make them available (that because the inner query is being processed before the outer) .
You just need a subquery to create MY_ID before doing anything with it. By creating MY_ID in the inner query the outer query can use to define new fields.
SELECT
a.MY_ID,
a.MY_ID + 5 AS EXTRA_ID
from
(SELECT
CASE
WHEN ID < 4 THEN ID
ELSE 10
END
AS MY_ID
FROM FOO) as a

MonetDB: Enumerate groups of rows based on a given "boundary" condition

Consider the following table:
id gap groupID
0 0 1
2 3 1
3 7 2
4 1 2
5 5 2
6 7 3
7 3 3
8 8 4
9 2 4
Where groupID is the desired, computed column, such as its value is incremented whenever the gap column is greater than a threshold (in this case 6). The id column defines the sequential order of appearance of the rows (and it's already given).
Can you please help me figure out how to dynamically fill out the appropriate values for groupID?
I have looked in several other entries here in StackOverflow, and I've seen the usage of sum as an aggregate for a window function. I can't use sum because it's not supported in MonetDB window functions (only rank, dense_rank, and row_num). I can't use triggers (to modify the record insertion before it takes place) either because I need to keep the data mentioned above within a stored function in a local temporary table -- and trigger declarations are not supported in MonetDB function definitions.
I have also tried filling out the groupID column value by reading the previous table (id and gap) into another temporary table (id, gap, groupID), with the hope that this would force a row-by-row operation. But this has failed as well because it gives the groupID 0 to all records:
declare threshold int;
set threshold = 6;
insert into newTable( id, gap, groupID )
select A.id, A.gap,
case when A.gap > threshold then
(select case when max(groupID) is null then 0 else max(groupID)+1 end from newTable)
else
(select case when max(groupID) is null then 0 else max(groupID) end from newTable)
end
from A
order by A.id asc;
Any help, tip, or reference is greatly appreciated. It's been a long time already trying to figure this out.
BTW: Cursors are not supported in MonetDB either --
You can assign the group using a correlated subquery. Simply count the number of previous values that exceed 6:
select id, gap,
(select 1 + count(*)
from t as t2
where t2.id <= t.id and t2.gap > 6
) as Groupid
from t;

multiple loop inside single for statement using cursor

I have 2 cursors inside my procedure for which i want to use single for loop because using this i want to loop 2 variable inside single select query. The two variable has 2 different values which is used inside single select query. These 2 values are coming from KPI_DEFINITION table which gives me timestamp which i want to compare in my select query for data extraction. The first column KPI_FREQUENCY has value for example 30 and KPI_FREQ_TIME_UNIT column has value MINUTE. So if we combine these 2 column we will get 30 MINUTE and there is another combined column value which is 50 MINUTE and there might be more. So thats why i want to put this inside loop and compare in my select query with the start_date field value.But dont know how to do that. I simply use 2 cursors to take this 2 column and trying to loop it inside single for loop but dont know how to do that.There are might be another solution for this if i dont want to use two cursors but did not find a way.
create or replace PROCEDURE "EXT_TEST" AS
LAST_WF_ID Number := 0;
LAST_UNIT NUMBER:=10;
LAST_UNIT_VAL VARCHAR2(20);
CURSOR KPI_FREQUENCY_CUR IS
Select KPI_FREQUENCY INTO LAST_UNIT from RATOR_MONITORING_CONFIGURATION.KPI_DEFINITION WHERE
EVENT_ID = 10028;
CURSOR KPI_FREQ_TIME_UNIT_CUR IS
Select KPI_FREQ_TIME_UNIT INTO LAST_UNIT_VAL 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);
select MIN(ID) INTO LAST_WF_ID from WF_WORKFLOW#FONIC_RETAIL where start_date > sysdate - numtodsinterval(LAST_UNIT,LAST_UNIT_VAL);
DBMS_OUTPUT.PUT_LINE('LAST_WF_ID - ' || LAST_WF_ID);
END EXT_TEST;
Sample data from KPI_DEFINITION table:
KPI_DEF_ID KPI_FREQUENCY KPI_FREQ_TIME_UNIT EVENT_ID
1000136 30 MINUTE 10028
1000137 50 MINUTE 10028
Pending seeing what data your actually on .. I'd suggest trying something a lot simpler ... such as this:
select r.kpi_frequency, r.kpi_freq_time_unit, min(f.id)
from rator_monitoring_configuration.kpi_definition r,
wf_workflow#fonic_retail f
where r.event_id = 10028
and f.start_date > sysdate - numtodsinterval ( r.kpi_frequency, r.kpi_freq_time_unit );
group by r.kpi_frequency, r.kpi_freq_time_unit
order by r.kpi_frequency, r.kpi_freq_time_unit;

Browse subcolumns, but discard some

I have a table (or view) in my PostgreSQL database and want to do the following:
Query the table and feed a function in my application subsequent n-tuples of rows from the query, but only those that satisfy some condition. I can do the n-tuple listing using a cursor, but I don't know how to do the condition checking on database level.
For example, the query returns:
3
2
4
2
0
1
4
6
2
And I want triples of even numbers. Here, they would be:
(2,4,2) (4,2,0) (4,6,2)
Obviously, I cannot discard the odd numbers from the query result. Instead using cursor, a query returning arrays in similar manner would also be acceptable solution, but I don't have any good idea how to use them to do this.
Of course, I could check it at application level, but I think it'd be cleaner to do it on database level. Is it possible?
With the window function lead() (as mentioned by #wildplasser):
SELECT *
FROM (
SELECT tbl_id, i AS i1
, lead(i) OVER (ORDER BY tbl_id) AS i2
, lead(i, 2) OVER (ORDER BY tbl_id) AS i3
FROM tbl
) sub
WHERE i1%2 = 0
AND i2%2 = 0
AND i3%2 = 0;
There is no natural order of rows - assuming you want to order by tbl_id in the example.
% .. modulo operator
SQL Fiddle.
You can also use an array aggregate for this instead of using lag:
SELECT
a[1] a1, a[2] a2, a[3] a3
FROM (
SELECT
array_agg(i) OVER (ORDER BY tbl_id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM
tbl
) x(a)
WHERE a[1] % 2 = 0 AND a[2] % 2 = 0 AND a[3] % 2 = 0;
No idea if this'll be better, worse, or the same as Erwin's answer, just putting it in for completeness.

Ranking in MySQL, how do I get the best performance with frequent updates and a large data set?

I want grouped ranking on a very large table, I've found a couple of solutions for this problem e.g. in this post and other places on the web. I am, however, unable to figure out the worst case complexity of these solutions. The specific problem consists of a table where each row has a number of points and a name associated. I want to be able to request rank intervals such as 1-4. Here are some data examples:
name | points
Ab 14
Ac 14
B 16
C 16
Da 15
De 13
With these values the following "ranking" is created:
Query id | Rank | Name
1 1 B
2 1 C
3 3 Da
4 4 Ab
5 4 Ac
6 6 De
And it should be possible to create the following interval on query-id's: 2-5 giving rank: 1,3,4 and 4.
The database holds about 3 million records so if possible I want to avoid a solution with complexity greater than log(n). There are constantly updates and inserts on the database so these actions should preferably be performed in log(n) complexity as well. I am not sure it's possible though and I've tried wrapping my head around it for some time. I've come to the conclusion that a binary search should be possible but I haven't been able to create a query that does this. I am using a MySQL server.
I will elaborate on how the pseudo code for the filtering could work. Firstly, an index on (points, name) is needed. As input you give a fromrank and a tillrank. The total number of records in the database is n. The pseudocode should look something like this:
Find median point value, count rows less than this value (the count gives a rough estimate of rank, not considering those with same amount of points). If the number returned is greater than the fromrank delimiter, we subdivide the first half and find median of it. We keep doing this until we are pinpointed to the amount of points where fromrank should start. then we do the same within that amount of points with the name index, and find median until we have reached the correct row. We do the exact same thing for tillrank.
The result should be log(n) number of subdivisions. So given the median and count can be made in log(n) time it should be possible to solve the problem in worst case complexity log(n). Correct me if I am wrong.
You need a stored procedure to be able to call this with parameters:
CREATE TABLE rank (name VARCHAR(20) NOT NULL, points INTEGER NOT NULL);
CREATE INDEX ix_rank_points ON rank(points, name);
CREATE PROCEDURE prc_ranks(fromrank INT, tillrank INT)
BEGIN
SET #fromrank = fromrank;
SET #tillrank = tillrank;
PREPARE STMT FROM
'
SELECT rn, rank, name, points
FROM (
SELECT CASE WHEN #cp = points THEN #rank ELSE #rank := #rn + 1 END AS rank,
#rn := #rn + 1 AS rn,
#cp := points,
r.*
FROM (
SELECT #cp := -1, #rn := 0, #rank = 1
) var,
(
SELECT *
FROM rank
FORCE INDEX (ix_rank_points)
ORDER BY
points DESC, name DESC
LIMIT ?
) r
) o
WHERE rn >= ?
';
EXECUTE STMT USING #tillrank, #fromrank;
END;
CALL prc_ranks (2, 5);
If you create the index and force MySQL to use it (as in my query), then the complexity of the query will not depend on the number of rows at all, it will depend only on tillrank.
It will actually take last tillrank values from the index, perform some simple calculations on them and filter out first fromrank values.
Time of this operation, as you can see, depends only on tillrank, it does not depend on how many records are there.
I just checked in on 400,000 rows, it selects ranks from 5 to 100 in 0,004 seconds (that is, instantly)
Important: this only works if you sort on names in DESCENDING order. MySQL does not support DESC clause in the indices, that means that the points and name must be sorted in one order for INDEX SORT to be usable (either both ASCENDING or both DESCENDING). If you want fast ASC sorting by name, you will need to keep negative points in the database, and change the sign in the SELECT clause.
You may also remove name from the index at all, and perform a final ORDER'ing without using an index:
CREATE INDEX ix_rank_points ON rank(points);
CREATE PROCEDURE prc_ranks(fromrank INT, tillrank INT)
BEGIN
SET #fromrank = fromrank;
SET #tillrank = tillrank;
PREPARE STMT FROM
'
SELECT rn, rank, name, points
FROM (
SELECT CASE WHEN #cp = points THEN #rank ELSE #rank := #rn + 1 END AS rank,
#rn := #rn + 1 AS rn,
#cp := points,
r.*
FROM (
SELECT #cp := -1, #rn := 0, #rank = 1
) var,
(
SELECT *
FROM rank
FORCE INDEX (ix_rank_points)
ORDER BY
points DESC
LIMIT ?
) r
) o
WHERE rn >= ?
ORDER BY rank, name
';
EXECUTE STMT USING #tillrank, #fromrank;
END;
That will impact performance on big ranges, but you will hardly notice it on small ranges.