Progress 4GL - Enumerable.Take(TSource) Method in - progress-4gl

Thanks in advance for looking at this question!
This is all in the context of a FOR EACH loop which can get quite lengthy - think 100,000 records - and I'm looking for a way to take n records from a position in that resultset e.g. start # 4000 & take the next 500 records.
I was looking around for keywords, in the ABL Reference, such as:
Position
LookAhead
RECID - Whether we can find a RECID at the nth position
Query Tuning
So far no luck. Any smarties out there with a hint?

Here is an example that I created against the sports database. The sports database is a sample database similar to the AdventureWorks database in SQL Server.
This should get you started:
def var v-query as char no-undo.
def var h as handle no-undo.
/* Here is where can set a dynamic query */
assign v-query = "for each orderline no-lock".
/* Create handle used for query */
create query h.
/* Set the table against the query so you can access it conveniently */
/* If you have other tables in your "for each", simply do a */
/* set-buffers on each table */
h:set-buffers(buffer orderline:handle).
/* Prepare Query */
h:query-prepare(v-query).
/* DO Open */
h:query-open.
/* Setup query to row 10 */
h:reposition-to-row(10).
LINE_LOOP:
repeat:
/* Read next row */
h:get-next.
/* Check if we are not past the end */
if h:query-off-end then leave LINE_LOOP.
/* Since we added orderline as a buffer we can now use it here */
disp orderline.ordernum
orderline.linenum
orderline.itemnum
orderline.price
orderline.qty.
end. /* repeat */
h:query-close.
FYI, the Progress Knowledge base and the PSDN have great samples and tutorials

other example - filling of dataset tables - BATCHING
DEFINE TEMP-TABLE ttOrder LIKE Order.
DEFINE DATASET dsOrder FOR ttOrder.
/* you can set a buffer object */
/*DEFINE DATA-SOURCE srcOrder FOR Order.*/
/* or you can set a query */
DEFINE QUERY qOrder FOR Order SCROLLING.
QUERY qOrder:QUERY-PREPARE ("FOR EACH Order").
DEFINE DATA-SOURCE srcOrder FOR QUERY qOrder.
BUFFER ttOrder:ATTACH-DATA-SOURCE( DATA-SOURCE srcOrder:HANDLE ).
/*The maximum number of ProDataSet temp-table rows to retrieve in each FILL operation.*/
BUFFER ttOrder:BATCH-SIZE = 10.
/* Empties the table before the FILL operation begins.
Without setting this attribute will next rows append to existing in temp-table */
BUFFER ttOrder:FILL-MODE = "EMPTY".
/* first time - result 1 - 10 */
DATASET dsOrder:FILL ().
FOR EACH ttOrder:
DISPLAY ttOrder.ordernum.
END.
/* set the startpoint to position 11 */
DATA-SOURCE srcOrder:RESTART-ROW = 11.
/* second time 11 - 20 */
DATASET dsOrder:FILL ().
FOR EACH ttOrder:
DISPLAY ttOrder.ordernum.
END.

Related

Postgres and tables internal organization

