Select multiple rows as array - sql

I have a table where two people may have the same name, but separate IDs. I am given the name, and need to request their IDs.
When I use a command like:
SELECT id_num INTO cust_id FROM Customers WHERE name=CName;
If I use this command on the command line (psql), it returns 2 results (for example).
But when I use it as part of an SQL script (PL/pgSQL), it always just grabs the first instance.
I tried selecting into cust_id[], but that produced an error. So what is the proper way to select ALL results, and pump them into an array or another easy way to use them?

In declare
DECLARE id_nums bigint[];
in select
id_nums := ARRAY(select cust_id from Customers WHERE name = CName);
If you prefer loop use
DECLARE id_num bigint;
FOR id_num in select cust_id from Customers WHERE name = CName LOOP
your code here
END LOOP;
Read plpgsql control structures in postgresql docs 9.1.

To put data from individual rows into an array, use an array constructor:
DECLARE id_nums int[]; -- assuming cust_id is of type int
id_nums := ARRAY (SELECT cust_id FROM customers WHERE name = cname);
Or the aggregate function array_agg()
id_nums := (SELECT array_agg(cust_id) FROM customers WHERE name = cname);
Or use SELECT INTO for the assignment::
SELECT INTO id_nums
ARRAY (SELECT cust_id FROM customers WHERE name = cname);

Related

Returned my Cursor in my oracle PL/SLQ function but not all rows are being returned. Can you only return 1 row in a Oracle pl/sql function?

Here is my code I have 2 rows that share the same name, reservation date, and hotel Id. I don't understand why when I execute this function it gives me the error "exact fetch returns more than requested number of rows" instead of returning my both rows in my Reservation Table.
I have returned the cursor correctly I assume, so it should work?
CREATE OR REPLACE FUNCTION findres(cname IN reservation.cust_name%type,
hotelID IN reservation.hotel_id%type,
resdate IN reservation.reserve_date%type)
RETURN reservation.reserve_id%type is
resid reservation.reserve_id%type;
BEGIN
SELECT reserve_id
INTO resid
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate;
RETURN resid;
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('No reservation found');
END;
/
From the documentation for definition of into_clause : the SELECT INTO statement retrieves one or more columns from a single row and stores them in either one or more scalar variables or one record variable
Then the current SELECT statement should be replaced against the cases of returning more than one row. The following queries might be alternatives for your current SQL Select statement
SELECT reserve_id
INTO resid
FROM
( SELECT r.*,
ROW_NUMBER() OVER (ORDER BY 0) AS rn
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate
)
WHERE rn = 1;
If DB version is 12+, then use
SELECT reserve_id
INTO resid
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate
FETCH NEXT 1 ROW ONLY;
without a subquery in order to return one row only, considering you only get duplicates for those columns with no ordering rules for the data. Through use of these queries, no need to handle no_data_found or too_many_rows exceptions.
Update : If your aim is to return all the rows even there are more than one row at once, then you can use SYS_REFCURSOR such as
CREATE OR REPLACE FUNCTION findres(cname reservation.cust_name%type,
hotelID reservation.hotel_id%type,
resdate reservation.reserve_date%type)
RETURN SYS_REFCURSOR IS
recordset SYS_REFCURSOR;
BEGIN
OPEN recordset FOR
SELECT reserve_id
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate;
RETURN recordset;
END;
/
and call in such a way that
VAR v_rc REFCURSOR
EXEC :v_rc := findres('Avoras',111,date'2020-12-06');
PRINT v_rc
from the SQL Developer's console.

Can we use LIKE operator along with MEMBER OF operator in a stored procedure?

I have an array of data using which I select rows from a table. For that I use member of operator in where clause. I want to know if we can do that same but by using Like operator along with member of operator.
When my Array consists of{Delhi, Mumbai, Kolkata}
I select the rows which have these three values in their row.
This is how I do that:
select ...
Into...
From xyz where city member of array;
///Receiving the array from an in parameter of the stored procedure.
And it works perfectly fine.
But If my array has {Del, Mum, Kolk} //parts of the actual names
How do I use this array for the same purpose, maybe using Like operator.
Create or replace zz2(ar in array_collection, c out sys_refcursor)
Is
anotherabc tablename.city%type
Begin
Open c
For
Select ABC
Into anotherabc
From tablename where city member of ar;
End zz2;
I expect the output to have all the rows which have cities starting with the alphabet/characters present in the array. Using member of operator
Something like this?
Select ABC
Into anotherabc a
From tablename WHERE EXISTS
( select 1 FROM ( select column_value as city
FROM TABLE(ar) ) s where a.city like s.city||'%' )
There is no direct way to use LIKE with MEMBER OF.
If It is the protocol that your collection contains the first three characters of the city name then you can use substr() to match only the first three characters in MEMBER OF.
try the following thing:
DECLARE
TYPE t_tab IS TABLE OF varchar(3);
l_tab1 t_tab := t_tab('Del','Mom','Kol');
BEGIN
DBMS_OUTPUT.put('Is ''Delhi'' MEMBER OF l_tab1? ');
IF SUBSTR('Delhi',1,3) MEMBER OF l_tab1 THEN -- note the use of SUBSTR here
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
db<>fiddle demo
Cheers!!

Postgresql Nested Foreach Int Array

