How can i make this INSERT in PL/SQL Procedure? - oracle

I am new in pl/sql programming and I need your help.
I would like to make a procedure.
To be more specific, I have tables like the folowing TABLE1
================================================
|COL1 | COL2 | COL3 | COL4 | COL5 | COL6 |COL7|
===============================================
|600 | 140 | 2 | 10 | 1300 | 500 | 1 |
|600 | 140 | 2 | 20 | 1400 | 340 | 4 |
|600 | 140 | 2 | 15 | 1400 | 230 | 3 |
|600 | 140 | 2 | 35 | 1700 | 120 | 2 |
|600 | 150 | 3 | 10 | 1300 | 166 | 6 |
|600 | 150 | 3 | 15 | 1400 | 435 | 5 |
----------------------------------------------
For the same COL1 and COL2/COL3 , check the select different values from COL4
For instance for COL1=600 , COL2=140/COL3=2 and COL2=150/COL3=3
Return 20 and 35
And insert in this table TABLE1 the rows
600 , 150 , 3, 20 , 1400 , 340, 7 (seq number)
600 , 150 , 3, 35 , 1700 , 120, 8 (seq number)
I make inserts in Table1 if P_FLG1 = 'Y'. IF P_FLG2 = 'Y' I make inserts also in TABLE3 etc
I am trying to make the procedure like below but I can't finish it
PROCEDURE COPY_COLUMNS ( P_COL1 IN A.COL1%TYPE,
P_FROM_COL2 IN B.COL2%TYPE,
P_FROM_COL3 IN B.COL3%TYPE,
P_TO_COL2 IN B.COL2%TYPE,
P_TO_COL3 IN B.COL3%TYPE,
P_FLG1 IN VARCHAR2,
P_FLG2 IN VARCHAR2,
P_FLG3 IN VARCHAR2
) IS
CURSOR CFL1 IS select COL4
FROM TABLE1
WHERE COL1 = P_COL1 AND COL2 = P_FROM_COL2 AND COL3 = P_FROM_COL3
MINUS
select COL4
FROM TABLE1
WHERE COL1 = P_COL1 AND COL2 = P_TO_COL2 AND COL3 = P_TO_COL3;
CURSOR CFL2 IS select COL4
FROM TABLE2
WHERE COL1 = P_COL1 AND COL2 = P_FROM_COL2 AND COL3 = P_FROM_COL3
MINUS
select COL4
FROM TABLE2
WHERE COL1 = P_COL1 AND COL2 = P_TO_COL2 AND COL3 = P_TO_COL3;
CURSOR CFL3 IS select COL4
FROM TABLE3
WHERE COL1 = P_COL1 AND COL2 = P_FROM_COL2 AND COL3 = P_FROM_COL3
MINUS
select COL4
FROM TABLE3
WHERE COL1 = P_COL1 AND COL2 = P_TO_COL2 AND COL3 = P_TO_COL3;
V_REC CFL1%ROWTYPE;
BEGIN
IF P_FLG1='N' OR P_FLG2='N' OR P_FLG3='N' OR P_FLG4 ='N' OR P_FLG5 = 'N' THEN
GOTO label; --do nothing
END IF;
IF P_FLG1 = 'Y' THEN
OPEN CFL1;
FETCH CFL1 INTO V_REC;
CLOSE C1;
SELECT COL5, COL6
FROM TABLE1
WHERE COL1 = P_COL1 AND COL2 = P_FROM_COL2 AND COL3 = P_FROM_COL3 AND COL4 = V_REC.COL4;
FOR REC IN CFL1 LOOP
INSERT INTO TABLE1
SELECT P_COL1, P_TO_COL2, P_TO_COL3, CFL1.COL4, -- COL5 , COL6 ?? -- , SEQ.NEXTVAL)
END LOOP;
END IF;
-- ..........
<<label>>
END;
I would appreciate it if you could help me.
Thanks a lot

CFL1.COL4 won't work, since you need to reference the resultset variable : REC.COL4.
And it doesn't hurt to specify the columns of the table you insert into.
for example :
INSERT INTO TABLE1 (col1, col2) values (rec.col1, rec.col2);
or
INSERT INTO TABLE1 (col1, col2) select rec.col1, rec.col2 from dual;
Also, you first fetch the CFL1, expecting only 1 record value.
But then use the same cursor in loop. Remove the fetch and get that col4 value from REC? A direct select in a procedure won't work anyway.

Related

Oracle query by column1 where column2 is the same

