text:
13 Using Pipelined and Parallel Table Functions
The table functions and the generic datatypes ANYTYPE
, and ANYDATASET
are often used with table functions.
13.1 Overview of Table Functions
Table functions are functions that produce a collection of rows (either a nested table or a varray) that can be queried like a physical database table. You use a table function like the name of a database table, in the FROM
clause of a query.
A table function can take a collection of rows as input. An input collection parameter can be either a collection type or a REF CURSOR
Table function may be executed in parallel, and returned rows can be streamed directly to the next process without intermediate staging. Rows from a collection returned by a table function can also be pipelined; this means that they are iteratively returned as they are produced, instead of being returned in a single batch after all processing of the table function's input is completed.
Streaming, pipelining, and parallel execution of table functions can improve performance in the following manner:
By enabling multithreaded, concurrent execution of table functions
By eliminating intermediate staging between processes
By improving query response time: With non-pipelined table functions, the entire collection returned by a table function must be constructed and returned to the server before the query can return a single result row. Pipelining enables rows to be returned iteratively, as they are produced. This also reduces the memory that a table function requires, as the object cache does not have to materialize the entire collection.
By iteratively providing result rows from the collection returned by a table function as the rows are produced instead of waiting until the entire collection is staged in tables or memory and then returning the entire collection
Figure 13-1 shows a typical data-processing scenario in which data goes through several (in this case, three) transformations, implemented by table functions, before finally being loaded into a database. In this scenario, the table functions are not run in parallel, and the entire result collection must be staged after each transformation.
Figure 13-1 Typical Data Processing with Non-Parallel, Non-Pipelined Table Functions
Description of "Figure 13-1 Typical Data Processing with Non-Parallel, Non-Pipelined Table Functions"
By contrast, Figure 13-2 shows how streaming and parallel execution can streamline the same scenario.
Figure 13-2 Data Processing Using Pipelining and Parallel Execution
Description of "Figure 13-2 Data Processing Using Pipelining and Parallel Execution"
13.2 Table Function Concepts
Consider table functions and some concepts related to pipelining and parallel execution of these table functions.
13.2.1 Table Functions
Table functions return a collection type instance and can be queried like a table by calling the function in the FROM
clause of a query. Table functions use the TABLE
The following example shows a table function GetBooks
that takes a CLOB
as input and returns an instance of the collection type BookSet_t
. The CLOB
column stores a catalog listing of books in some format (either proprietary or following a standard such as XML). The table function returns all the catalogs and their corresponding book listings. The collection type BookSet_t
is defined in Example 13-1.
The CLOB
s are stored in a table Catalogs
, as demonstrated in Example 13-2.
Function GetBooks()
is defined in Example 13-3.
The query in Example 13-4 returns all the catalogs and their corresponding book listings.
Example 13-1 Creating a Collection Type
CREATE TYPE Book_t AS OBJECT ( name VARCHAR2(100), author VARCHAR2(30), abstract VARCHAR2(1000)); CREATE TYPE BookSet_t AS TABLE OF Book_t;
Example 13-2 Storing a Clob in a Table
CREATE TABLE Catalogs ( name VARCHAR2(30), cat CLOB);
Example 13-3 Creating a Function that Returns a Collection Type
CREATE FUNCTION GetBooks(a CLOB) RETURN BookSet_t;
Example 13-4 Using a Collection Type in a Query
SELECT c.name, Book.name, Book.author, Book.abstract FROM Catalogs c, TABLE(GetBooks(c.cat)) Book;
13.2.2 Pipelined Table Functions
Data is said to be pipelined if it is consumed by a consumer (transformation) as soon as the producer (transformation) produces it, without being staged in tables or a cache before being input to the next transformation.
Pipelining enables a table function to return rows faster and can reduce the memory required to cache a table function's results.
A pipelined table function can return the table function's result collection in subsets. The returned collection behaves like a stream that can be fetched from on demand. This makes it possible to use a table function like a virtual table.
Pipelined table functions can be implemented in two ways:
In the native PL/SQL approach, the consumer and producers can run on separate execution threads (either in the same or different process context) and communicate through a pipe or queuing mechanism. This approach is similar to co-routine execution.
In the interface approach, the consumer and producers run on the same execution thread. Producer explicitly returns the control back to the consumer after producing a set of results. In addition, the producer caches the current state so that it can resume where it left off when the consumer invokes it again.
The interface approach requires you to implement a set of well-defined interfaces in a procedural language.
The co-routine execution model provides a simpler, native PL/SQL mechanism for implementing pipelined table functions, but this model cannot be used for table functions written in C or Java. The interface approach, on the other hand, can. The interface approach requires the producer to save the current state information in a context object before returning so that this state can be restored on the next invocation.
In this discussion, the term table function refers to a pipelined table function— a table function that returns a collection in an iterative, pipelined way.
13.2.3 Pipelined Table Functions with REF CURSOR Arguments
A pipelined table function can accept any argument that regular functions accept. A table function that accepts a REF CURSOR
as an argument can serve as a transformation function. That is, it can use the REF CURSOR
to fetch the input rows, perform some transformation on them, and then pipeline the results out (using either the interface approach or the native PL/SQL approach).
For example, the following code sketches the declarations that define a StockPivot
function. This function converts a row of the type (Ticker, OpenPrice, ClosePrice
) into two rows of the form (Ticker, PriceType, Price
). Calling StockPivot
for the row ("ORCL", 41, 42
) generates two rows: ("ORCL", "O", 41
) and ("ORCL", "C", 42
Input data for the table function might come from a source such as table StockTable
CREATE TABLE StockTable ( ticker VARCHAR(4), openprice NUMBER, closeprice NUMBER );
The declarations are in Example 13-5.
Example 13-6 uses the StockPivot
table function.
In the preceding query, the pipelined table function StockPivot
fetches rows from the CURSOR
subquery SELECT * FROM StockTable
, performs the transformation, and pipelines the results back to the user as a table. The function produces two output rows (collection elements) for each input row.
Note that when a CURSOR
subquery is passed from SQL to a REF CURSOR
function argument as in the preceding example, the referenced cursor is open when the function begins executing.
Note also that cursor operations are not allowed for REF CURSOR
variables based on table functions: SELECT FOR UPDATE
, and WHERE CURRENT OF
See Also:
Pipelined Table Functions: Interface Approach Example for a complete implementation of this table function using the interface approach, in both C and Java.
Example 13-5 Declaring a Pipelined Table Function with REF CURSOR Arguments
-- Create the types for the table function's output collection -- and collection elements CREATE TYPE TickerType AS OBJECT ( ticker VARCHAR2(4), PriceType VARCHAR2(1), price NUMBER ); CREATE TYPE TickerTypeSet AS TABLE OF TickerType; -- Define the ref cursor type CREATE PACKAGE refcur_pkg IS TYPE refcur_t IS REF CURSOR RETURN StockTable%ROWTYPE; END refcur_pkg; / -- Create the table function CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED ... ; /
Example 13-6 Using a Pipelined Table Function with REF CURSOR Arguments
SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));
13.2.4 Parallel Execution of Table Functions
With parallel execution of a function that appears in the SELECT
list, execution of the function is pushed down to and conducted by multiple slave scan processes. These each execute the function on a segment of the function's input data.
For example, the query
SELECT f(col1) FROM tab;
is parallelized if f
is a pure function. The SQL executed by a slave scan process is similar to:
SELECT f(col1) FROM tab WHERE ROWID BETWEEN :b1 AND :b2;
Each slave scan operates on a range of rowids and applies function f
to each contained row. Function f
is then executed by the scan processes; it does not run independently of them.
Unlike a function that appears in the SELECT
list, a table function is called in the FROM
clause and returns a collection. This affects the way that table function input data is partitioned among slave scans because the partitioning approach must be appropriate for the operation that the table function performs. (For example, an ORDER BY
operation requires input to be range-partitioned, whereas a GROUP BY
operation requires input to be hash partitioned.)
A table function itself specifies in its declaration the partitioning approach that is appropriate for it, as described in "Input Data Partitioning". The function is then executed in a two-stage operation. First, one set of slave processes partitions the data as directed in the function's declaration; then a second set of slave scans executes the table function in parallel on the partitioned data. The table function in the following query has a REF CURSOR
SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab)));
The scan is performed by one set of slave processes, which redistributes the rows (based on the partitioning method specified in the function declaration) to a second set of slave processes that actually executes function f
in parallel.
13.3 Pipelined Table Functions
Consider issues involved in implementing pipelined table functions.
13.3.1 Implementation Choices for Pipelined Table Functions
As noted previously, two approaches are supported for implementing pipelined table functions: the interface approach and the PL/SQL approach.
The interface approach requires the user to supply a type that implements a predefined Oracle interface consisting of start, fetch, and close operations. The type is associated with the table function when the table function is created. During query execution, the fetch
method is invoked repeatedly to iteratively retrieve the results. With the interface approach, the methods of the implementation type associated with the table function can be implemented in any of the supported internal or external languages (including PL/SQL, C/C++, and Java).
With the PL/SQL approach, a single PL/SQL function includes a special instruction to pipeline results (single elements of the collection) out of the function instead of returning the whole collection as a single value. The native PL/SQL approach is simpler to implement because it requires writing only one PL/SQL function.
The approach used to implement pipelined table functions does not affect the way they are used. Pipelined table functions are used in SQL statements in exactly the same way regardless of the approach used to implement them.
13.3.2 Declaring Pipelined Table Functions
You declare a pipelined table function by specifying the PIPELINED
keyword. This keyword indicates that the function returns rows iteratively. The return type of the pipelined table function must be a collection type (a nested table
or a varray
Example 13-7 shows declarations of pipelined table functions implemented using the interface approach. The interface routines for functions GetBooks
and StockPivot
have been implemented in the types BookMethods
and StockPivotImpl
, respectively.
Example 13-8 shows declarations of the same table functions implemented using the native PL/SQL approach.
Example 13-7 Declaring Pipelined Table Functions for the Interface Approach
CREATE FUNCTION GetBooks(cat CLOB) RETURN BookSet_t PIPELINED USING BookMethods; CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED USING StockPivotImpl;
Example 13-8 Declaring Pipelined Table Functions for the Native PL/SQL Approach
CREATE FUNCTION GetBooks(cat CLOB) RETURN BookSet_t PIPELINED IS ...; CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED IS...;
13.3.3 Implementing the Native PL/SQL Approach
In PL/SQL, the PIPE ROW
statement causes a table function to pipe a row and continue processing. The statement enables a PL/SQL table function to return rows as soon as they are produced. This is demonstrated in Example 13-9. For performance reasons, the PL/SQL run-time system provides the rows to the consumer in batches.
In Example 13-9, the PIPE ROW(out_rec)
statement pipelines data out of the PL/SQL table function.
The PIPE ROW
statement may be used only in the body of pipelined table functions; an error is raised if it is used anywhere else. The PIPE ROW
statement can be omitted for a pipelined table function that returns no rows.
A pipelined table function must have a RETURN
statement that does not return a value. The RETURN
statement transfers the control back to the consumer and ensures that the next fetch gets a NO_DATA_FOUND
Example 13-9 Implementing a Pipelined Table Function for the Native PL/SQL Approach
CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED IS out_rec TickerType := TickerType(NULL,NULL,NULL); in_rec p%ROWTYPE; BEGIN LOOP FETCH p INTO in_rec; EXIT WHEN p%NOTFOUND; -- first row out_rec.ticker := in_rec.Ticker; out_rec.PriceType := 'O'; out_rec.price := in_rec.OpenPrice; PIPE ROW(out_rec); -- second row out_rec.PriceType := 'C'; out_rec.Price := in_rec.ClosePrice; PIPE ROW(out_rec); END LOOP; CLOSE p; RETURN; END; /
13.3.4 Pipelining Between PL/SQL Table Functions
With serial execution, results are pipelined from one PL/SQL table function to another using an approach similar to co-routine execution. Example 13-10 pipelines results from function g
to function f
Parallel execution works similarly, except that each function executes in a different process or set of processes.
Example 13-10 Pipelining Function Results from One Function to Another
SELECT * FROM TABLE(f(CURSOR(SELECT * FROM TABLE(g()))));
13.3.5 Combining PIPE ROW with AUTONOMOUS_TRANSACTION
Because table functions pass control back and forth to a calling routine as rows are produced, there is a restriction on combining table functions and PRAGMA AUTONOMOUS_TRANSACTION
s. If a table function is part of an autonomous transaction, it must COMMIT
or ROLLBACK
before each PIPE ROW
statement, to avoid an error in the calling subprogram.
13.3.6 Implementing the Interface Approach
To use the interface approach, you must define an implementation type that implements the ODCITable
interface. This interface consists of start, fetch, and close routines whose signatures are specified by Oracle and which you implement as methods of the type.
Oracle invokes the methods to perform the following steps in the execution of a query that contains a table function:
- Start by initializing the scan context parameter, using the
function. - Fetch to produce a subset of the rows in the result collection. The
method is invoked as many times as necessary to return the entire collection. - Close and clean up (release memory and so on) using
after the lastODCITableFetch()
The ODCITable
interface also defines two optional routines, ODCITablePrepare()
and ODCITableDescribe()
, that are invoked at compilation time:
determines the structure of the data type the table function returns, in situations where this cannot be defined in a static manner. -
initializes the scan context parameter. If this method is implemented, the scan context it prepares is passed to theODCITableStart()
routine, and the context is maintained between restarts of the table function. It also provides projection information and supports the return of transient anonymous types.
18.104.22.168 Scan Context
For the fetch method to produce the next set of rows, a table function must be able to maintain context between successive invocations of the interface routines to fetch another set of rows. This context, called the scan context, is defined by the attributes of the implementation type. A table function preserves the scan context by modeling it in an object instance of the implementation type.
184.108.40.206 Start Routine
The start routine ODCITableStart()
is the first routine that is invoked to begin retrieving rows from a table function. This routine typically performs the setup needed for the scan, creating the scan context (as an object instance sctx
) and returning it to Oracle. However, if ODCITablePrepare()
is implemented, it creates the scan context, which is then passed to the ODCITableStart()
routine. The arguments to the table function, specified by the user in the SELECT
statement, are passed in as parameters to this routine.
Note that any REF CURSOR
arguments of a table function must be declared as SYS_REFCURSOR
type in the declaration of the ODCITableStart()
. Ordinary REF CURSOR
types cannot be used as formal argument types in ODCITableStart()
. Ordinary REF CURSOR
types can only be declared in a package, and types defined in a package cannot be used as formal argument types in a type method. To use a REF CURSOR
type in ODCITableStart()
, you must use the system-defined SYS_REFCURSOR
See Also:
188.8.131.52 Fetch Routine
The fetch routine ODCITableFetch()
is invoked one or more times by Oracle to retrieve all the rows in the table function's result set. The scan context is passed in as a parameter. This routine returns the next subset of one or more rows.
The fetch routine is called by Oracle repeatedly until all the rows have been returned by the table function. Returning more rows in each invocation of ODCITableFetch()
reduces the number of fetch calls that must be made and thus improves performance. The table function should return a null collection to indicate that all rows have been returned.
The nrows
parameter indicates the number of rows that are required to satisfy the current OCI call. For example, if the current OCI call is an ODCITableFetch()
that requested 100
rows, and 20
rows have been returned, then the nrows
parameter is equal to 80
. The fetch function is allowed to return a different number of rows. The main purpose of this parameter is to prevent ODCITableFetch()
from returning more rows than actually required. If ODCITableFetch()
returns more rows than the value of this parameter, the rows are cached and returned in subsequent ODCITableFetch()
calls, or they are discarded if the OCI statement handle is closed before they are all fetched.
See Also:
220.127.116.11 Close Routine
The close routine ODCITableClose() is invoked by Oracle after the last fetch invocation. The scan context is passed in as a parameter. This routine performs the necessary cleanup operations.
Figure 13-3 Flowchart of Table Function Row Source Execution
Description of "Figure 13-3 Flowchart of Table Function Row Source Execution"
22.214.171.124 Describing Returned Data Sructures; Describe Method
Sometimes it is not possible to define the structure of the return type from the table function statically. If the shape of the rows is different in different queries, it may depend on the actual arguments with which the table function is invoked. Such table functions can be declared to return AnyDataSet
. AnyDataSet
is a generic collection type. It can be used to model any collection (of any element type) and has an associated set of APIs (both PL/SQL and C) that enable you to construct AnyDataSet
instances and access the elements.
The following example shows a table function declared to return an AnyDataSet
collection whose structure is not fixed at function creation time:
CREATE FUNCTION AnyDocuments(VARCHAR2) RETURN ANYDATASET PIPELINED USING DocumentMethods;
You can implement a ODCITableDescribe()
routine to determine the format of the elements in the result collection when the format depends on the actual parameters to the table function. ODCITableDescribe()
is invoked by Oracle at query compilation time to retrieve the specific type information. Typically, the routine uses the user arguments to determine the shape of the return rows. The format of elements in the returned collection is conveyed to Oracle by returning an instance of AnyType
The AnyType
instance specifies the actual structure of the returned rows of the specific query. Like AnyDataSet
, AnyType
has an associated set of PL/SQL and C interfaces with which to construct and access the metadata information.
The query in Example 13-11, for an AnyDocuments
function, returns information on either books or magazines.
Example 13-12 is an implementation of the ODCITableDescribe()
method, which consults the DTD of the XML documents at the specified location to return the appropriate AnyType
value, either a book or a magazine. The AnyType
instance is constructed by invoking the constructor APIs with the field name and data type information.
When Oracle invokes ODCITableDescribe()
, it uses the type information that is returned in the AnyType
argument to resolve references in the command line, such as the reference to the x.Abstract
attribute in Example 13-12. This functionality is applicable only when the returned type is a named type, and therefore has named attributes.
Another feature of ODCITableDescribe()
is its ability to describe SELECT
list parameters, such as using OCI interfaces, when executing a SELECT *
query. The information retrieved reflects one SELECT
list item for each top-level attribute of the type returned by ODCITableDescribe()
Because the ODCITableDescribe()
method is called at compile time, the table function should have at least one argument that has a value at compile time, like a constant. By using the table function with different arguments, you can get different return types from the function, as demonstrated in Example 13-13.
The ODCITableDescribe()
functionality is available only if the table function is implemented using the interface approach. A native PL/SQL implementation of a table function that returns ANYDATASET
returns rows whose structure is opaque to the server.
Example 13-11 Querying for AnyType Data
SELECT * FROM TABLE(AnyDocuments('http://.../documents.xml')) x WHERE x.Abstract like '%internet%';
Example 13-12 Implementing the ODCITableDescribe() Method
CREATE TYPE Mag_t AS OBJECT ( name VARCHAR2(100), publisher VARCHAR2(30), abstract VARCHAR2(1000) ); STATIC FUNCTION ODCITableDescribe(rtype OUT ANYTYPE, url VARCHAR2) IS BEGIN Contact specified web server and retrieve document... Check XML doc schema to determine if books or mags... IF books THEN rtype=AnyType.AnyTypeGetPersistent('SYS','BOOK_T'); ELSE rtype=AnyType.AnyTypeGetPersistent('SYS','MAG_T'); END IF; END;
Example 13-13 Using Functions that Return AnyType
-- Issue a query for books SELECT x.Name, x.Author FROM TABLE(AnyDocuments('Books.xml')) x; -- Issue a query for magazines SELECT x.Name, x.Publisher FROM TABLE(AnyDocuments('Magazines.xml')) x;
See Also:
"Transient and Generic Types" for information on
220.127.116.11 Preparing a Query for Execution; Prepare Method
is invoked at query compilation time. It generates and saves information to decrease the execution time of the query.
If you do not implement ODCITablePrepare()
, ODCITableStart()
initializes the context each time it is called. However, if you do implement ODCITablePrepare()
, it initializes the scan context, which is passed to the ODCITableStart()
when the query is executed, reducing startup time. In addition, when ODCITablePrepare()
is implemented, ODCITableClose()
is called only one time during the query, rather than each time the table function is restarted. This has the following benefits:
It decreases execution time by reducing the number of calls to
. -
It allows the scan context to be maintained between table function restarts.
also provides projection information to the table function. If you do not implement ODCITablePrepare()
for table functions that return collections of user-defined types (UDTs), your table function must set every attribute of the UDT of each element, because it has no way of knowing which attributes are used. In contrast, selecting from a regular table fetches only the required columns, which is naturally faster in most cases. However, if you do implement ODCITablePrepare()
, it can build an array of attribute positions, record the return type information in an argument of type ODCITabFuncInfo
, and save this information in the scan context, as described in Example 13-14.
Implementing ODCITablePrepare()
also allows your table function to return transient anonymous types. ODCITablePrepare()
is called at the end of query compilation, so it can be passed the table descriptor object (TDO) built by the describe method. The describe method can build and return a transient anonymous TDO. Oracle transforms this TDO so that it can be used during query execution, and passes the transformed TDO to the prepare method in the RetType
attribute. If the describe method returns a TDO for a type that is not anonymous, that TDO is identical to the transformed TDO. Thus, if a table function returns:
A named collection type, the
attribute contains the TDO of this type. -
, and the describe method returns a named type, theRetType
attribute contains the TDO of the named type. -
, and the describe method returns an anonymous type, Oracle transforms this type, andRetType
contains the transformed TDO.
Example 13-14 Building an Array of Attribute Positions and Save it in a Scan Context
CREATE TYPE SYS.ODCITabFuncInto AS OBJECT ( Attrs SYS.ODCINumberList, RetType SYS.AnyType );
13.3.7 Querying Table Functions
Pipelined table functions are used in the FROM
clause of SELECT
statements independently from implementation, either in native PL/SQL or through the interface approach. The result rows are retrieved by Oracle iteratively from the table function implementation, as demonstrated in Example 13-15.
However, if the output of a table function is determined solely by the values passed into it as arguments, such that the function always produces exactly the same result value for each respective combination of values passed in, you can declare the function DETERMINISTIC
, and Oracle automatically buffers rows for it. Note, though, that the database has no way of knowing whether a function marked DETERMINISTIC
really is DETERMINISTIC
, and if one is not, results are unpredictable.
Example 13-15 Using a Table Function to Iteratively Retrieve Rows
SELECT x.Ticker, x.Price FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable))) x WHERE x.PriceType='C';
18.104.22.168 Implementing Multiple Calls to Table Functions
Multiple invocations of a table function, either within the same query or in separate queries result in multiple executions of the underlying implementation. That is, in general, there is no buffering or reuse of rows, as demonstrated in Example 13-16.
Example 13-16 Using Multiple Invocations of a Table Function
SELECT * FROM TABLE(f(...)) t1, TABLE(f(...)) t2 WHERE t1.id = t2.id; SELECT * FROM TABLE(f()); SELECT * FROM TABLE(f());
188.8.131.52 Using PL/SQL REF CURSOR Variables
variables can be defined for queries over table functions, as demonstrated in Example 13-17.
Cursors over table functions have the same fetch semantics as ordinary cursors. REF CURSOR
assignments based on table functions do not have special semantics.
However, the SQL optimizer does not optimize across PL/SQL statements; therefore, Example 13-19 runs better than Example 13-18.
Additionally, Example 13-18 is slower because of the overhead associated with executing two SQL statements, and because it does not take advantage of efficiencies realized by pipelining results between two functions, as Example 13-19 does.
Example 13-17 Defining REF CURSOR Variables for Table Function Queries
OPEN c FOR SELECT * FROM TABLE(f(...));
Example 13-18 Using a REF CURSOR Variable
BEGIN OPEN r FOR SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab))); SELECT * BULK COLLECT INTO rec_tab FROM TABLE(g(r)); END;
Example 13-19 Using a REF CURSOR Variable More Effectively
SELECT * FROM TABLE(g(CURSOR(SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab))))));
13.3.8 Performing DML Operations Inside Table Functions
A table function must be declared with the autonomous transaction pragma in order for the function to execute DML statements. This pragma causes the function to execute in an autonomous transaction not shared by other processes, as demonstrated in Example 13-20.
During parallel execution, each instance of the table function creates an independent transaction.
Example 13-20 Declaring a Table Function with Autonomous Transaction Pragma
CREATE FUNCTION f(p SYS_REFCURSOR) return CollType PIPELINED IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN ... END;
13.3.9 Performing DML Operations on Table Functions
Table functions cannot be the target table in UPDATE
, or DELETE
statements. For example, the following statements raise an error:
UPDATE F(CURSOR(SELECT * FROM tab)) SET col = value; INSERT INTO f(...) VALUES ('any', 'thing');
However, you can create a view over a table function and use INSTEAD OF
triggers to update it, as in Example 13-21.
Example 13-22 demonstrates how an INSTEAD OF
trigger is fired when the user inserts a row into the BookTable
triggers can be defined for all DML operations on a view built on a table function.
Example 13-21 Creating a View over a Table
CREATE VIEW BookTable AS SELECT x.Name, x.Author FROM TABLE(GetBooks('data.txt')) x;
Example 13-22 How an INSTEAD OF Trigger is Fired when a Row is Inserted into a View
CREATE TRIGGER BookTable_insert INSTEAD OF INSERT ON BookTable REFERENCING NEW AS n FOR EACH ROW BEGIN ... END; INSERT INTO BookTable VALUES (...);
13.3.10 Handling Exceptions in Table Functions
Exception handling in table functions works just as it does with ordinary user-defined functions.
Some languages, such as C and Java, provide a mechanism for user-supplied exception handling. If an exception raised within a table function is handled, the table function executes the exception handler and continues processing. Exiting the exception handler takes control to the enclosing scope. If the exception is cleared, execution proceeds normally.
An unhandled exception in a table function causes the parent transaction to roll back.
13.4 Parallel Table Functions
For a table function to be executed in parallel, it must have a partitioned input parameter. Parallelism is turned on for a table function if, and only if, both the following conditions are met:
The function has a
clause in its declaration. -
Exactly one
is specified with aPARTITION BY
clause.If the
clause is not specified for any inputREF CURSOR
as part of thePARALLEL_ENABLE
clause, the SQL compiler cannot determine how to partition the data correctly.
13.4.1 Inputting Data with Cursor Variables
You can pass a set of rows to a PL/SQL function in a REF CURSOR
parameter, as demonstrated in Example 13-23.
Results of a subquery can be passed to a function directly, as demonstrated in Example 13-24. The CURSOR
keyword is required to indicate that the results of a subquery should be passed as a REF CURSOR
Example 13-23 Passing a Set of Rows to a PL/SQL Function in a REF CURSOR
FUNCTION f(p1 IN SYS_REFCURSOR) RETURN ... ;
Example 13-24 Directly Passing Results from a Subquery to a Function
SELECT * FROM TABLE(f(CURSOR(SELECT empno FROM tab)));
220.127.116.11 Using Multiple REF CURSOR Input Variables
PL/SQL functions can accept multiple REF CURSOR
input variables, as demonstrated in Example 13-25.
Function g
can be invoked as demonstrated in Example 13-26.
You can pass table function return values to other table functions by creating a REF CURSOR
that iterates over the returned data, as demonstrated in Example 13-27.
Example 13-25 Passing a Set of Rows to a PL/SQL Function Through REF CURSOR
CREATE FUNCTION g(p1 pkg.refcur_t1, p2 pkg.refcur_t2) RETURN... PIPELINED ... ;
Example 13-26 Invoking a Function that Uses Several REF CURSOR Parameters
SELECT * FROM TABLE(g(CURSOR(SELECT empno FROM tab), CURSOR(SELECT * FROM emp));
Example 13-27 Using REF CURSOR to Pass Return Values Between Table Functions
SELECT * FROM TABLE(f(CURSOR(SELECT * FROM TABLE(g(...)))));
188.8.131.52 Explicitly Opening a REF CURSOR for a Query
You can explicitly open a REF CURSOR
for a query and pass it as a parameter to a table function, as demonstrated in Example 13-28.
Example 13-28 Explicitly Using a Query REF CURSOR as Table Function Parameter
BEGIN OPEN r FOR SELECT * FROM TABLE(f(...)); -- Must return a single row result set. SELECT * INTO rec FROM TABLE(g(r)); END;
126.96.36.199 PL/SQL REF CURSOR Arguments to Java and C/C++ Functions
Parallel and pipelined table functions may be written in C/C++, Java, or PL/SQL. Unlike PL/SQL, C/C++ and Java do not support the REF CURSOR
type, but you can still pass a REF CURSOR
argument to C/C++ and Java functions.
If a table function is implemented as a C callout, then an IN REF CURSOR
argument passed to the callout is automatically available as an executed OCI statement handle. You can use this handle like any other executed statement handle.
argument to a callout passed as an IN OUT
parameter is converted to an executed statement handle on the way in to the callout, and the statement handle is converted back to a REF CURSOR
on the way out. (The inbound and outbound statement handles may be different.)
If a REF CURSOR
type is used as an OUT
argument, or a return type to a call, then the call must return the statement handle, which are converted to a REF CURSOR
for the caller, as demonstrated in Example 13-28.
If the function is written as a Java call, the IN REF CURSOR
argument is automatically converted to an instance of the Java ResultSet
class. The IN REF CURSOR
to ResultSet
mapping is available only if you use a fat JDBC driver based on OCI. This mapping is not available for a thin JDBC driver. As with an executed statement handle in a C call, when a REF CURSOR
is either an IN OUT
argument, an OUT
argument, or a return type for the function, a Java ResultSet
is converted back to a PL/SQL REF CURSOR
on its way out to the caller.
A predefined weak REF CURSOR
type, SYS_REFCURSOR
, is also supported. With SYS_REFCURSOR
, you do not have to first create a REF CURSOR
type in a package before you can use it. This weak REF CURSOR
type can be used in the ODCITableStart() method, which, as a type method, cannot accept a package type.
To use a strong REF CURSOR
type, you still must create a PL/SQL package and declare a strong REF CURSOR
type in it. Also, if you are using a strong REF CURSOR
type as an argument to a table function, then the actual type of the REF CURSOR
argument must match the column type, or an error is generated.
To partition a weak REF CURSOR
argument, you must partition by ANY
, because a weak REF CURSOR
argument cannot be partitioned by RANGE
or HASH
. Oracle recommends that you not use weak REF CURSOR
arguments to table functions.
Example 13-29 Using a REF CURSOR in a Call
CREATE OR replace PACKAGE p1 AS TYPE rc IS REF cursor; END; CREATE OR REPLACE LIBRARY MYLIB AS 'mylib.so'; CREATE OR REPLACE FUNCTION MyCallout (stmthp p1.rc) RETURN binary_integer AS LANGUAGE C LIBRARY MYLIB WITH CONTEXT PARAMETERS (context, stmthp ocirefcursor, RETURN sb4); sb4 MyCallout (OCIExtProcContext *ctx, OCIStmt ** stmthp) OCIEnv *envhp; /* env. handle */ OCISvcCtx *svchp; /* service handle */ OCIError *errhp; /* error handle */ OCISession *usrhp; /* user handle */ int errnum = 29400; /* choose some oracle error number */ char errmsg[512]; /* error message buffer */ size_t errmsglen; /* Length of error message */ OCIDefine *defn1p = (OCIDefine *) 0; OCINumber *val=(OCINumber *)0; OCINumber *rval = (OCINumber *)0; sword status = 0; double num=0; val = (OCINumber*) OCIExtProcAllocCallMemory(ctx, sizeof(OCINumber)); /* Get OCI handles */ if (GetHandles(ctx, &envhp, &svchp, &errhp, &usrhp,&rval)) return -1; /* Define the fetch buffer */ psdro_checkerr(NULL, errhp, OCIDefineByPos(*stmthp, &defn1p, errhp, (ub4) 1, (dvoid *) &num, (sb4) sizeof(num), SQLT_FLT, (dvoid *) 0, (ub2 *)0, (ub2 *)0, (ub4) OCI_DEFAULT)); /* Fetch loop */ while ((status = OCIStmtFetch(*stmthp, errhp, (ub4) 1, (ub4) OCI_FETCH_NEXT, (ub4) OCI_DEFAULT)) == OCI_SUCCESS || status == OCI_SUCCESS_WITH_INFO) { printf("val=%lf\n",num); } return 0; }
13.4.2 Input Data Partitioning
The table function declaration can specify data partitioning for exactly one REF CURSOR
parameter, as demonstrated in Example 13-30. The PARTITION BY
phrase in the PARALLEL_ENABLE
clause specifies which one of the input cursors to partition, and what columns to use for partitioning.
When explicit column names are specified in the column list, the partitioning method may be either RANGE
or HASH
. The input rows are hash- or range-partitioned on the specified columns.
The ANY
keyword enables you to indicate that the function behavior is independent of the partitioning of the input data. When this keyword is used, the run-time system randomly partitions the data among the slaves. This keyword is appropriate for use with functions that take in one row, manipulate its columns, and generate output row(s) based on the columns of this row only.
To demonstrate, the pivot-like function StockPivot()
in Example 13-31 takes as input a row of the type (Ticker varchar(4), OpenPrice number, ClosePrice number)
, and generates rows of the type (Ticker varchar(4), PriceType varchar(1), Price number)
. Thus, the row ("ORCL", 41, 42
) generates two rows: ("ORCL", "O", 41
) and ("ORCL", "C", 42
You can use directly TickerTypeSet
, as created in Example 13-5, instead of rec_tab_type
or define it the same way.
The function f()
may be used to generate another table from Stocks
table, as shown in Example 13-32.
If StockTable
is scanned in parallel and partitioned on OpenPrice
, then the function StockPivot()
is combined with the data-flow operator that scans StockTable
and therefore sees the same partitioning.
If StockTable
is not partitioned, and the scan on it does not execute in parallel, the insert into AlternateStockTable
also runs sequentially, as demonstrated in Example 13-33.
If function g()
runs in parallel and is partitioned by ANY
, then the parallel insert can belong in the same data-flow operator as g()
Whenever the ANY
keyword is specified, the data is partitioned randomly among the slaves. This effectively means that the function is executed in the same slave set which does the scan associated with the input parameter.
No redistribution or repartitioning of the data is required here. In the case, when the cursor p
itself is not parallel-enabled, the incoming data is randomly partitioned on the columns in the column list. The round-robin table queue is used for this partitioning.
If you create an XMLIndex
index on a partitioned XMLType
table, or a partitioned table with an XMLType
column using range, list, or hash partitioning, that index is equipartitioned with the base table.
Example 13-30 Specifying Data Partitioning for a REF CURSOR Parameter
CREATE FUNCTION f(p ref_cursor_type) RETURN rec_tab_type PIPELINED PARALLEL_ENABLE(PARTITION p BY [{HASH | RANGE} (column_list) | ANY ]) IS BEGIN ... END;
Example 13-31 Implementing the StockPivot() Function
CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN rec_tab_type PIPELINED PARALLEL_ENABLE(PARTITION p BY ANY) IS ret_rec rec_type; BEGIN FOR rec IN p LOOP ret_rec.Ticker := rec.Ticker; ret_rec.PriceType := "O"; ret_rec.Price := rec.OpenPrice; PIPE ROW(ret_rec); ret_rec.Ticker := rec.Ticker; -- Redundant; not required ret_rec.PriceType := "C"; ret_rec.Price := rec.ClosePrice; PIPE ROW ret_rec; END LOOP; RETURN; END;
Example 13-32 Using a REF CURSOR to Generate a Table from Another Table
INSERT INTO AlternateStockTable SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));
Example 13-33 Using a REF CURSOR to Scan and Insert
CREATE FUNCTION g(p refcur_pkg.refcur_t) RETURN ... PIPELINED
PARALLEL_ENABLE (PARTITION p BY ANY)
INSERT INTO AlternateStockTable
SELECT * FROM TABLE(f(CURSOR(SELECT * FROM Stocks))), TABLE(g(CURSOR( ... )))
WHERE join_condition;
13.4.3 Parallel Execution of Leaf-Level Table Functions
Parallel execution of a table function requires a REF
as an input parameter. If the table function does not inherently require a set of rows as input, then you must redesign the function to require a REF
as input.
For example, the following function reads a set of external files in parallel and returns the records they contain. To provide work for a REF CURSOR
, you might first create a table and populate it with the filenames. A REF CURSOR
over this table can then be passed as a parameter to the table function readfiles()
, as demonstrated by Example 13-34.
Example 13-34 Using a REF CURSOR to Read a Set of External FIles
CREATE TABLE filetab(filename VARCHAR(20)); INSERT INTO filetab VALUES('file0'); INSERT INTO filetab VALUES('file1'); ... INSERT INTO filetab VALUES('fileN'); SELECT * FROM TABLE(readfiles(CURSOR(SELECT filename FROM filetab))); CREATE FUNCTION readfiles(p pkg.rc_t) RETURN coll_type PARALLEL_ENABLE(PARTITION p BY ANY) IS ret_rec rec_type; BEGIN FOR rec IN p LOOP done := FALSE; WHILE (done = FALSE) LOOP done := readfilerecord(rec.filename, ret_rec); PIPE ROW(ret_rec); END LOOP; END LOOP; RETURN; END;
13.5 Input Data Streaming for Table Functions
Data streaming is the manner in which a table function orders or clusters rows that it fetches from cursor arguments. A function can stream its input data in any of the following ways:
Place no restriction on the ordering of the incoming rows.
Order them on a particular key column or columns.
Cluster them on a particular key.
To control the behavior of the input stream, use the syntax in Example 13-36.
Input streaming may be specified for either sequential or parallel execution of a function. If an ORDER BY
or CLUSTER BY
clause is not specified, rows are input in a random order. The semantics of ORDER BY
are different for parallel execution from the semantics of the ORDER BY
clause in a SQL statement. In a SQL statement, the ORDER BY
clause globally orders the entire data set. In a table function, the ORDER BY
clause orders the respective rows local to each instance of the table function running on a slave.
Clustering causes rows that have the same key values to appear next to one another, but it does not otherwise do any ordering of rows.
should be the REFCURSOR
received in the function.
Example 13-35 illustrates the syntax for ordering the input stream. In the example, function f()
takes in rows of the kind (Region, Sales)
and returns rows of the form (Region, AvgSales)
, showing average sales for each region.
13.5.1 Setting up the Input Stream
Example 13-35 Ordering the Input Stream
CREATE FUNCTION f(p ref_cursor_type) RETURN tab_rec_type PIPELINED
CLUSTER p BY (Region)
PARALLEL_ENABLE(PARTITION p BY HASH(Region)) IS
ret_rec rec_type;
cnt number;
sum number;
FETCH p INTO rec;
EXIT WHEN p%NOTFOUND;
IF (first rec in the group) THEN
cnt := 1;
sum := rec.Sales;
ELSIF (last rec in the group) THEN
IF (cnt <> 0) THEN
ret_rec.Region := rec.Region;
ret_rec.AvgSales := sum/cnt;
PIPE ROW(ret_rec);
cnt := cnt + 1;
sum := sum + rec.Sales;
Example 13-36 Controlling Input Data Streaming
FUNCTION f(p ref_cursor_type) RETURN tab_rec_type [PIPELINED] {[ORDER | CLUSTER] expr BY column_list} PARALLEL_ENABLE({PARTITION p BY [ANY | {HASH | RANGE} column_list]} ) IS BEGIN ... END;
13.5.2 Parallel Execution: Partitioning and Clustering
Partitioning and clustering are easily confused, but they do different things. Sometimes partitioning can be sufficient without clustering in parallel execution.
Consider a function SmallAggr
that performs in-memory aggregation of salary for each department_id
, where department_id
can be either 1
, 2
, or 3
. The input rows to the function can be partitioned by HASH
on department_id
so that all rows with department_id
equal to 1
go to one slave, all rows with department_id
equal to 2
go to another slave, and so on.
The input rows do not have to be clustered on department_id
to perform the aggregation in the function. Each slave could have a 1
by 3
array SmallSum[1..3]
, in which the aggregate sum for each department_id
is added in memory into SmallSum[department_id]
. On the other hand, if the number of unique values of department_id
were very large, you would want to use clustering to compute department aggregates and write them to disk one department_id
at a time.
13.6 Creating Domain Indexes in Parallel
Creating a domain index can be a lengthy process because of the large amount of data that a domain index typically handles. You can exploit the parallel-processing capabilities of table functions to alleviate this bottleneck by using table functions to create domain indexes in parallel.
Typically, the ODCIIndexCreate()
routine performs the following steps:
Creates tables for storing the index data
Fetches the relevant data, such as
, from the base table, transforms it, and inserts relevant transformed data into the table created for storing the index data. -
Builds secondary indexes on the tables that store the index data, for faster access at query time.
Step 2 is the bottleneck in creating domain indexes. You can speed up this step by encapsulating these operations in a parallel table function and invoking the function from the ODCIIndexCreate()
function. In Example 13-37, a table function IndexLoad()
is defined to do just that.
where p
is a cursor of the form:
SELECT /*+ PARALLEL (base_table, par_degree) */ keycols ,rowid FROM base_table
The par_degree
value can be explicitly specified; otherwise, it is derived from the parallel degree of the base table.
The function IndexMerge()
, defined in Example 13-38, is needed to merge the results from the several instances of IndexLoad()
The new steps in ODCIIndexCreate()
would be:
Create metadata structures for the index (tables to store the index data).
Explicitly commit the transaction so that the
function can access the committed data. -
in parallel, as shown in the following code example.Invoking the Merging of Parallel Domain Index Loads
status := ODCIIndexMerge(CURSOR( SELECT * FROM TABLE(ODCIIndexLoad(ia, parms, CURSOR( SELECT key_cols, ROWID FROM basetable)))))
Create secondary index structures.
See Also:
13.6.1 Loading Domain Indexes
The formation of secondary indexes is very time consuming, and Oracle recommends that you implement parallel loading of domain index, as illustrated in the "Loading a Domain Index in Parallel" section, and subsequently recombine them as shown in "Merging the Results from Parallel Domain Index Loads" example.
Example 13-37 Loading a Domain Index in Parallel
CREATE FUNCTION IndexLoad(ia ODCIIndexInfo, parms VARCHAR2, p refcur-type) RETURN status_code_type PARALLEL_ENABLE(PARTITION p BY ANY) PRAGMA AUTONOMOUS_TRANSACTION IS BEGIN FOR rec IN p LOOP - process each rec and determine the index entry - derive name of index storage table from parameter ia - insert into table created in ODCIIndexCreate END LOOP; COMMIT; -- explicitly commit the autonomous txn RETURN ODCIConst.Success; END;
Example 13-38 Merging the Results from Parallel Domain Index Loads
CREATE FUNCTION IndexMerge(p refcur-type) RETURN NUMBER IS BEGIN FOR rec IN p LOOP IF (rec != ODCIConst.Success) RETURN Error; END LOOP; RETURN Success; END;
13.7 Transient and Generic Types
Oracle has three special SQL data types that enable you to dynamically encapsulate and access type descriptions, data instances, and sets of data instances of any other SQL type, including object and collection types. You can also use these three special types to create anonymous, or unnamed, types, including anonymous collection types. See Table 13-1.
The three SQL types are implemented as opaque types; the internal structure of these types is not known to the database: their data can be queried only by implementing functions, typically 3GL routines. Oracle provides both an OCI and a PL/SQL API for implementing such functions.
Table 13-1 Generic SQL Types
Type | Description |
A type description type. A An |
A self-describing data instance type. A |
A self-describing data set type. A |
Each of these three types can be used with any built-in type native to the database with object types and collection types, both named and unnamed. The types provide a generic way to work dynamically with type descriptions, lone instances, and sets of instances of other types. Using the APIs, you can create a transient ANYTYPE
description of any kind of type. Similarly, you can create or convert (cast) a data value of any SQL type to an ANYDATA
and can convert an ANYDATA
(back) to a SQL type. And similarly again with sets of values and ANYDATASET
The generic types simplify working with stored procedures. You can use the generic types to encapsulate descriptions and data of standard types and pass the encapsulated information into parameters of the generic types. In the body of the procedure, you can detail how to handle the encapsulated data and type descriptions of whatever type.
You can also store encapsulated data of a variety of underlying types in one table column of type ANYDATA
or ANYDATASET
. For example, you can use ANYDATA
with advanced queuing to model queues of heterogeneous types of data. You can query the data of the underlying data types like any other data.
Note, however, that ANYDATA
and ANYDATASET
objects that were created on a transient type, such as unnamed type constructed through an ANYTYPE
API, cannot be stored persistently in an ANYDATA
or ANYDATASET
table column.
Corresponding to the three generic SQL types are three OCI types that model them. Each has a set of functions for creating and accessing the respective type:
, corresponding toSYS.ANYTYPE
, corresponding toSYS.ANYDATA
, corresponding toSYS.ANYDATASET
See Also:
Oracle Call Interface Programmer's Guide for the
, andOCIAnyDataSet
APIs and details on how to use them -
Oracle Database PL/SQL Packages and Types Reference for information about the interfaces to the
, andANYDATASET
types and about theDBMS_TYPES
package, which defines constants for built-in and user-defined types, for use withANYTYPE
, andANYDATASET
Oracle Database Object-Relational Developer's Guide for definition of generic, transient, and opaque types.