I found an explanation of how things work internally in postgresql. There was the following picture:
and the following explanation:
Items after the headers is an array identifier composed of (offset,
length) pairs pointing to the actual items.
Because an item identifier is never moved until it is freed, its index
can be used on a long-term basis to reference an item, even when the
item itself is moved around on the page to compact free space. A
Pointer to an item is called CTID (ItemPointer), created by
PostgreSQL, it consists of a page number and the index of an item
identifier.
Could you be so kind to clear a couple of things out here?
Am I right that items near the page header are CTIDs themselves or Items and CTIDs are different things?
Do CTIDs never move around or rows?
Depending on the answers, maybe I'll understand what the following means exactly "Because an item identifier is never moved until it is freed, its index can be used on a long-term basis to reference an item, even when the item itself is moved around on the page to compact free space."
However, additional more detailed explanation would be nice.
What is called “item” in the picture is a “line pointer” in PostgreSQL jargon. It is defined in src/include/storage/itemid.h:
/*
* A line pointer on a buffer page. See buffer page definitions and comments
* for an explanation of how line pointers are used.
*
* In some cases a line pointer is "in use" but does not have any associated
* storage on the page. By convention, lp_len == 0 in every line pointer
* that does not have storage, independently of its lp_flags state.
*/
typedef struct ItemIdData
{
unsigned lp_off:15, /* offset to tuple (from start of page) */
lp_flags:2, /* state of line pointer, see below */
lp_len:15; /* byte length of tuple */
} ItemIdData;
typedef ItemIdData *ItemId;
These line pointers are stored in an array right after the page header.
See the excellent documentation in src/include/storage/bufpage.h:
/*
* A postgres disk page is an abstraction layered on top of a postgres
* disk block (which is simply a unit of i/o, see block.h).
*
* specifically, while a disk block can be unformatted, a postgres
* disk page is always a slotted page of the form:
*
* +----------------+---------------------------------+
* | PageHeaderData | linp1 linp2 linp3 ... |
* +-----------+----+---------------------------------+
* | ... linpN | |
* +-----------+--------------------------------------+
* | ^ pd_lower |
* | |
* | v pd_upper |
* +-------------+------------------------------------+
* | | tupleN ... |
* +-------------+------------------+-----------------+
* | ... tuple3 tuple2 tuple1 | "special space" |
* +--------------------------------+-----------------+
* ^ pd_special
*
* NOTES:
*
* linp1..N form an ItemId (line pointer) array. ItemPointers point
* to a physical block number and a logical offset (line pointer
* number) within that block/page. Note that OffsetNumbers
* conventionally start at 1, not 0.
*
* tuple1..N are added "backwards" on the page. Since an ItemPointer
* offset is used to access an ItemId entry rather than an actual
* byte-offset position, tuples can be physically shuffled on a page
* whenever the need arises. This indirection also keeps crash recovery
* relatively simple, because the low-level details of page space
* management can be controlled by standard buffer page code during
* logging, and during recovery.
Answers to your questions:
The ctid of a tuple is the physical address, consisting of the block number (starting at 0) and the line pointer (starting at 1). You can identify the line pointer from the ctid of a table row: it is the second number. For example, (321,5) would be the fifth line pointer on the 322th page.
The location of the actual tuple in the block is not fixed: it is stored in lp_off. That allows PostgreSQL to move the data around in a block without changing the physical address (tid) of the tuples. The line pointer itself never changes.
As explained above, the actual data can move in the block, but the line pointer doesn't change. The ctid of a tuple is what is stored in the index. The statement should be clear now.

Script to update 5500 fields in a telephone type column with random numbers

I would need to create a php7 script that generates 5500 random telephone numbers starting with the example number 3
"3471239900". The script should go to overwrite the data already present.
/**
* genera numero tel casuale che inizia per 3
*/
function telefono()
{
$telefono = '';
for ($k=0; $k<9; $k++) {
//genera casuale 9 cifre
$telefono .= rand(0, 9);
}
//inizia per 3
return '3' . $telefono;
}
$res = mysqli_query($conn, 'SELECT id_com FROM commesse ORDER BY id_com');
while ($riga = mysqli_fetch_assoc($res)) {
$id = (int)$riga['id_com'];
$query = "UPDATE commesse SET cliente=tel='".telefono()."' WHERE id_com=" . $id_com;
}
You don't need to invent such code to fill a single column in a database table with random numbers.
Following update statement will populate cliente_tel column of the commesse table with 10 digit random numbers all beginning with 3.
UPDATE
`commesse`
SET
`cliente_tel` = CONCAT("3",ROUND(RAND()*(999999999-100000000)+100000000))
WHERE 1;
Using ROUND() is necessary here since RAND() returns a float between 0 and 1.
Good to remember: Running any kind of update/insert statement in a loop is always expensive and slow. Try to avoid running SQL queries in a loop as much as possible.

Increment date variable

I have a SAS dataset, in which I must add a date variable, starting with a certain date (ex: July 10, 2014). For each observation, the date must increase by one day. I cannot figure out how to increment the date. Whenever I try, I get the same date for all observations.
Welcome to Stack Overflow! Let's assume your dataset looks as so:
Have
Obs Var1
1 Mazda
2 Ford
3 BMW
Want
Obs Date Var1
1 01JAN2015 Mazda
2 02JAN2015 Ford
3 03JAN2015 BMW
You can use a Sum Statement with a SAS Date Literal to accomplish this goal.
data want;
format Date date9. /* Makes date the first var, looks prettier */
set have;
if(_N_ = 1) then Date = '31DEC2014'd; /* Set initial value */
Date+1; /* Increment SAS date value by 1 day for each day */
run;
If you have not used the automatic variable N before, it's an iteration counter for each time SAS goes from the top of the data step to the bottom.
The likely reason that you are seeing the same date for each day is because you are not retaining the value you want to increment. Consider the below example program:
data WontWork;
set have;
Add_Me = 1;
/* Do loop just simulates dataset iterations */
do i = 1 to 10;
Add_Me = Add_Me + 1;
output;
end;
drop i;
run;
Explanation
Whenever SAS runs through one iteration of the data step, the Program Data Vector (PDV) resets all non-automatic variables to missing. To fix this, you must either use a Retain statement and then increment the variable, or use a Sum Statement to do the job of both retaining and summing up the variable. The Retain/Sum Statements both tell SAS to remember the last value of a variable so that it does not get reset to missing when it iterates through the data step. One unique property of the retain statement is that you can set an initial value. By default, the retain statement will initialize the variable as missing. The sum statement will always initialize a variable as missing.
data works;
retain Add_Me 0;
/* Do loop just simulates dataset iterations */
do i = 1 to 10;
Add_Me = sum(Add_Me, 1);
output;
end;
drop i;
run;
OR
data works2;
/* Do loop just simulates dataset iterations */
do i = 1 to 10;
Add_Me+1;
output;
end;
drop i;
run;
Note that the sum statement does both of these steps, and also handles missing values. Think of it as a shortcut.
I hope this resolved your problem, and again welcome to Stack Overflow!