I have a table like this in Oracle 9i DB:
+------+------+
| Col1 | Col2 |
+------+------+
| 1 | a |
| 2 | a |
| 3 | a |
| 4 | b |
| 5 | b |
+------+------+
Col1 is the primary key, Col2 is indexed.
I input col1 as condition for my query and I want to get col1 where col2 is the same as my input.
For example I query for 1 and the result should be 1,2,3.
I know I can use self join for this, I would like to know if there is a better way to do this.
I'd call this a semi-join: does it satisfy your 'no self joins' requirement?:
SELECT *
FROM YourTable
WHERE Col2 IN ( SELECT t2.Col2
FROM YourTable t2
WHERE t2.Col1 = 1 );
I'd be inclined to avoid the t2 range variable like this:
WITH YourTableSearched
AS ( SELECT Col2
FROM YourTable
WHERE Col1 = 1 )
SELECT *
FROM YourTable
WHERE Col2 IN ( SELECT Col2
FROM YourTableSearched );
but TNH I would probably do this:
WITH YourTableSearched
AS ( SELECT Col2
FROM YourTable
WHERE Col1 = 1 )
SELECT *
FROM YourTable
NATURAL JOIN YourTableSearched;
It's possible. Whether it's better (i.e. more performant) than using a self-join, particularly if there is an index on col1, col2, is anyone's guess.
Assuming col1 is unique, you could do:
SELECT col1
FROM (SELECT col1,
col2,
MAX(CASE WHEN col1 = :p_col1_value THEN col2 END) OVER () col2_comparison
FROM your_table)
WHERE col2 = col2_comparison;
And with :p_col1_value = 1:
COL1
----------
1
2
3
And with :p_col1_value = 5:
COL1
----------
4
5

How to split two columns data in to two rows apart from using union? [duplicate]

I have 50 column in a table and it returns only one row and I want that one row with 50 column to be displayed in 50 rows and one column.
Can any one suggest me the Oracle query for it?
You can use UNPIVOT for one row like this to get only column with values
SELECT colvalue
FROM
(
SELECT *
FROM Table1
UNPIVOT INCLUDE NULLS
(
colvalue FOR cols IN (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, ... col50)
)
);
Sample output:
| COLVALUE |
------------
| 1 |
| 2 |
| (null) |
|..........|
If you need column with column names from your pivoted table just ditch the outer select
SELECT *
FROM Table1
UNPIVOT INCLUDE NULLS
(
colvalue FOR cols IN (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, ... col50)
);
Sample output:
| COLS | COLVALUE |
--------------------
| COL1 | 1 |
| COL2 | 2 |
| COL3 | (null) |
| ..... |......... |
Here is SQLFiddle demo
Be prepared for a lot of typing :) Oracle has UNPIVOT functionality but it wants at least two columns in the result, so it won't work for your situation.
First off, you'll need a counter from 1 to 50. You can query one like this:
SELECT LEVEL as Counter FROM DUAL CONNECT BY LEVEL <= 50
If you execute this query you'll get the numbers 1-50 as your result. With that as a basis, here's the full(ish) query:
WITH Cols AS (
SELECT LEVEL as Counter
FROM DUAL
CONNECT BY LEVEL <= 50
)
SELECT
CASE Cols.Counter
WHEN 1 THEN Col1
WHEN 2 THEN Col2
WHEN 3 THEN Col3
. . .
WHEN 50 THEN Col50
END AS myColumn
FROM myTable
CROSS JOIN Cols
ORDER BY Cols.Counter
Note that all of the columns must be the same data type, so if you have a mixture of character, number and date you'll need to convert them all to character.
Note that this query assumes one row in the table, as mentioned in the question. If there's more than one row you should end with ORDER BY a-column-that-identifies-the-row, Cols.Counter.

how to use Posexplode function in hive