text:
Detailed brass finding made in the USA and plated with high quality materials. Available in several finishes as well as raw form to be finished in your own creative way. Our regularly stocked finishes are listed below.
Our finishes:
Raw - Un-plated
Antique Grunge - Hand finished - Shades and coverage will vary
Brass Ox - Oxidation over raw brass - Shown in photo.
Gold Ox - 24 kt gold with black antique oxidation
Rose Gold Ox - Rose gold with oxidation
Silver Ox - 99.99% pure silver with oxidation
Verdigris Patina - Hand finished - shades and coverage will vary.
Material: brass
Qty: 2 pcs Raw - 1 pc Finished
Size: 30mm x 42mm

subset: commoncrawl


text:
SaltScapes™ Cell Cleaner
Look to a certified salt pool expert
A Certified Salt Pool Expert has been trained to understand exactly what is needed to keep a salt pool performing at peak condition. They also understand why you chose a salt pool in the first place and will work hard to maintain your satisfaction with that choice.
- Strong and highly effective formula
- Removes chalky white-calcium scale deposits from cell
- Works in as little as 15-30 minutes
- Easier to handle than other acid cell cleaners


text:
Pablo Cruise (Musical group)
Padilla, Julie (Playwright, actress)
Paiment, Nicole (Conductor) 3/16
Palmer, Jon Phillip (Playwright)
Pang, Chloe (Pianist)
Pansy Division (Musical group)
Parasol, Rykarda (Musician) 2/11
Parenti, Noel (Mime)
Paris (Rapper)
x Jackson, Oscar
Partch, Harry (Composer)
REF 731.55 G795s: 88-91
REF 780.2 ZZ65d: 347-372
S.F. Chronicle (Feb 10 1932): 9 Brief review of "Monophony" performance by A. Fried.
Pashley, Newton (Organist, choirmaster)
Pasqualetti, John (Choreographer)
xx Pacific Ballet (vf)
Dancemagazine (September 1976): 38-46
Passive Aggressives (Musical Group)
Pastiche (A cappella quartet)
Paterson, William (Stage actor)
Patten, Dr. Bebe Harrison (Radio broadcaster)
Patton Jr., John (Opera singer)
Paul, David (Record producer, mobile DJ, artist representative)
Pavement (Musical group)
Payton, James (Musician)
Pearl Harbor and The Explosions (Musical group)
x Gates, Pearl E.
x Harbour, Pearl
REF 784.5 ZB173n: 46
Pearson, Carol Lynn (Playwright) 3/10
Pebbles (Vocalist)
x McKissick, Perri
Pelton, Stephen (Dancer/Choreographer)
Penn, Sean (Actor, director)
Pense, Lydia
See: Cold Blood
Perrier, Denise (Jazz vocalist)
Persinger, Rolf (Violist)
Peter Bilt (Musical group)
x Bilt, Peter
Petersen, Donna (Vocalist, teacher)
Voice teacher at Mills College
Peterson, Wayne (Composer, pianist, teacher)
Petric, Faith (Folksinger)
See also: San Francisco Folk Music Club (vf)
Petrik, John (Musician)
Pfaff, Rose (Composer-artist)
Philadelphia, Billy (Pianist)
Pierce, Charles (Actor)
Pierszalowski, Van (Musician)
Pine Box Boys (Musical Group) 2/15
Pinska, Klarna (Choreographer)
See: Xoregos Dance Company (vf)
Dance (vf)
Pippin, Donald (Musician, director, conductor)
xx Old Spaghetti Factory (vf)
xx Pocket Opera
Pisoni, Larry (Clown)
Previous to 1989 see Pickle Family Circus
Pisoni, Lorenzo (Actor) 4/14
Pitt, Leonard (Mime)
Plank, Al (Jazz pianist)
Pleshakov, Vladimir (Pianist)
Plummer, Scotty (Banjoist)
Pointer Sisters (Pop, rhythm and blues vocalists)
Poligono, Linda (Pianist)
Polkacide (Musical group)
Polland, Anne Marie (Film costumer)
Polland, Pamela (Jazz vocalist, composer)
Pollard, Lisa (Jazz saxophonist)
Pontzious, Richard (Critic)
Portillo, Lourdes (Filmmaker)
Portman, Frank
Post, Alexander Commins (Organist, teacher)
Powell, Emmit (Vocalist, promoter, disc jockey)
Pride & Joy (Musical group)
Priester, Julian (Jazz trombonist, bandleader)
Primus (Musical group)
Pritchard, Sir John (Music director, conductor)
Pritchard, Mike (Comedian)
Prophet, Chuck (Singer/songwriter)
Psychefunkapus (Musical group)
Puente, Tito (King of Salsa)
x Cruz, Celia (Queen of Salsa)
Puerling, Gene
See: Hi Lo's
Purvis, Richard (Organist, composer, teacher)
See: Ackamoor, Idris


