I need to load some queries into vectors to avoid temporary tables in a function:
create table mytable
( eref serial primarykey,
edia date,
eimpte numeric);
---
CREATE OR REPLACE FUNCTION peps(rseller integer)
RETURNS void AS
$BODY$
declare
dep_dia date[] := '{}';
dep_impte numeric[]:= '{}';
dep_ref integer[]:= '{}';
ndepositos integer :=0;
rec record;
begin
for rec in
select eref, edia, eimpte from mytable order by edia, eimpte
loop
ndepositos:=ndepositos+1;
dep_dia[ndepositos] :=edia;
dep_impte[ndepositos]:=eimpte;
dep_ref[ndepositos] :=eref;
end loop;
raise notice ' ndeps %', ndepositos;
end
$BODY$
language plpgsql volatile;
it does not work:
ERROR: column "edia" does not exist
LINE 1: SELECT edia
^
what am I doing wrong?
Thanks in advance
Don't loop! Postgres provides a great function for this:
SELECT array_agg(eref), array_agg(edia), array_agg(eimpte)
FROM (SELECT * from mytable order by edia, eimpte) AS foo
INTO your variables
By putting the order by in a subquery, the aggregate functions will get the values in the order you want. This should be faster than looping.
You are not having edia column while creating the table. Looking at reamining code, I feel your create table query should be like following:
create table mytable
( edia serial primarykey,
eref date,
eimpte numeric);
Related
I am trying to sort the following array
'{ job.8.10.2.54, job.8.11*, job.44.89.16.188, job.29292.9, job.8.1, job.8.3, job.8.2}'
as
'{job.29292.9, job.44.89.16.188, job.8.9.2.54, job.8.11*, job.8.3, job.8.2, job.8.1}'
So, the idea would be to group those by their body, aka "the part up to the last dot."
Then order by the last_nb, meaning the part after the last dot ( and ignoring the * )
create or replace function order_array(tasks varchar[])
returns varchar[]
language plpgsql
as $$
declare
new_array varchar[];
task varchar:='';
body varchar='';
last_nb integer;
begin
--store body
task:=tasks[1];
body:= substring(task,'.*(?=\.)\.') ;
last_nb:= substring(task, '([\d]+)\*?$');
raise notice 'body %', body;
raise notice 'last_nb %', last_nb;
new_array:=array_agg(x ORDER BY substring(x, '([\d]+)\*?$')::INTEGER desc) FROM unnest(tasks) x;
return new_array;
end
$$;
select * from order_array('{job.8.10.2.54, job.8.11*, job.44.89.16.188, job.29292.9, job.8.1, job.8.3, job.8.2}');
Here, I am getting
{job.44.89.16.188,job.8.10.2.54,job.8.11*,job.29292.9,job.8.3,job.8.2,job.8.1}
The sorting seems to be working fine, I also found a way to group with
select array(select unnest(tasks) order by 1) as sorted_arr, count(*) from unnest(tasks) group by sorted_arr into new_array;
How could I combine those too in a single statement?
Thanks!
Finally, the solution was not to group, but order by body, then by last_nb
CREATE OR REPLACE FUNCTION sortage(
tasks VARCHAR[]
)
RETURNS VARCHAR[]
LANGUAGE plpgsql
AS $$
DECLARE
regex_body VARCHAR :='.*(?=\.)\.'; -- takes everything up to the last dot
regex_last_nb VARCHAR :='([\d]+)\*?$'; -- takes everything after the last dot, excluding the *
BEGIN
return array(SELECT id FROM unnest( tasks ) as id order BY substring(id,regex_body), substring(id, regex_last_nb)::INTEGER desc );
END
$$;
This is my initial PL/SQL code :
TYPE VarcharArray IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
and i use it in the following code :
PROCEDURE Create(inFatherId IN VARCHAR2, inBarcode IN VarcharArray, inItemId IN VarcharArray)
IS
myCount NUMBER(38);
sampleId_FromDb NUMBER(38);
itemId_FromDb NUMBER(38);
BEGIN
myCount := inBarcode.COUNT;
FOR i IN 1..myCount
LOOP
SELECT ITEM.Id INTO itemId_FromDb FROM ITEM WHERE FatherId = inFatherId AND CampaignItemId = inItemId(i);
SELECT SAMPLE_SEQUENCE.NEXTVAL INTO sampleId_FromDb FROM DUAL;
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId) VALUES(sampleId_FromDb, inBarcode(i), itemId_FromDb);
END LOOP;
END;
I've seen that the array type can be translated into MS SQL with Table-Valued Parameters, however how can i iterate in a similar fashion so that i include in the iteration the thee operations ?
In the current PL/SQL implementation i send up to 50.000 elements in the array and the performance is decent. I would desire something similar also in MS SQL.
There's no need to be looping and inserting one row at a time. That's just a way to make your code slower. Since tables don't have any order in them, you need to add one column to define the order. Your type would be like this:
CREATE TYPE VarcharArray AS TABLE(ID int, Item VARCHAR(100));
Then, you can rewrite your procedure as a single INSERT statement.
CREATE PROCEDURE SomeProcedure(
#FatherId AS VARCHAR, --This might need a length or will be defaulted to length 1
#Barcode AS VarcharArray READONLY,
#ItemId AS VarcharArray READONLY
)
AS
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId)
SELECT NEXT VALUE FOR SAMPLE_SEQUENCE,
bc.Item,
i.Id
FROM ITEM i
JOIN #ItemId ii ON i.CampaignItemId = ii.Item
JOIN #Barcode bc ON ii.ID = bc.ID
WHERE i.FatherId = #FatherId;
You could also create a table with both values and prevent any ordering problems that could occur.
CREATE TYPE BarcodeItems AS TABLE(Item VARCHAR(100), Barcode VARCHAR(100));
GO
CREATE PROCEDURE SomeProcedure(
#FatherId AS VARCHAR, --This might need a length or will be defaulted to length 1
#BarcodeItems AS BarcodeItems READONLY
)
AS
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId)
SELECT NEXT VALUE FOR SAMPLE_SEQUENCE,
bi.Item,
i.Id
FROM ITEM i
JOIN #BarcodeItems bi ON i.CampaignItemId = bi.Item
WHERE i.FatherId = #FatherId;
I have a sql server table named Student like below:
I wish to select the students with the highest score from each class, which shall produce the output like this:
Due to some constraint, I can't be sure how many unique class names would exist in the table. My stored procedure is :
create procedure selectBestStudent
as
begin
select Name, max(TestScore)
from [TestDB1].[dbo].[StudentTest]
group by Name
end
But the result is wrong. Any idea?
You can use ROW_NUMBER with a PARTITION BY:
SELECT Name, Class, TestScore
FROM (
SELECT Name, Class, TestScore,
ROW_NUMBER() OVER (PARTITION BY Class
ORDER BY TestScore DESC) AS rn
FROM StudentTest) AS t
WHERE t.rn = 1
ROW_NUMBER enumerates records within each Class partition: the ORDER BY clause guarantees that the record having the greatest TestScore value is assigned a value equal to 1.
Note: To handle ties you can use RANK in place of ROW_NUMBER. This way you can get all students that share the same maximum TestScore for the same Class.
You can also achieve this goal with NOT EXISTS()
SELECT * FROM Student s
WHERE NOT EXISTS(select 1 FROM Student t
where t.class = s.class
and t.testScore > s.testScore)
This will select only those rows that doesn't have a row with a higher value on testScore
I think you will have a problem with the Group By and the MAX() when there are multiple people with the same score in a class.
I solved it with a fetch if you don't know yet what this is, you can look here. It's easier than it looks at the beginning!
I know that might be a horrible way to do it but its's easy to understand and it worked! :D
USE [TestDB]
GO
DECLARE #class char(10), #testscore int;
DECLARE #result Table
(
Name char(10),
Class char(10),
TestScore int
);
-- Get Classes and their Maxima
DECLARE TestScore_cursor CURSOR FOR SELECT [class], MAX([testscore]) FROM [student] GROUP BY [class];
OPEN TestScore_cursor;
-- Perform the first fetch.
FETCH NEXT FROM TestScore_cursor INTO #class, #testscore;
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
-- Search Students by Class and Score and add them to tempTable #result
INSERT INTO #result SELECT [name], [class], [testscore] From [student] where [testScore] = #testscore AND [class] = #class;
FETCH NEXT FROM TestScore_cursor INTO #class, #testscore;
END
-- Show the Result
SELECT * FROM #result;
CLOSE TestScore_cursor;
DEALLOCATE TestScore_cursor;
GO
Let's say I have a table called SomeTable with a primary key row_id and json array field called some_json.
some_json would look something like this:
[{'some_key': 'some_value_1'}, {'some_key': 'some_value_2'},
{'some_key': 'some_value_3'}, {'some_key': 'some_value_4'}]
and I have a function that takes a text array parameter called values_to_remove like this:
['some_value_2', 'some_value_3', etc]
I want to update some_json by removing all of its json objects that contain a text value that's also in the values_to_remove array. I have a rough idea of what I need to do but don't know how to piece it all together with the correct syntax, so forgive me if doesn't make sense.
Here's what I have so far (forgive me for butchering the syntax):
CREATE OR REPLACE FUNCTION remove_json_items (removal_id smallint,
values_to_remove text[])
RETURNS void AS $$
BEGIN
UPDATE SomeTable
SET some_json = array_to_json(array_remove(ARRAY(SELECT *
FROM json_array_elements(some_json)),
(SELECT *
FROM json_array_elements(some_json),
unnest(values_to_remove)
WHERE some_json->>some_key = values_to_remove.value
)))
WHERE row_id = removal_id;
END;
$$ LANGUAGE plpgsql;
What's the correct solution for achieving this? Thanks.
Here is the function made with plain SQL
CREATE OR REPLACE FUNCTION remove_json_items (removal_id smallint,
values_to_remove text[])
RETURNS void AS $$
UPDATE sometable
SET some_json =
(SELECT json_agg(ae)
FROM (
SELECT json_array_elements(some_json) AS ae from sometable WHERE row_id = removal_id
) sq1
WHERE NOT ARRAY[(SELECT * FROM json_to_record(ae) AS x(some_key text))] <# ARRAY['some_value_3', 'some_value_2'])
WHERE row_id = removal_id;
$$ LANGUAGE SQL;
I need to create a function like this (scaled down to a minimum) where I send an array of strings that should be matched. But I cant make the query to work.
create or replace function bar(x text[]) returns table (c bigint) language plpgsql as $$
begin
return query select count(1) as counter from my_table where my_field in (x);
end;$$;
and call it like this
select * from bar(ARRAY ['a','b']);
I could try to let the parameter x be a single text string and then use something like
return query execute 'select ... where myfield in ('||x||')';
So how would I make it work with the parameter as an array?
would that be better or worse compared to let the parameter be a string?
Yes, an array is the cleaner form. String matching would leave corner cases where separators and patterns combined match ...
To find strings that match any of the given patterns, use the ANY construct:
CREATE OR REPLACE FUNCTION bar(x text[])
RETURNS bigint LANGUAGE sql AS
$func$
SELECT count(*) -- alias wouldn't visible outside function
FROM my_table
WHERE my_field = ANY(x);
$func$;
count(*) is slightly faster than count(1). Same result.
Note, I am using a plain SQL function (instead of plpgsql). Either has its pros and cons.
That's fixed with the help of unnest that converts an array to a set (btw, the function doesn't have to be plpgsql):
CREATE OR REPLACE FUNCTION bar(x text[]) RETURNS BIGINT LANGUAGE sql AS $$
SELECT count(1) AS counter FROM my_table
WHERE my_field IN (SELECT * FROM unnest(x));
$$;
The problem with using the array seems to be fixed by using
return query select count(1) as counter from my_table where my_field in (array_to_string(x,','));
The point of effiency still remains unsolved.