I am looking to create a packed record that can hold an array the varies in length from 5 - 50 elements. Is it possible to do this in such a way that the record can be packed with no wasted space? I will know how many elements will be in the array when I go to create the record.
-- the range of the array
type Array_Range_T is Integer range 5 .. 50;
-- the array type
type Array_Type_T is array range (Array_Range_T) of Integer;
-- the record
type My_Record_T (Array_Length : Integer := 5) is
record
-- OTHER DATA HERE
The_Array : Array_Type_T(Array_Length);
end record;
-- Pack the record
for My_Record_T use
record
-- OTHER DATA
The_Array at 10 range 0 .. Array_Length * 16;
end record;
for My_Record_T'Size use 80 + (Array_Length * 16);
This obviously won't compile, but shows the spirit of what I am trying to do. If possible I would like to keep the length of the array out of the record.
Thank you!
There really isn't a way in Ada to represent the record the way you're asking for. However, since your concern really isn't with how the record is represented in memory, but rather with how it's transmitted to a socket, you probably don't need to worry about record representation clauses.
Instead, you can define your own Write routine:
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T);
for My_Record_T'Write use Write;
or, I believe this will work in Ada 2012:
type My_Record_T is record
...
end record
with Write => Write;
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T);
and then the body will look like
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T) is
begin
-- Write out the record components, EXCEPT Array_Length and The_Array.
T1'Write (Stream, Item.F1); -- F1 is a record field, T1 is its type
T2'Write (Stream, Item.F2); -- F2 is a record field, T2 is its type
...
-- Now write the desired data
declare
Data_To_Write : Array_Type_T (1 .. Item.Array_Length)
renames Item.The_Array (1 .. Item.Array_Length);
-- I'm assuming the lower bound is 1, but if not, adjust your code
-- accordingly
begin
Array_Type_T'Write (Stream, Data_To_Write);
-- Note: using 'Write will write just the data, without any bound
-- information, which is what you want.
end;
end Write;
This won't work if the other components need to be packed, though, e.g. if you want to write a byte to the socket that contains one 3-bit record component and one 5-bit record component. If that's necessary, I don't think the built-in 'Write attributes will do that for you; you may need to do your own bit-twiddling, or you could get tricky and define an array of Stream_Elements and use an Address clause or aspect to define an array that overlays the rest of the record. But I wouldn't use the overlay method unless I were 100% certain that the reader at the other end of the socket were an Ada program that uses the exact same type definition.
Note: I haven't tested this.
Not sure I completely understand what you're trying to achieve but can't you do something like this
-- the range of the array
type Array_Range_T is range 1 .. 50;
-- the array type
type Array_Type_T is array (Array_Range_T range <>) of Integer;
Array_Length : constant := 5; --5 elements in the array
-- the record
type My_Record_T is
record
-- OTHER DATA HERE
The_Array : Array_Type_T (1 .. Array_Length);
end record;
-- Pack the record
for My_Record_T use
record
-- OTHER DATA
The_Array at 0 range 0 .. (Array_Length * Integer'Size) - 1 ;
end record;
for My_Record_T'Size use (Array_Length * Integer'Size);
Related
Is there a way to evaluate a STRING expression in Eiffel? (A big source of error I know... but a powerful mechanism!)
Looking for a way to do a generic setting mechanism for database fields and classes I'm trying to do something like.
fields_mapping: HASH_TABLE [ANY, STRING]
do
create Result.make (10)
Result.put (name, "name")
Result.put (surname, "surname")
...
end
set_fields
do
across
fields_mapping as field
loop
eval("set_" + field.key + "(" + field.item + ")")
end
end
I know I could do it with agents, but it seems for me less generic as I have to define every function 2 times
in the fields_mapping
in another fields_mapping I have for JSON conversion
Existing implementations of Eiffel are compiling rather than interpreting. Therefore, evaluation of arbitrary expressions is not supported. Updating arbitrary object fields using given values is still possible using reflection classes:
update_object (field_values: HASH_TABLE [ANY, STRING]; target: ANY)
-- Update fields of object `target` with values from `field_values`.
local
object: REFLECTED_REFERENCE_OBJECT
fields: like field_properties
position: like {REFLECTED_OBJECT}.field_count
do
create object.make (target)
fields := field_properties (object.dynamic_type)
across
field_values as v
loop
if attached fields [v.key] as f then
position := f.position
if {REFLECTOR}.field_conforms_to (v.item.generating_type.type_id, f.type_id) then
inspect object.field_type (position)
when {REFLECTOR_CONSTANTS}.integer_32_type then
object.set_integer_32_field (position, {INTEGER_32} / v.item)
when ... then
-- Other basic types.
when {REFLECTOR_CONSTANTS}.reference_type then
object.set_reference_field (position, v.item)
else
print ("Unhandled field type%N")
end
else
print ("Field type mismatch%N")
end
end
end
end
The code above is a bit simplified because it does not handle types SPECIAL and TUPLE as well as user-defined expanded types. The code relies on a helper feature that records type information so that next time it should not be recomputed from scratch:
field_properties (type_id: like {TYPE [ANY]}.type_id):
HASH_TABLE [TUPLE [position: INTEGER; type_id: INTEGER], STRING]
-- Positions and types of fields indexed by their name
-- for a specified type ID `type_id`.
local
i: like {REFLECTOR}.field_count_of_type
do
Result := field_positions_table [type_id]
if not attached Result then
from
i := {REFLECTOR}.field_count_of_type (type_id)
create Result.make (i)
field_positions_table.force (Result, type_id)
until
i <= 0
loop
Result [{REFLECTOR}.field_name_of_type (i, type_id)] :=
[i, {REFLECTOR}.field_static_type_of_type (i, type_id)]
i := i - 1
end
end
end
field_positions_table: HASH_TABLE [HASH_TABLE
[TUPLE [position: INTEGER; type_id: INTEGER], STRING], INTEGER]
once
create Result.make (1)
end
Using the feature update_object and assuming an object x has fields foo and bar of types INTEGER_32 and detachable STRING_8 respectively, the following code
field_values: HASH_TABLE [ANY, STRING]
do
create field_values.make (10)
field_values ["foo"] := {INTEGER_32} 5
field_values ["bar"] := " bottles"
update_object (field_values, x)
print (x.foo)
print (x.bar)
will print 5 bottles regardless of the previous state of the object x.
I'm relatively new to pl/sql and i'm trying to make a list with records objects but i dont know how to initialize for each item of the list both fields from record item. For example : in procedure "new item" how i can initialize example(1) ? with example(1).id_std := integer and example(1).procent := integer ? Thanks!
This is how my code looks like :
set serveroutput on;
CREATE OR REPLACE PACKAGE newExercise IS
TYPE item IS RECORD(
id_std INTEGER,
procent INTEGER
);
TYPE tabel IS VARRAY(5) OF item;
PROCEDURE newItem (example tabel);
example2 tabel := tabel();
end newExercise;
/
CREATE OR REPLACE PACKAGE BODY newExercise IS
PROCEDURE newItem (example tabel) IS
BEGIN
FOR i IN 1..example.LIMIT LOOP
DBMS_OUTPUT.PUT_LINE(example(i));
end loop;
end newItem;
end newExercise;
/
Record types are for storing the results of queries. So you could do this:
declare
recs newExercise.tabel;
begin
select level, level * 0.25
bulk collect into recs
from dual
connect by level <= 5;
newExercise.newItem (recs);
end;
/
Note that VARRAY is not a suitable collection type for this purpose, because it's not always possible to predict how many rows a query will return. It's better to use
TYPE tabel IS table OF item;
When you refer to the record you usually have to specify specific fields. This populates the records with calculated values; to be able to do that I've had to changed the procedure argument from the default IN direction to IN OUT, both in the specification:
CREATE OR REPLACE PACKAGE newExercise IS
TYPE item IS RECORD(
id_std INTEGER,
procent INTEGER
);
TYPE tabel IS VARRAY(5) OF item;
PROCEDURE newItem (example IN OUT tabel);
-- ^^^^^^ make in/out to be updateable
-- example2 tabel := tabel(); -- not used
END newExercise;
/
and in the body:
CREATE OR REPLACE PACKAGE BODY newExercise IS
PROCEDURE newItem (example IN OUT tabel) IS
-- ^^^^^^ make in/out to be updateable
BEGIN
FOR i IN 1..example.LIMIT LOOP
-- extend collection to create new record
example.extend();
-- assign values to record fields
example(i).id_std := i;
example(i).procent := 100 * (1/i);
END LOOP;
END newItem;
END newExercise;
/
The LIMIT is five, from the definition, but the varray instance is initially empty (from tabel()). For population you can loop from 1 to that limit of five, but you have to extend() the collection to actually create the record in that position. Records are created with all fields set to null by default. You can then assign values to the fields of each record. (I've just made something up, obviously).
You can then test that with an anonymous block:
declare
example newExercise.tabel := newExercise.tabel();
begin
-- call procedure
newExercise.newItem(example);
-- display contents for debuggibg
FOR i IN 1..example.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Item ' || i
|| ' id_std: ' || example(i).id_std
-- ^^^^^^^ refer to field
|| ' procent: ' || example(i).procent);
-- ^^^^^^^ refer to field
END LOOP;
end;
/
Item 1 id_std: 1 procent: 100
Item 2 id_std: 2 procent: 50
Item 3 id_std: 3 procent: 33
Item 4 id_std: 4 procent: 25
Item 5 id_std: 5 procent: 20
PL/SQL procedure successfully completed.
I've put the original loop to display the contents of the array in that block, as you wouldn't generally have that as part of a procedure. You could still use LIMIT for that loop, but COUNT is safer in case the procedure doesn't fully populate it.
You can also extend once before the loop:
PROCEDURE newItem (example IN OUT tabel) IS
BEGIN
-- extend collection to create all new records
example.extend(example.LIMIT);
FOR i IN 1..example.LIMIT LOOP
example(i).id_std := i;
example(i).procent := 100 * (1/i);
END LOOP;
END newItem;
If you already know the values you want to assign - and they aren't coming from a table, in which case you'd use APC's approach - you can just assign to the last created record; this is a rather contrived example:
PROCEDURE newItem (example IN OUT tabel) IS
BEGIN
example.extend(); -- first record
example(example.LAST).id_std := 1;
example(example.LAST).procent := 7;
example.extend(); -- second record, left with null fields
example.extend(); -- third record
example(example.LAST).id_std := 3;
example(example.LAST).procent := 21;
example.extend(); -- fourth record, left with null fields
END newItem;
and the same anonymous block now gives:
Item 1 id_std: 1 procent: 7
Item 2 id_std: procent:
Item 3 id_std: 3 procent: 21
Item 4 id_std: procent:
PL/SQL procedure successfully completed.
Notice the null values, and that there is no 5th row.
Or again extend the collection once, and refer to the numbered records directly:
PROCEDURE newItem (example IN OUT tabel) IS
BEGIN
example.extend(4);
example(1).id_std := 1;
example(1).procent := 7;
example(3).id_std := 3;
example(3).procent := 21;
END newItem;
which gets the same result from the anonymous block.
EDIT: I'm having alot of trouble formatting this code for some reason please bear with me. Also i'm aware some code is missing. This is just one portion of the code.
I am simulating a batch load that is run nightly to do some load testing. The problem I face is that my auto-generated PK's exceed the columns datalength after 100 or so inserts. How would I cap off my Strings without violating the unique constraint while inserting around 20,000 rows per table. my goal is to get rid of the random strings due to a change in requirements.Below is the portion of code I'm having trouble with.
declare
l_cnt integer := 0;
t_cnt integer := 0;
c_cnt integer := 0;
f_cnt integer := 0;
i integer := 0;
TYPE T_EMPL_NO IS TABLE OF VARCHAR2(1000) INDEX BY BINARY_INTEGER;
TAB_EMPL_NO T_EMPL_NO;
TAB_SEC_PK T_EMPL_NO;
TAB_THR_PK T_EMPL_NO;
TAB_FTH_PK T_EMPL_NO;
begin
dbms_output.put_line('START LOAD TEST');
LOOP
i := i + 1;
TAB_EMPL_NO(l_cnt) := 'JB'||i;
TAB_SEC_PK(t_cnt) := dbms_random.string('L',6);
TAB_THR_PK(c_cnt) := dbms_random.string('L',1);
TAB_FTH_PK(f_cnt) := dbms_random.string('L',20);
Insert into AOMS.PARTS_MONTH_CLOSE(
NAMES OF COLUMNS HERE
) Values (
TAB_EMPL_NO(l_cnt),
TAB_SEC_PK(t_cnt),
TAB_THR_PK(c_cnt),
TAB_FTH_PK(f_cnt)
);
l_cnt := l_cnt + SQL%ROWCOUNT;
EXIT WHEN l_cnt = 100; -- change to record count 22k
END LOOP;
dbms_output.put_line('P2ACCTMO :Rows inserted: ' || l_cnt);
END;
/
As a bonus If I wanted the script to run for an hour but not exceed the amount of records that insert during a loop how would I do that? Thanks so much for any help.
To define a numeric datatype with a given range (let's say 1 to 1000, for the sake of argument) you can use a user-defined PL/SQL subtype. To do this you'd use something like
SUBTYPE MY_ZERO_TO_1K_SUBTYPE IS NUMBER(4,0) RANGE 0..1000;
and then define variables of this subtype just as you would any other variable:
nLimited_number MY_ZERO_TO1K_SUBTYPE;
You can set a variable of this type to 0, 1, 2, ..., 998, 999, 1000. However, if you set it to a negative value or a value greater than 1000 an ORA-06502: PL/SQL: numeric or value error exception will be raised.
I would suggest to go for SEQUENCES rather than using Random string
generation. In this way you wlll not face unique key violation and
also you can set the max seq limit to the datatype limit of the
column. Hope this information helps.
How can I declare an array like variable with two or three values and get them randomly during execution?
a := [1, 2, 5] -- sample sake
select random(a) -- returns random value
Any suggestion where to start?
Try this one:
select (array['Yes', 'No', 'Maybe'])[floor(random() * 3 + 1)];
Updated 2023-01-10 to fix the broken array literal. Made it several times faster while being at it:
CREATE OR REPLACE FUNCTION random_pick()
RETURNS int
LANGUAGE sql VOLATILE PARALLEL SAFE AS
$func$
SELECT ('[0:2]={1,2,5}'::int[])[trunc(random() * 3)::int];
$func$;
random() returns a value x where 0.0 <= x < 1.0. Multiply by 3 and truncate it with trunc() (slightly faster than floor()) to get 0, 1, or 2 with exactly equal chance.
Postgres indexes are 1-based by default (as per SQL standard). This would be off-by-1. We could increment by 1 every time, but for efficiency I declare the array index to start with 0 instead. Slightly faster, yet. See:
Normalize array subscripts so they start with 1
The manual on mathematical functions.
PARALLEL SAFE for Postgres 9.6 or later. See:
PARALLEL label for a function with SELECT and INSERT
When to mark functions as PARALLEL RESTRICTED vs PARALLEL SAFE?
You can use the plain SELECT statement if you don't want to create a function:
SELECT ('[0:2]={1,2,5}'::int[])[trunc(random() * 3)::int];
Erwin Brandstetter answered the OP's question well enough. However, for others looking for understanding how to randomly pick elements from more complex arrays (like me some two months ago), I expanded his function:
CREATE OR REPLACE FUNCTION random_pick( a anyarray, OUT x anyelement )
RETURNS anyelement AS
$func$
BEGIN
IF a = '{}' THEN
x := NULL::TEXT;
ELSE
WHILE x IS NULL LOOP
x := a[floor(array_lower(a, 1) + (random()*( array_upper(a, 1) - array_lower(a, 1)+1) ) )::int];
END LOOP;
END IF;
END
$func$ LANGUAGE plpgsql VOLATILE RETURNS NULL ON NULL INPUT;
Few assumptions:
this is not only for integer arrays, but for arrays of any type
we ignore NULL data; NULL is returned only if the array is empty or if NULL is inserted (values of other non-array types produce an error)
the array don't need to be formatted as usual - the array index may start and end anywhere, may have gaps etc.
this is for one-dimensional arrays
Other notes:
without the first IF statement, empty array would lead to an endless loop
without the loop, gaps and NULLs would make the function return NULL
omit both array_lower calls if you know that your arrays start at zero
with gaps in the index, you will need array_upper instead of array_length; without gaps, it's the same (not sure which is faster, but they shouldn't be much different)
the +1 after second array_lower serves to get the last value in the array with the same probability as any other; otherwise it would need the random()'s output to be exactly 1, which never happens
this is considerably slower than Erwin's solution, and likely to be an overkill for the your needs; in practice, most people would mix an ideal cocktail from the two
Here is another way to do the same thing
WITH arr AS (
SELECT '{1, 2, 5}'::INT[] a
)
SELECT a[1 + floor((random() * array_length(a, 1)))::int] FROM arr;
You can change the array to any type you would like.
CREATE OR REPLACE FUNCTION pick_random( members anyarray )
RETURNS anyelement AS
$$
BEGIN
RETURN members[trunc(random() * array_length(members, 1) + 1)];
END
$$ LANGUAGE plpgsql VOLATILE;
or
CREATE OR REPLACE FUNCTION pick_random( members anyarray )
RETURNS anyelement AS
$$
SELECT (array_agg(m1 order by random()))[1]
FROM unnest(members) m1;
$$ LANGUAGE SQL VOLATILE;
For bigger datasets, see:
http://blog.rhodiumtoad.org.uk/2009/03/08/selecting-random-rows-from-a-table/
http://www.depesz.com/2007/09/16/my-thoughts-on-getting-random-row/
https://blog.2ndquadrant.com/tablesample-and-other-methods-for-getting-random-tuples/
https://www.postgresql.org/docs/current/static/functions-math.html
CREATE FUNCTION random_pick(p_items anyarray)
RETURNS anyelement AS
$$
SELECT unnest(p_items) ORDER BY RANDOM() LIMIT 1;
$$ LANGUAGE SQL;
In this block of SAS data step code I am setting a Table from an SQL query called TEST_Table. This table contains multiple columns including a larger section of columns titled PREFIX_1 to PREFIX_20. Each column starts with PREFIX_ and then an incrementing number from 1 to 20.
What I would like to do is iteratively cycle through each column and analyze the value of that column.
Below is an example of what I am trying to go for. As you can see I would like to create a variable that increases on each iteration and then I use that count value as a part of the variable name I am checking.
data TEST_Data;
set TEST_Table;
retain changing_number;
changing_number=1;
do while(changing_number<=20);
if PREFIX_changing_number='BAD_IDENTIFIER' then do;
PREFIX_changing_number='This is a bad part';
end;
end;
run;
How would be the best way to do this in SAS? I know I can do it by simply checking each value individually from 1 to 20.
if PREFIX_1 = 'BAD_IDENTIFIER' then do;
PREFIX_1 = 'This is a bad part';
end;
if PREFIX_2 = ...
But that would be really obnoxious as later I will be doing the same thing with a set of over 40 columns.
Ideas?
SOLUTION
data TEST_Data;
set TEST_Table;
array SC $ SC1-SC20;
do i=1 to dim(SC);
if SC{i}='xxx' then do;
SC{i}="bad part";
end;
end;
run;
Thank you for suggesting Arrays :)
You need to look up Array processing in SAS. Simply put, you can do something like this:
data TEST_Data;
set TEST_Table;
*retain changing_number; Remove this - even in your code it does nothing useful;
array prefixes prefix:; *one of a number of ways to do this;
changing_number=1;
do while(changing_number<=20);
if prefixes[changing_number]='BAD_IDENTIFIER' then do;
prefixes[changing_number]='This is a bad part';
end;
end;
run;
A slightly better loop is:
do changing_number = 1 to dim(prefixes);
... loop ...
end;
As that's all in one step, and it is flexible with the number of array elements (dim = number of elements in the array).