text:
The simple farming techniques that we call Agriculture 1.0 prevailed for millennia, until a succession of developments—ranging from seed genetics and fertilization to modern irrigation and mechanization—built the basis for Agriculture 2.0. Those advances spawned a productivity revolution that enabled countries such as Argentina to increase their tonnage by more than 7% each year since the late 1980s.
Enter Agriculture 3.0, with its state-of-the-art digital capabilities that represent the biggest advances in decades. Agriculture companies, including growers, equipment manufacturers, input providers and new technology-focused entrants, are turning to digital techniques for the next wave of yield and efficiency improvements. Sensor-connected crops, fields, machinery and livestock give farmers greater visibility into their operations, far surpassing previously available data collection methods. Software suites analyze the collected data and help farmers make better, faster decisions. Remote- or machine-controlled farm equipment allows for more precision operations and significant reductions in labor costs.
These and other new technologies arrive at a critical moment in history. Population and consumption growth in developing nations is expected to push crop demand nearly 50% higher than the 2010 level by 2050 (see Figure 1). The exploding demand for food will require a corresponding rise in the global supply produced, especially from developing nations like China, Brazil, Argentina and India—the key countries in the equation.
But can they meet the challenge? To feed the world, governments everywhere need to help growers make optimal use of their land and improve yield efficiency, leapfrogging into the wonders of Agriculture 3.0 (see the sidebar "Agriculture 3.0, explained"). Yet most countries still need to expand their best practices for Agriculture 2.0 before they can even think of deploying new techniques to bring their agriculture sectors to full potential. The challenge is particularly great in the developing countries of Latin America, Asia and Africa, where a limited understanding of what they could achieve prevents the different stakeholders from advancing together toward a more ambitious agenda.
Bain & Company research shows that the majority of countries unintentionally restrict themselves from creating substantial economic value from their agriculture in two fundamental ways.
First, many countries still manage their agriculture sectors based on tradition and empirical knowledge, rather than a comprehensive analytical approach. For example, it is common for countries to view each crop independently, focusing on maximizing the output for that specific crop, without determining the potential trade-offs of growing competing—and equally viable—crops on the same land. Indeed, regardless of a country's level of agricultural advancement, there's a crucial (and tricky) first step in the journey to full potential: clearly understanding the best options. Would a particular country derive more socioeconomic benefits by growing sugarcane on land now used for raising cattle, for example? Or would the country be better off if it instead used that land to grow cocoa trees and integrate downstream with the export chocolate business, even considering the five years typically required for cocoa to start generating cash flow?
The second issue: A lack of coordination among the many stakeholders—including local governments, sector agencies, farmers, NGOs and multinational organizations like the United Nations—can thwart efforts. All have their own agendas and, sometimes, conflicting goals. Consider how different stakeholders would likely react when a certain crop faces a cyclical loss of competitiveness. Farmers and trade associations might press for tactical protectionist measures, whereas a multinational organization might focus on building the structural foundations of fair trade by promoting true competition. Meanwhile, an NGO might be more interested in the social impact of diminished income through that crop.
Solving these fundamental problems starts when governments understand the broader strategic picture of full-potential agriculture and align that vision with private industry (see the sidebars "Why it's government's role" and "Getting started"). Regardless of the type of government, several elements must be in place before the sector can progress from Agriculture 1.0 to Agriculture 3.0. Those elements include development of logistics infrastructure, appropriate financing and insurance tools, and market liquidity and access (avoiding bottlenecks through intermediaries that ultimately harm farmers), along with addressing the often thorny issue of land ownership. Among the examples of government actions that have helped advance agriculture: Brazil's ethanol program, which transitioned from heavy incentives in the 1970s to full market dynamics today.
In the absence of government and private-sector alignment, however, even the best intentions and efforts fail to deliver significant results. Consider that Brazil's annual tonnage increased by only 2.2% between 2009 and 2014. Chile was slightly more productive, with a 2.6% growth rate, while Colombia's tonnage grew by only 1.2%.
Working with governments around the world, Bain has found that the most effective countries systematically address these obstacles, stepping up to the challenge of feeding the world while improving their economies. For most countries, the size of the prize is as great as doubling agro-industrial GDP, coupled with substantial job creation. Our analysis shows that, in developing countries, a twofold increase in agro-industrial GDP can generate enough jobs to reduce total unemployment by 25%.
Achieving such benefits requires defining the particular agro-industrial strategy that will yield the best results. The first major step for any country is to complete an area prioritization exercise, using Big Data and evolving digital capabilities to evaluate the agro-ecological characteristics of fertile micro-segments of its land.1 Looking at plots as small as 2 hectares or as large as 200 hectares, it can determine the potential productivity of competing crops in fine detail. Next, it needs to factor in the socioeconomic value of each crop—not only what it contributes to employment but also to primary GDP (through the agricultural production itself) and potential downstream GDP (through the processing, refining and manufacturing associated with the crop), together with the profits it generates for the farmer. With that analysis in hand, a government can gauge the best use of land and the specific trade-offs between the options—essential for promoting a long-term agro-industrial agenda, defining the most appropriate incentive policies and engaging all stakeholders.
This approach optimizes the economic results for export-oriented countries, like those of Latin America, as well as countries aiming for self-sufficiency. It takes into account the mix of crops that would yield the largest economic benefit, including the trade-off between producing what you do best and importing what you need, or producing locally what you need in lieu of exports.
While we have worked with government clients throughout the world to advance their agricultural agendas, the experience of one Latin American country best illustrates how a government can use the method to obtain a clear view of its full potential.
Relying on agro-ecological information, this Latin American government ranked the potential yields (marginal, moderate or optimal) for crops such as cocoa, bananas, coffee and palm, covering more than 60,000 polygons of fertile land that wasn't part of natural reserves or parks, or otherwise protected (see Figures 2 and 3). With the help of Agriculture 3.0 techniques, it then took an important giant step: It compared the potential yields among those different crops to determine the winner in each of the polygons. Why is it so rare for governments to take the simple step of comparing the potentials for different crops? As we mentioned, most countries have a siloed approach to crop management, and, lacking tools and proper governance, there's little incentive for cooperation among teams tasked with different crops. As a result, countries tend to give minimal thought to the trade-offs among those crops.
As part of its systematic approach, the Latin American country not only considered how it could maximize producer margins but also broadened the view of each crop in each land area by evaluating the potential for downstream integration. For example, based on agricultural output and producer margins alone, bananas seemed like the best crop for many polygons. However, by analyzing the crop's value chain and the processes and products involved, the country discovered that bananas offered significantly less opportunity than cocoa for downstream development beyond the primary crop. While processing of bananas included a limited number of products such as sweets and flour, cocoa offered far more downstream opportunity for expanding production and activities in products and subproducts—everything from cocoa butter and industrial chocolate to cocoa shells and fertilizer (see Figure 4). In fact, the agro-industrial multiplier for cocoa was 2.50, more than twice the 1.05 level for bananas. The agro-industrial multiplier (agricultural GDP plus related industrial GDP, divided by agricultural GDP) provides a much broader and more realistic indication than the primary agricultural GDP of a crop's potential overall contribution.
Beyond this first result, a host of additional considerations came into play. In this case, the government factored in the opportunity cost of switching from one crop to another, taking into account the cost of eliminating the initial crop and planting the new one, along with the number of nonproductive years, which is especially critical for crops like cocoa and palm. In the four or five years it takes a new palm tree to start ramping up production, for example, the producer would miss the profits that an annual crop like corn would have generated. Palm may be more attractive in the long run, but it's an unrealistic option for a farmer who is either insufficiently capitalized or who can't obtain adequate financing to navigate through that cashless period. Other countries will have their own considerations to accommodate, such as the role of indigenous farming in the social fabric of a community, or environmental issues such as maintaining corridors for wildlife migration.
The Latin American government then looked at its findings in light of the country's overall economic priorities. It needed to make a choice. It compared the potential effects of each crop on the country's primary agricultural GDP, the related agro-industrial GDP, wages and producer margins. In the end, producer margins became the top criterion for making trade-offs: It was best for convincing farmers about proposed area redistributions, reassuring them that they would be able to take care of their own needs first.
This analysis provided a clear picture of the specific productivity, crop-switch and infrastructure initiatives (such as irrigation and roads) the country would need to implement, as well as how it could integrate the more advanced techniques of Agriculture 3.0. As a result, the government could develop a much stronger set of incentive policies tailored to farmers' specific challenges, enhancing their competitiveness while promoting the best outcome for the country.
Finally, the government needed to consider implementation challenges and find a way to prevent the false starts, stalls and duplication of efforts that come with weak governance. It laid out a strong governance structure involving both public and private stakeholders, with well-defined key performance indicators to track progress for all parties. Under the plan, parties spanning government ministries, private-sector representatives, trade associations, development agencies and NGOs come together from the strategy definition stage all the way to execution in the fields.
The Latin American country now has a solid plan in place that could double its agro-industrial GDP within the next 15 years, in our estimate (see Figure 5). By systematically bringing its crop productivity and downstream integration to full potential, it stands to become an unexpected agricultural leader, proving that tackling the daunting food challenge can yield major rewards: boosting employment by more than 200,000 jobs and increasing GDP, while delivering on the growing demand for food.
Agriculture 3.0, explained
When Agriculture 3.0 is fully available, with digital capabilities widely implemented and connected, producers will be able to increase yields and lower costs with a host of new technology solutions. It will look like this:
- Better and cheaper satellite mapping, enhanced by more precise crop identification and growth algorithms, allows for improved monitoring of crop status.
- Field sensors collect data on nutrient quality, moisture, weather and other factors that affect yields.
- Super-accurate yield maps determine precise seed and input prescriptions for each plot.
- Variable-rate seeders and applicators provide each plot with the optimal amount of inputs.
- Autonomous equipment navigates fields with limited or no oversight.
- Drones survey plots to monitor weeds, yield and soil quality.
- Real-time livestock data allows farmers to identify sick or in-heat animals.
- Farmers input data, view analyses and make decisions from mobile devices in the field.
- Data is stored in a central repository and in the cloud, where it can be accessed for analysis.
- Farmers access data from their home control centers, or from online portals offered by precision agriculture companies.
- Real-time data on machine performance helps predict and prevent maintenance issues, increasing equipment uptime.
Already, some producers are stepping into this future. For example, India's Chitale Dairy tracks its cows' location and status via radio-frequency identification, maintaining a database on the herd. But such digital capabilities will be commonplace on the farm only after three waves of innovation.
Wave 1 involves adopting point solutions that are commercially available today, such as GPS, automated steering, sprayer control, logistics, drones, satellites, custom application of inputs, and advanced water, weather and yield sensors.
Wave 2 will involve implementing decision support systems as they evolve, such as predictive maintenance, autonomous vehicles and drones, and real-time, multi-node data and analytics.
Wave 3 will require connected ecosystems: autonomous fleets; full-farm solutions with interconnected telematics; real-time, multi-node data and analytics; and automated real-time application of inputs.
Delivering the farm of the future means overcoming numerous challenges, everything from establishing regulations and standards for autonomous vehicles to allaying producer concerns about the security and ownership of sensitive farm data. Companies developing digital solutions also need to create trust with farmers, prove the value of technologies such as data-based growing and build an installed base to make their offerings profitable.
Why it's government's role
Farming is the world's oldest economic activity, so there's no shortage of professionals or institutions with an expert opinion on the agricultural chain. The problem is that, as in the ancient Indian parable of the seven blind people and the elephant, while each might be right about part of the solution, they are all wrong on the whole. And here lies the importance of governments playing the central developmental role in their respective countries.
Our experience shows that governments achieve the best results when they take the lead in understanding the bigger picture, create the framework for collaboration and use rigorous analysis to guide their decisions. In the most successful cases, governments establish a pro-business environment that allows the private sector to contribute the best possible intelligence on the status quo, and then actively participate in discussions involving where to play (which crops to plant and where) and how to win (what tools, programs and incentives to use). Governments then carry this collaborative approach through to implementation, communicating clearly about the path forward with all stakeholders to establish a convergence of purpose.
When a government fails to be proactive, it often ends up becoming either a roadblock to development or an unfocused protectionist machine that, from a socioeconomic perspective, ultimately creates more harm than good.
Getting started
To achieve full-potential agriculture, governments need to fight the fragmentation of responsibilities that we find in many countries by consolidating leadership for the entire value chain, from the primary crop activity to the industrial steps downstream. The governance structure should enable leaders to quickly make decisions and take action on key elements such as incentives, financing, infrastructure and education.
This setup not only promotes agility and effectiveness, it also sends a strong signal to the investment community, which will quickly recognize the value of a pro-business environment. By engaging the private sector, both in general and through crop-specific committees, governments can make companies their partners in a full-potential strategy.
With all this in place, governments can define the strategy. That entails:
- analyzing the status quo;
- strategically characterizing the various crop value chains from a global perspective;
- launching remedial data-gathering efforts to provide vital pieces of missing information;
- running various optimization scenarios;
- understanding the trade-offs and making the appropriate choices; and
- determining the enabling elements—that is, the incentives to put in place, the infrastructure to build, the financing to offer and the education to provide.
1 Agro-ecological characteristics include land slope, depth, texture, stoniness, pH, salinity, toxicity, fertility, drainage, precipitation, temperature, humidity, altitude, irrigation and others, the combination of which determines potential crop yields.
José de Sá is a partner in Bain & Company's São Paulo office and a member of the firm's Social & Public Sector practice. Frederic Declercq is a Bain partner based in São Paulo, and Dalton Maine and Fernando Martins are partners based in Chicago; all three are members of the Agribusiness sector of Bain's Industrial Goods & Services practice.