What I was trying to do is, declare two arrays.
One is for invoice array contains invoice id and that invoice's debt money,(for example [6238,236.0000])
The other one is for client's previous recieved collections.(for example client had 3 collections [62.000,40.000,10.000])
My tables are like these.
Invoice Payments (Contains all payments for specific invoice and client)
Total Numeric(18,4)
InvoiceId integer
Description character varying
ClientId integer
...
some other values too which is not important here.
Invoice
InvoiceId integer
grandtotal numeric(18,4)
ClientId integer
InvoiceDate Date
...
some other values too which is not important here.
Client Movements
ClientId integer
CreatedDate Date
Will numeric(18,4)
...
some other values too which is not important here.
I tried to create a function with nested Foreach statements at PostgreSQL. Actually, this is going to be a trigger after insert, update, delete functions.
For testing purposes, I write as a function for now. So I get my _clientid and _clientmoveid manually.
CREATE FUNCTION invoice_pay(_clientid int,_clientmoveid int) RETURNS int[] AS $$
DECLARE _invoices varchar[] := (select array_agg(DISTINCT '[' || inv.invoiceid || ',' || CAST (inv.grandtotal AS int) || ']') from invoices inv
left join invoicepayments as ip on inv.invoiceid = ip.invoiceid where inv.clientid = _clientid group by inv.invoiceid,inv.grandtotal order by inv.invoicedate);
DECLARE _collections varchar[] := (SELECT array_agg(DISTINCT '[' || clm.will || ']') from clientmovements clm where clm.clientid = _clientid
group by clm.createddate order by clm.createddate desc);
DECLARE _total numeric(18,4);
DECLARE _collectionTotal int;
DECLARE _invoicesArray int[];
BEGIN
/*first delete all the previous payments*/
DELETE FROM InvoicePayments where clientid = _clientid;
/*loop through all invoices and those remaining payments */
/*first array contains invoiceid and invoicegrandtotal [6232,246.0000]*/
FOREACH _invoicesArray SLICE 1 IN ARRAY _invoices
LOOP
/*loop throught all collections that client had. The second array contains only collections from client. [[15],[20],[40]]*/
FOREACH _collectionTotal IN ARRAY _collections
LOOP
/*the total calculation is the if collection is bigger than the invoice grandtotal, close invoice as paid. Other than grandtotal minus collection*/
_total := (case when _collectionsTotal > _invoicesArray[2] then _collectionsTotal else _invoicesArray[2] - collectionsTotal end);
insert into invoicepayments(total,description,clientid,invoiceid)
values(_total,'test',_clientid,_invoicesArray[1]);
END LOOP;
END LOOP;
RETURN _invoices;
END;
$$ LANGUAGE plpgsql;
After that function, i get an error like
invalid input syntax for integer: "{6328,236.0000}"
Probably at some point, my array's first or second value does not work like I think. I use them like _invoicesArray[1] that. I want to take 6328 from _invoicesArray[1] value but it gives me an error. What should I do for nested foreach and what do I do wrong?

Trigger - Write SELECT Result directly into :new.field

I am actually training in oracle SQL basics and I have created a trigger that looks like this one (it works!):
create or replace TRIGGER insert_refereenation
BEFORE INSERT OR UPDATE ON Game
FOR EACH ROW
DECLARE
v_nation_id NUMBER(4) := '';
v_nationname VARCHAR2(100) := '';
BEGIN
SELECT Nation_id
INTO v_nation_id
FROM referre
WHERE referee_id= :new.referee_id;
SELECT Name
INTO v_nationname
FROM Nation
WHERE Nation_id = v_nation_id;
:NEW.referee_nation_name:= v_nationname;
END;
This trigger works fine but I have two simple questions:
Is it possible to write the result from the second SELECT right into the: NEW.referee_nation_name field? So I wouldn´t need the second variable.
Do you see other things which can get optimized?
You could use a join so that you have only one query instead of two, and use the :new.column_name directly in the into clause:
SELECT n.name
INTO :new.referee_nation_name
FROM referee r
JOIN nation n ON r.nation_id = n.nation_id
WHERE r.referee_id = :new.referee_id;

PL/SQL loop through cursor

My problem isn't overly complicated, but I am a newbie to PL/SQL.
I need to make a selection from a COMPANIES table based on certain conditions. I then need to loop through these and convert some of the fields into a different format (I have created functions for this), and finally use this converted version to join to a reference table to get the score variable I need. So basically:
select id, total_empts, bank from COMPANIES where turnover > 100000
loop through this selection
insert into MY_TABLE (select score from REF where conversion_func(MY_CURSOR.total_emps) = REF.total_emps)
This is basically what I am looking to do. It's slightly more complicated but I'm just looking for the basics and how to approach it to get me started!
Here's the basic syntax for cursor loops in PL/SQL:
BEGIN
FOR r_company IN (
SELECT
ID,
total_emps,
bank
FROM
companies
WHERE
turnover > 100000
) LOOP
INSERT INTO
my_table
SELECT
score
FROM
ref_table
WHERE
ref.total_emps = conversion_func( r_company.total_emps )
;
END LOOP;
END;
/
You don't need to use PL/SQL to do this:
insert into my_table
select score
from ref r
join companies c
on r.total_emps on conversion_func(c.total_emps)
where c.turnover > 100000
If you have to do this in a PL/SQL loop as asked, then I'd ensure that you do as little work as possible. I would, however, recommend bulk collect instead of the loop.
begin
for xx in ( select conversion_func(total_emps) as tot_emp
from companies
where turnover > 100000 ) loop
insert into my_table
select score
from ref
where total_emps = xx.tot_emp
;
end loop;
end;
/
For either method you need one index on ref.total_emps and preferably one on companies.turnover