I am using posexplode to split single to multiple records in hive.
Along with multiple records as output i need to generate sequence number for each row.
col1, col2, col3 and col4 are defined as string because rarely we get alpha data as well.
col1 | col2| col3 | col4
---------------------------
7 | 9 | A | 3
5 | 6 | 9
Seq | Col
----------
1 | 7
2 | 9
3 | A
4 | 3
1 | 5
2 | 6
3 | 9
I am using below mentioned query but I am getting error
-bash: syntax error near unexpected token (
My query is :
SELECT
seq, col
FROM
(SELECT array( col1, col2 , col3,col4) as arr_r FROM srctable ) arrayrec
LATERAL VIEW posexplode(arrayrec) EXPLODED_rec as seq, col
How can this be resolved
I am able to run successfully this query :
SELECT col FROM
(SELECT array( col1, col2 , col3,col4)
as arr_r FROM srctable ) arrayrec
LATERAL VIEW explode(arrayrec) EXPLODED_rec as col
Which produces below output
Col
-----
7
9
A
3
5
6
9
I have checked the link : How to get first n elements in an array in Hive
Try
SELECT Seq, col FROM
(SELECT array( col1, col2 , col3,col4)
as arr_r FROM srctable ) arrayrec
LATERAL VIEW posexplode(arrayrec.arr_r) EXPLODED_rec as Seq, col;
Also check your hive version. posexplode() is available as of Hive 0.13.0.

Oracle: Get column names of Top n values over multiple columns

My question is similar to
https://community.oracle.com/message/4418327
in my query i need the MAX value of 3 different columns.
example: column 1 = 10, column 2 = 20, column 3 = 30 > output should
be 30. i need this MAX value to sort the list by it.
However instead of the actually value I need the column name and ideally not just the max one but top 3 as example.
The desired output would then be
ID first second third
-------------------------------
1 column 3 column 2 column1
There is no built-in function for what you are asking. So user1578653's answer is good, straight-forward and fast. Another way would be to write a function with PL/SQL.
In case you want to use pure SQL, but want it easier to add columns to the comparision, then if you are satisfied with just two columns (id and column names string), you can do this:
select id, listagg(colname, ', ') within group (order by value desc) as columns
from
(
select id, 'column1' as colname, col1 as value from mytable
union all
select id, 'column2' as colname, col2 as value from mytable
union all
select id, 'column3' as colname, col3 as value from mytable
-- add more columns here, if you wish
)
group by id;
Be aware this is slower than user1578653's SQL statement, as the table is being read three times. (I also treat equal values differently here, same values lead to random order.)
You can use PIVOT/UNPIVOT for this.
Fisrt UNPIVOT your table and assign rank with values ordered in descending order.
create table sample(
id number,
col1 number,
col2 number,
col3 number
);
insert into sample values(1,10,20,30);
insert into sample values(2,10,20,15);
select id, col_name, val,
row_number() over (partition by id order by val desc) r
from sample
unpivot(val for col_name in (
col1 AS 'col1', col2 AS 'col2', col3 AS 'col3')
);
Output:
| ID | COL_NAME | VAL | R |
|----|----------|-----|---|
| 1 | col3 | 30 | 1 |
| 1 | col2 | 20 | 2 |
| 1 | col1 | 10 | 3 |
| 2 | col2 | 20 | 1 |
| 2 | col3 | 15 | 2 |
| 2 | col1 | 10 | 3 |
sqlfiddle.
Next PIVOT the col_name based on the rank column.
with x(id, col_name, val,r) as (
select id, col_name, val,
row_number() over (partition by id order by val desc)
from sample
unpivot(val for col_name in (
col1 AS 'col1', col2 AS 'col2', col3 AS 'col3')
)
)
select * from (
select id, col_name, r
from x
)
pivot(max(col_name) for r in (
1 as first, 2 as second, 3 as third)
);
Output:
| ID | FIRST | SECOND | THIRD |
|----|-------|--------|-------|
| 1 | col3 | col2 | col1 |
| 2 | col2 | col3 | col1 |
sqlfiddle.
Not sure if there is a better way of doing this, but here's a possible solution using the CASE statement. Here's the table structure I've tested it on:
CREATE TABLE MAX_COL(
"ID" INT,
COLUMN_1 INT,
COLUMN_2 INT,
COLUMN_3 INT
);
And here's the SQL I used:
SELECT
"ID",
CASE
WHEN COLUMN_1 > COLUMN_2 AND COLUMN_1 > COLUMN_3 THEN 'COLUMN_1'
WHEN COLUMN_2 > COLUMN_1 AND COLUMN_2 > COLUMN_3 THEN 'COLUMN_2'
WHEN COLUMN_3 > COLUMN_2 AND COLUMN_3 > COLUMN_1 THEN 'COLUMN_3'
ELSE 'NONE'
END AS "FIRST",
CASE
WHEN COLUMN_1 > COLUMN_2 AND COLUMN_1 < COLUMN_3 THEN 'COLUMN_1'
WHEN COLUMN_2 > COLUMN_1 AND COLUMN_2 < COLUMN_3 THEN 'COLUMN_2'
WHEN COLUMN_3 > COLUMN_2 AND COLUMN_3 < COLUMN_1 THEN 'COLUMN_3'
ELSE 'NONE'
END AS "SECOND",
CASE
WHEN COLUMN_1 < COLUMN_2 AND COLUMN_1 < COLUMN_3 THEN 'COLUMN_1'
WHEN COLUMN_2 < COLUMN_1 AND COLUMN_2 < COLUMN_3 THEN 'COLUMN_2'
WHEN COLUMN_3 < COLUMN_2 AND COLUMN_3 < COLUMN_1 THEN 'COLUMN_3'
ELSE 'NONE'
END AS "THIRD"
FROM
MAX_COL;
Please note that you will need to deal with situations where one or more columns have the same value. In this case I am just returning 'NONE' if this occurs, but you may want to do something else.
UPDATE
You could also use PLSQL functions to achieve this, which may be easier to use for many columns. Here's an example:
CREATE OR REPLACE
FUNCTION COLUMN_POSITION (IDVAL INT, POSITION INT) RETURN VARCHAR2 AS
TYPE COLUMN_VALUE_TYPE IS TABLE OF NUMBER INDEX BY VARCHAR2(64);
COLS COLUMN_VALUE_TYPE;
COL_KEY VARCHAR2(64);
QUERY_STR VARCHAR2(1000 CHAR) := 'SELECT COL FROM(SELECT T.*, DENSE_RANK() OVER (ORDER BY VAL DESC) AS RANK FROM (';
COL_NAME VARCHAR2(64);
BEGIN
COLS('COLUMN_1') := NULL;
COLS('COLUMN_2') := NULL;
COLS('COLUMN_3') := NULL;
--ADD MORE COLUMN NAMES HERE...
COL_KEY := COLS.FIRST;
LOOP
EXIT WHEN COL_KEY IS NULL;
QUERY_STR := QUERY_STR || 'SELECT ' || COL_KEY || ' AS VAL, '''|| COL_KEY ||''' AS COL FROM MAX_COL WHERE ID = '|| IDVAL ||' UNION ALL ';
COL_KEY := COLS.NEXT(COL_KEY);
END LOOP;
QUERY_STR := SUBSTR(QUERY_STR, 0, LENGTH(QUERY_STR) - 11);
QUERY_STR := QUERY_STR || ') T ) WHERE RANK = ' || POSITION;
EXECUTE IMMEDIATE QUERY_STR INTO COL_NAME;
RETURN COL_NAME;
END;
This example has only 3 columns but you could easily add more. You can then use this in a query like this:
SELECT
MAX_COL.*,
COLUMN_POSITION(ID, 1) AS "FIRST",
COLUMN_POSITION(ID, 2) AS "SECOND",
COLUMN_POSITION(ID, 3) AS "THIRD"
FROM MAX_COL
SELECT
(CASE
WHEN A > B AND A > C THEN 'A'
WHEN B > A AND B > C THEN 'B'
WHEN C > B AND C > A THEN 'C'
END) AS "MaxValue_Column_Name",
greatest(A,B,C) AS Max_Value FROM max_search ;
--table name =max_search and column_name are A,B and C

Oracle query to convert multiple column into one column

I have 50 column in a table and it returns only one row and I want that one row with 50 column to be displayed in 50 rows and one column.
Can any one suggest me the Oracle query for it?
You can use UNPIVOT for one row like this to get only column with values
SELECT colvalue
FROM
(
SELECT *
FROM Table1
UNPIVOT INCLUDE NULLS
(
colvalue FOR cols IN (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, ... col50)
)
);
Sample output:
| COLVALUE |
------------
| 1 |
| 2 |
| (null) |
|..........|
If you need column with column names from your pivoted table just ditch the outer select
SELECT *
FROM Table1
UNPIVOT INCLUDE NULLS
(
colvalue FOR cols IN (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, ... col50)
);
Sample output:
| COLS | COLVALUE |
--------------------
| COL1 | 1 |
| COL2 | 2 |
| COL3 | (null) |
| ..... |......... |
Here is SQLFiddle demo
Be prepared for a lot of typing :) Oracle has UNPIVOT functionality but it wants at least two columns in the result, so it won't work for your situation.
First off, you'll need a counter from 1 to 50. You can query one like this:
SELECT LEVEL as Counter FROM DUAL CONNECT BY LEVEL <= 50
If you execute this query you'll get the numbers 1-50 as your result. With that as a basis, here's the full(ish) query:
WITH Cols AS (
SELECT LEVEL as Counter
FROM DUAL
CONNECT BY LEVEL <= 50
)
SELECT
CASE Cols.Counter
WHEN 1 THEN Col1
WHEN 2 THEN Col2
WHEN 3 THEN Col3
. . .
WHEN 50 THEN Col50
END AS myColumn
FROM myTable
CROSS JOIN Cols
ORDER BY Cols.Counter
Note that all of the columns must be the same data type, so if you have a mixture of character, number and date you'll need to convert them all to character.
Note that this query assumes one row in the table, as mentioned in the question. If there's more than one row you should end with ORDER BY a-column-that-identifies-the-row, Cols.Counter.

Resources