text:
Three-division, four-time champion Abner Mares has been forced to pull out of the WBA Super Featherweight title match against two-time super featherweight champion Gervonta Davis on Saturday, February 9 after suffering an injury while training.
Davis will now defend against former world champion Hugo Ruiz in the main event of SHOWTIME CHAMPIONSHIP BOXING, with coverage beginning at 10 p.m. ET/7 p.m. PT from Dignity Health Sports Park in Carson, California and presented by Premier Boxing Champions.
Ruiz is an all-action brawler who previously picked up a world title at super bantamweight in 2015 and most recently defeated Alberto Guevara in January on the Pacquiao vs. Broner PPV event in a featherweight fight. Ruiz, who owns 18 first round knockouts, floored Guevara in the opening minute and cruised to a near-shutout decision victory.
He had originally been slotted for an interim title shot against Jhack Tepora in January before Tepora missed weight, but will now have his chance to fight for a belt against one of the most-feared punchers at 130-pounds on February 9. The 30-year-old from Sinaloa, Mexico stands at five feet, nine inches tall, nearly four inches taller than Davis, and has won three straight fights leading up to this fight.
"I trained for three months to fight a southpaw when I fought in January," said Ruiz. "When the opponent changed, it was hard to adjust in 24 hours. After the fight I immediately went back to the gym, because you never know what's going to happen in boxing. I'm 100 percent ready to knockout Gervonta Davis."
"Hugo Ruiz has another opportunity to fight for a world title because he's demonstrated that he's devoted to boxing and his career," said Sampson Lewkowicz, Ruiz's Promoter. "He went right back to the gym after his last fight. He's ready for another great opportunity to be a world champion and he's going to take advantage of that opportunity."
Mares injured his right elbow during sparring at the Robert Garcia Boxing Gym in Riverside, Calif. Mares said he had experienced soreness in his elbow following the past two fights, but nothing that had ever given him nor his team concern.
"I'm not going to sugarcoat this. I'm disappointed and devastated that I've injured my right elbow in sparring,'' Mares said. "This is something that I haven't had happen to me during my career and the feeling is just horrible. I will be going to see my doctor about this and am prepared to follow whatever recommendations and rehab that gets me back into the ring. I'm healthy, not afraid of doing the work and I will be back. This is a temporary setback. Don't count me out.''
"Boxing is a sport of brains, passion and physical punishment," said Mares' trainer Robert Garcia. "When you're in the ring sparring with talented, top-class sparring partners, the chances of injuries are very high. That was the situation in this case. He was sparring top young fighters and he injured his elbow. He cannot fight like that, especially in a fight against Davis."
Mares, who has engaged in numerous memorable fights throughout his storied career, was seeking to move up in weight and looking to capture his fifth world title in a fourth weight class.