Using cell value as reference to sheet in formulas

I have a spreadsheet with three sheets. Two are called 2012 and 2011 and have a bunch of similar data. The last sheet does comparisons between the data.
To be able to choose year, I'm using a cell (D1) where I can I can write either 2011 or 2012. The formulas then use the INDIRECT function to include this cell as part of the reference.
INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!F:F")
This is not a pretty solution and makes the formula quite long and complex.
=IFERROR(SUM(FILTER( INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!M:M") ; (INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!B:B")=$A4)+(INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!B:B")=$A5)+(INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!B:B")=$A6)+(INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!B:B")=$A7)+(INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!B:B")=$A8); MONTH(INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!D:D"))=$B$1 ; INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!F:F")=D$3));0)
Is there a better way of doing this?
I've tried to create a separate spreadsheet for the calculations sheet and importing (IMPORTRANGE) the data from the two sheets together on one sheet with VMERGE (custom function from the script gallery) but there is quite a lot of of data in these two sheets and the import takes a long time. Any changes (like changing year) also take a long time to recalculate.
Database functions tend to be cleaner when doing this kind of thing.
https://support.google.com/docs/bin/static.py?hl=en&topic=25273&page=table.cs&tab=1368827
Database functions take a while to learn, but they are powerful.
Or
You could put INDIRECT(CHAR(39)&$D$1&CHAR(39)&"!B:B") in a cell on its own.
I think that you have two years of information where the schema is identical (column C has the same type of information on both sheets). Also, I'm assuming that column B tracks the year.
If so, consider holding all of your information on one sheet and and use the spreadsheet function "QUERY" to create views.
For instance, this formula returns all the cells between A1:E from a sheet named "DataSheet" where the values in column B = 2010.
=QUERY(DataSheet!A1:E; "SELECT * WHERE B = 2010";1)
Sometimes there is a really good reason to have the data stored on two sheets. If so, use one of the vMerge functions in the script gallery to assemble a working sheet. Then create views and reports from the working sheet.
function VMerge() {
var maxw=l=0;
var minw=Number.MAX_VALUE;
var al=arguments.length ;
for( i=0 ; i<al ; i++){
if( arguments[i].constructor == Array )l =arguments[i][0].length ;
else if (arguments[i].length!=0) l = 1 ; // literal values count as array with a width of one cell, empty cells are ignored!
maxw=l>maxw?l:maxw;
minw=l<minw?l:minw;
}
if( maxw==minw) { /* when largest width equals smallest width all are equal */
var s = new Array();
for( i=0 ; i<al ; i++){
if( arguments[i].constructor == Array ) s = s.concat( arguments[i].slice() )
else if (arguments[i].length!=0) s = s.concat( [[arguments[i]]] )
}
if ( s.length == 0 ) return null ; else return s //s
}
else return "#N/A: All data ranges must be of equal width!"
}
Hope this helps.

AChartEngine Messed up labels

My chart displays fine but as soon as I scroll to the side, I have random time appearing and it messes up the dates, see this picture:
http://img14.imageshack.us/img14/8329/statqs.jpg
I'd like to only display the date and nothing else, I don't know how the renderer comes up with time that I never entered.
Also I'd like to know how I can prevent scrolling to the left (x axis) and down (negative y), I can no longer use SetPanLimits because my x values are dates and not numbers.
Any help would be greatly appreciated!
I know this is very old, but for the next user, it may help to have the solution.
You can specify the date format to use
/**
* Creates a time chart intent that can be used to start the graphical view
* activity.
*
* #param context the context
* #param dataset the multiple series dataset (cannot be null)
* #param renderer the multiple series renderer (cannot be null)
* #param format the date format pattern to be used for displaying the X axis
* date labels. If null, a default appropriate format will be used.
* #return a time chart intent
* #throws IllegalArgumentException if dataset is null or renderer is null or
* if the dataset and the renderer don't include the same number of
* series
*/
public static final Intent getTimeChartIntent(Context context, XYMultipleSeriesDataset dataset,
XYMultipleSeriesRenderer renderer, String format) {
return getTimeChartIntent(context, dataset, renderer, format, "");
}
To show only day and month, use something like the following:
Intent intent = ChartFactory.getTimeChartIntent(context, dataset, mRenderer, "dd-MMM");