text:
Illustration of Law of Large Numbers
This example shows how to use MATLAB System blocks to illustrate the law of large numbers.
The law of large numbers states that the cumulative average probability of an event approaches the expected value as the number of trials approaches infinity. You can see this in the generated plot.
A Uniform Random Number block connected to Compare To Constant block acts as a Bernoulli trial. The example uses the high value as successful outcome of the experiment.
This example highlights the use of MATLAB System block as follows:
The Success Counter counts the number of successful trials. The Total Trials Counter counts the total number of trials. Both blocks use the same System object™. This System object is a source block that uses the "resetImpl" method to count the number of times the block has run at consecutive time steps.
The Analysis and Plot block calculates the average probability and plots it. It uses a System object that inherits from the interface classes matlab.System. The interface class matlab.System is required for all System objects. To use features not supported for code generation, such as unsupported functions, define propagator methods for the System object. As a result, this block cannot simulate in "Code generation" mode. It must simulate in "Interpreted execution" mode.
The two MATLAB System blocks use custom icons. The Analysis and Plot block uses an image file icon. The Total Trials Counter block uses a multiline text icon. The "getIconImpl" methods of the corresponding System objects define these custom icons.


text:
(visited by Jan-Hendrik May and Gary Correa Figueroa)
10-Jul-2005 --
Our visit started in Santa Cruz, a day earlier, and was part of a longer field work campaign in the Bolivian Chaco, where I had been doing research for my PhD in Geography (University of Bern).
Gary ("Recon-1) and I had gone by jeep, camping the night at "Aguas Feas", a beautiful seasonally flooded depression in the Bolivian Chaco. Next morning we departed towards the south where a small trail leads to an isolated cattle ranch. Following our GPS, we figured that our trail passed the confluence at only several hundred meters distance. Given the thorny shrubs of the Chaco, it took us half an hour to get to the confluence, and we probably wouldn't have made it without the good old machete.
Finally there, the view wasn't exactly breathtaking (see pictures), little space, some cacti, shrubs and at least some shadow... anyway, we had made our first confluence :-))


text:
While the entire world is going through this pandemic, different countries and regions are experiencing it differently. And we're going to talk now about what the situation looks like in a few different places - South Africa, Brazil and Israel and the Palestinian territories. Like the U.S., they are dealing with the more contagious delta variant. And while the vaccine is successful at preventing serious illness and death, many people can't get vaccinated. We're joined now by NPR's Daniel Estrin in Jerusalem, Eyder Peralta in Cape Town and Philip Reeves in Rio de Janeiro.
Good to have all three of you here.
PHILIP REEVES, BYLINE: Hi.
EYDER PERALTA, BYLINE: Hey, Ari.
DANIEL ESTRIN, BYLINE: Thank you.
SHAPIRO: To start with you, Phil, Brazil's reported the second most COVID deaths of any country right behind the U.S. The death toll there is above half a million. So what is happening in Brazil right now?
REEVES: Well, things are slightly better than they were - for example, back in April, when on one day, the Brazilians recorded more than 4,000 deaths in one day. Now the daily average is just over a thousand, but that's still high. New cases are going up, and there are a lot of them - more than 40,000 yesterday. So it's serious, especially as the vaccination program here is stumbling along and has, in some cities, had to be temporarily suspended because of logistical problems with supplies. And the delta variant is here. Not many cases at this point - 170 according to the health ministry - but it is here.
Brazilians are getting angry. They're getting angry about the way the government of President Jair Bolsonaro has responded in particular. You'll remember he was a denialist at the beginning of this thing. They're angry over the way he's bungled vaccine purchases, of allegations of government scams in vaccine purchases as well. And tens of thousands of them, four times in the last few months, have taken to the streets to protest.
SHAPIRO: Now, you say less than a quarter of Brazil's population has been vaccinated. In South Africa, the rates are even lower. Eyder, what are the trends there right now?
PERALTA: Look, right now we're seeing lots of deaths - 3,000 over the past week. But we are just coming out of a third wave that was driven by the delta variant. Just last week, the government lifted some of the toughest restrictions. But there are still reminders everywhere that this pandemic is ongoing. There's still a nighttime curfew here. Gatherings are restricted. Masks are mandated. Every public space seemingly is spraying you down as you come in with hand sanitizer, and they're taking your temperature. Luckily, the vaccination effort seems to be finding its footing, but it's clear that it's going to take a long time to get back to normal.
SHAPIRO: Daniel, you're in Jerusalem, and Israel was ahead of the curve on vaccines. What impact is the delta variant having there now?
ESTRIN: Well, cases are on the rise. Serious infections have more than doubled just in the last week. And that is happening even with a majority of Israel vaccinated. Though, we know that overall you're better protected with a vaccine than without. But the rising number of cases here is concerning, so now the rule is that everyone must wear a mask indoors. And then you have a million Israelis who are not vaccinated yet. And Israel is trying to pressure them to get the shots. So starting Thursday, if you're not vaccinated, you will not be allowed entry into events, into movie theaters, even houses of worship, unless you show a negative COVID test.
So that's in Israel. Then in the Israeli-occupied West Bank and in the Gaza Strip, the thing is is that vaccines are available, but most eligible Palestinians have not gone to get vaccinated. So the International Committee of the Red Cross has social media influencers spreading messages, trying to teach about the vaccine. Officials are even bringing vaccines to mosques.
REEVES: Daniel mentioned there Israel's vaccine success, and it's interesting because if you look at most of South America, you know, that stands in strong contrast to that. Most nations here are not very far ahead with their vaccination programs, but there are a couple of star performers - Chile and Uruguay. Chile is a particularly interesting case. Both these countries now have 63% or so of their populations fully vaccinated. But if you look at Chile, back in April, they had about a third of the population fully vaccinated, and they were getting international plaudits for their program. And yet 97% of their hospitals' intensive care units were occupied. The place was awash with new cases, and the health system was struggling and, in some cases, collapsed. Now, however, they are reaping the rewards. Yesterday, they recorded their lowest daily new infection rate since October. The number was well below 1,000.
SHAPIRO: Phil, you've described some of the challenges to vaccinating people in Brazil, but Eyder, tell us why it's been so difficult to get vaccination numbers up in South Africa and other parts of the continent.
PERALTA: Yeah, I mean, look, in South Africa, less than 5% of people are fully vaccinated. But here, this isn't a case of a country that has badly managed this pandemic. It's just the opposite. South Africa reacted very early and effectively, and they have tried to buy the vaccines. They've tried to make the vaccine. But rich countries have hoarded much of the supply, and there's just not enough for South Africa. But this seems to be changing. Big shipments of vaccines are starting to come in, and South African-made Johnson & Johnson vaccines have just started rolling out of a factory not far from where I am.
But again, talking about global pressures, even some of those vaccines that are being made here will have to be shipped to Europe. So the bottom line is that South Africa is a perfect example of just how hard it is to compete for these vaccines when you're a middle-income country. And there's great demand for vaccines here. Vaccination sites are mobbed. There are lines around the block right now of people who are waiting hours for their jab right now in many vaccination sites.
SHAPIRO: It's such a contrast with Israel, where the vast majority of the population has been vaccinated. And Daniel, I understand they're considering whether to offer booster shots.
ESTRIN: That's right - booster shots for the general population, starting with older people. They're already giving booster shots to people with weak immune systems. But this is posing a huge dilemma in Israel. You know, Israel was the first to vaccinate most of its population, but that means it's also the first facing this question of whether boosters are truly needed. It's been about seven months since the first Israelis were vaccinated with Pfizer, and some studies here suggest that the vaccine's protection has waned over time. And so Israel faces this alone. No other countries have rolled out booster shots on a wide scale. The data is still preliminary.
But leaders are under pressure here not to force more lockdowns, and so there's a lot of pressure to approve booster shots. And health officials are meeting tonight to discuss this. So I think when you look at Israel throughout the pandemic, it's been a case study. First, it was looked at as an example of vaccine disparity with Israelis getting shots early and Palestinians being very far behind. And now Israel could show us a glimpse into the future of what other countries, if they do manage to vaccinate their populations - what they'll face. It might not be enough.
SHAPIRO: That's NPR's Daniel Estrin in Jerusalem, Eyder Peralta in Cape Town and Philip Reeves in Rio de Janeiro.
Thanks to all three of you.
REEVES: You're welcome.
PERALTA: Thank you, Ari. Transcript provided by NPR, Copyright NPR.


text:
Two Cats in a Frying Pan Will Start Your Day Right
Breakfast, as they say, is the most important meal of the day. So why not start things off with a nice, healthy cat omelet? And look -- two of them have already climbed into a frying pan for you. We're kidding, of course. We don't condone eating cats. (Usually.)
In this video, Shironeko, who's better known as the "Zen Mastah" cat, chills in a frying pan with a furry friend named Mimi. And from the looks of it, Shironeko's zen mastery is fully intact, since he doesn't object at all to the younger cat invading his space.
In fact, the two lick each other and act impossibly cute as they cram themselves into the much-too-small frying pan. Shironeko doesn't even mind when Mimi begins to playfully swat at his tail. How does this mellow cat do it? If only we had his serenity.


text:
Western Digital Demonstrates 8TB Portable 20 Gbps SSD, Announces Dual-Connector 1TB Thumb Drive
by Ganesh T S on January 6, 2020 9:00 PM EST- Posted in
- Storage
- Western Digital
- SanDisk
- flash
- Trade Shows
- WD_Black
Western Digital has a history of showcasing cutting-edge technological advancements in the consumer electronics space at CES every year. In 2019, a 4TB thumb drive was demonstrated, and this year, we have a 8TB portable SSD with a USB 3.2 Gen 2x2 interface.
As QLC flash becomes more common-place, and the number of layers in 3D NAND keeps increasing, it becomes economically feasible to produce high-capacity compact flash storage devices. In fact, WD presented details of 128L BiCS 5 production wafers at ISSC 2019, though it is not clear if the 8TB prototype being demonstrated at CES 2020 utilizes BiCS 5 flash. In any case, given the size of the prototype (similar to the SanDisk Extreme Pro Portable SSDs already in the market), even BiCS 4 TLC (96L 3D NAND) flash is enough. The choice of TLC or QLC would boil down to economics, and given that this is a techonology demonstration prototype, there is no pricing aspect to consider.
WD also announced some products with retail availability at the show. The SanDisk Ultra Dual Drive Luxe USB Type-C is a 1TB thumb drive that comes with a Type-C male port and a Type-A female port. It is compact enough to fit in a keychain and will be available in late Q1 2020. Pricing has not been announced.
The WD_BLACK line of external storage solutions targeting gamers is also being showcased. The lineup includes the WD_BLACK P50 (one of the first external SSDs to feature a USB 3.2 Gen 2x2 20 Gbps interface), the WD_BLACK P10 (external bus-powered 2.5" hard drive in 1TB, 3TB, and 5TB capacities), and the WD_BLACK D10 (external 8TB 3.5" 7200 RPM hard drive with a USB 2.0 hub). The P10 and D10 also come in Xbox-branded variants.
The SanDisk ibi photo management device (a cloud-based service with local storage) is also being demonstrated in WD's CES suite.
12 Comments
View All Comments
Xajel - Tuesday, January 7, 2020 - link
"The SanDisk Ultra Dual Drive Luxe USB Type-C is a 1TB thumb drive that comes with a Type-C male port and a Type-A female port."
You mean "... and a Type-A male port." -
s.yu - Tuesday, January 7, 2020 - link
No, it's clear from the render that it's a female port, though I struggle to see the reasoning behind it. -
Xajel - Tuesday, January 7, 2020 - link
Nope, it's just too dark to see in that small picture..
Of course other pictures and information in the product page clearly indicates it's a male port.
The full size official picture
https://shop.westerndigital.com//content/dam/store... -
Santoval - Tuesday, January 7, 2020 - link
Your linked picture also shows (the same) female USB-A port. What's too dark exactly? That rendering is lit perfectly and there is no ambiguity. It's not small either. Did the article had a smaller and darker picture of the thumb drive earlier? -
ganeshts - Tuesday, January 7, 2020 - link
Apologies, guys. The picture does look like a female port, but, after looking at the earlier products in this line, it appears that it is indeed a male port. I have fixed the article text. -
TheSkullCaveIsADarkPlace - Tuesday, January 7, 2020 - link
You say there is no ambiguity in the picture? What the hell are you smoking? Have you ever seen a USB type-A receptacle (female) and plug (male)? From the persective provided by picture alone(!) it would be impossible to tell whether it is a receptacle or a plug. This is the very definition of ambiguity.
But, there is a rather easy way to resolve this ambiguity by simply considering what that thing in the picture is: a USB stick. Now, tell, what would make more sense on a USB stick: a plug (male) or a port/receptacle (female)? -
TheSkullCaveIsADarkPlace - Tuesday, January 7, 2020 - link
Okay. I retract my statement about ambiguity in the picture. I was dead wrong about that.. There is no ambiguity in the picture with regard to the USB type-A plug whatsoever. I agree in this respect with Santoval.
After some fiddling with the contrast levels, it becomes rather clear that it is a male plug. Yeah, while there is no amibuity, the picture unambiguously shows a male USB plug. No female receptacle in sight: https://imgur.com/suwk77h -
TheSkullCaveIsADarkPlace - Tuesday, January 7, 2020 - link
"Amibuity"? Fingers, why don't you listen to my brain? -
s.yu - Wednesday, January 8, 2020 - link
lol, I realize that it does make sense as a male port now, though it's practically impossible to tell without a female port side by side. -
PeachNCream - Tuesday, January 7, 2020 - link
I love that your comment inspired a string of nerd rage over port gender. People find some crazy stuff to get excited about.