sudo apt-get install gsm-utils sudo gsmctl -d /dev/ttyACM0 -o unlock sc all 1234here 1234 is actually your SIM Card PIN to be removed.
2011-10-30
How to remove SIM Card PIN from your GSM/UMTS modem on Ubuntu (Linux)
Posted by
valgog
at
13:03
10
comments
Labels: linux
2011-04-06
Index sizes depending on the type of the field being indexed
The sizes of indexes (and tables of cause) are influencing the look up speed directly (the smaller are the corresponding file system files, the faster one can scan it, not saying anything about the sizes, needed to be kept in memory caches)
So I did some simple experiment to demonstrate the effect of choosing different field types to be used as index fields to the size of the indexes and tables.
DROP DATABASE IF EXISTS eval;
CREATE DATABASE eval;
\c eval
*/
SET work_mem TO '128MB';
SET maintenance_work_mem TO '128MB';
DROP SCHEMA IF EXISTS eval_schema CASCADE;
CREATE SCHEMA eval_schema;
SET search_path to eval_schema;
CREATE TABLE eval_config ( table_name text, id_field_type text, id_field_expression text, row_count integer DEFAULT 100000 );
INSERT INTO eval_config VALUES
( 'integer_short_id_table', 'integer', 's.i' ),
( 'integer_large_id_table', 'integer', 's.i * 123' ),
( 'bigint_short_id_table', 'bigint', 's.i' ),
( 'bigint_large_id_table', 'bigint', 's.i * 123' ),
( 'text_number_short_id_table', 'text', 's.i' ),
( 'text_number_large_id_table', 'text', 's.i * 123' ),
( 'numeric_short_id_table', 'numeric', 's.i' ),
( 'numeric_large_id_table', 'numeric', 's.i * 123' ),
( 'binary_md5_text_table', 'bytea', $$decode( md5( s.i::text || '-text-filler'), 'hex' ) $$ ),
( 'md5_text_table', 'text', $$md5( s.i::text || '-text-filler' )$$ );
CREATE VIEW eval_table_stats AS
SELECT t.relname as "Table name",
c.id_field_type as "Indexed field type",
c.id_field_expression as "Expression",
c.row_count as "Row count",
s.stawidth as "Average id field width",
pg_size_pretty(pg_table_size(t.oid)) as "Table size without index",
pg_size_pretty(pg_indexes_size(t.oid)) as "Index size" /*,
n_tup_ins,
n_tup_upd,
n_tup_del,
n_tup_hot_upd,
n_live_tup,
n_dead_tup */
FROM eval_config as c
JOIN pg_class as t
ON c.table_name = t.relname
AND t.relkind = 'r'
JOIN pg_namespace as n
ON relnamespace = n.oid
AND n.nspname = 'eval_schema'
LEFT
JOIN pg_statistic as s
ON s.starelid = t.oid
AND s.staattnum = 1
LEFT
JOIN pg_stat_user_tables as w
ON w.relid = t.oid;
DO $SQL$
DECLARE
config record;
BEGIN
FOR config IN SELECT * FROM eval_config
LOOP
RAISE INFO 'Creating table %', quote_ident( config.table_name );
EXECUTE $$
CREATE TABLE $$ || quote_ident( config.table_name ) || $$
( id $$ || config.id_field_type || $$, data text );
$$;
RAISE INFO 'Filling table %', quote_ident( config.table_name );
EXECUTE $$
INSERT INTO $$ || quote_ident( config.table_name ) || $$
SELECT ( $$ || config.id_field_expression || $$ )::$$ || config.id_field_type || $$, 'some filling data'
FROM generate_series(1, $$ || config.row_count || $$) as s(i);
$$;
RAISE INFO 'Building index on %', quote_ident( config.table_name );
EXECUTE $$
CREATE INDEX ON $$ || quote_ident( config.table_name ) || $$ ( id );
$$;
RAISE INFO 'Analyzing table %', quote_ident( config.table_name );
EXECUTE $$
ANALYZE $$ || quote_ident( config.table_name ) || $$;
$$;
END LOOP;
END;
$SQL$;
SELECT * FROM eval_table_stats;
DO $SQL$
DECLARE
config record;
BEGIN
FOR config IN SELECT * FROM eval_config
LOOP
RAISE INFO 'Bloating table % (phase 1)', quote_ident( config.table_name );
EXECUTE $$
UPDATE $$ || quote_ident( config.table_name ) || $$
SET data = data
WHERE random() > 0.5;
$$;
RAISE INFO 'Bloating table % (phase 2)', quote_ident( config.table_name );
EXECUTE $$
UPDATE $$ || quote_ident( config.table_name ) || $$
SET data = data
WHERE random() > 0.5;
$$;
RAISE INFO 'Analyzing table %', quote_ident( config.table_name );
EXECUTE $$
ANALYZE $$ || quote_ident( config.table_name ) || $$;
$$;
END LOOP;
END;
$SQL$;
SELECT * FROM eval_table_stats;
As a result of execution of this script, we got several tables and some statistics on the table and index sizes.
I created the tables with
id field with types: integer, bigint, text and numeric, additionally bytea and text for the md5 hash indexes. Actually the table size is including the filler text data, so it's size is not only the size of the fields being evaluated:Table sizes just after insertion of 100T rows
| Table name | Indexed field type | Expression | Row count | Average id field width | Table size without index | Index size |
|---|---|---|---|---|---|---|
| integer_short_id_table | integer | s.i | 100000 | 4 | 5128 kB | 1768 kB |
| integer_large_id_table | integer | s.i * 1234 | 100000 | 4 | 5128 kB | 1768 kB |
| bigint_short_id_table | bigint | s.i | 100000 | 8 | 5552 kB | 2208 kB |
| bigint_large_id_table | bigint | s.i * 1234 | 100000 | 8 | 5552 kB | 2208 kB |
| text_number_short_id_table | text | s.i | 100000 | 5 | 5128 kB | 2200 kB |
| text_number_large_id_table | text | s.i * 1234 | 100000 | 9 | 5552 kB | 2624 kB |
| numeric_short_id_table | numeric | s.i | 100000 | 8 | 5552 kB | 2616 kB |
| numeric_large_id_table | numeric | s.i * 1234 | 100000 | 9 | 5624 kB | 2656 kB |
| binary_md5_text_table | bytea | decode( md5( s.i::text || '-text-filler'), 'hex' ) | 100000 | 17 | 6336 kB | 3552 kB |
| md5_text_table | text | md5( s.i::text || '-text-filler' ) | 100000 | 33 | 7880 kB | 5328 kB |
Table sizes after random bloating (2 times random update of 50% of rows)
| Table name | Indexed field type | Expression | Row count | Average id field width | Table size without index | Index size |
|---|---|---|---|---|---|---|
| integer_short_id_table | integer | s.i | 100000 | 4 | 10232 kB | 5288 kB |
| integer_large_id_table | integer | s.i * 1234 | 100000 | 4 | 10232 kB | 5272 kB |
| bigint_short_id_table | bigint | s.i | 100000 | 8 | 11 MB | 6592 kB |
| bigint_large_id_table | bigint | s.i * 1234 | 100000 | 8 | 11 MB | 6560 kB |
| text_number_short_id_table | text | s.i | 100000 | 5 | 10232 kB | 5896 kB |
| text_number_large_id_table | text | s.i * 1234 | 100000 | 9 | 11 MB | 7096 kB |
| numeric_short_id_table | numeric | s.i | 100000 | 8 | 11 MB | 7752 kB |
| numeric_large_id_table | numeric | s.i * 1234 | 100000 | 9 | 11 MB | 7880 kB |
| binary_md5_text_table | bytea | decode( md5( s.i::text || '-text-filler'), 'hex' ) | 100000 | 17 | 12 MB | 7336 kB |
| md5_text_table | text | md5( s.i::text || '-text-filler' ) | 100000 | 33 | 15 MB | 11 MB |
Table sizes just after insertion of 5M rows
| Table name | Indexed field type | Expression | Row count | Average id field width | Table size without index | Index size |
|---|---|---|---|---|---|---|
| integer_short_id_table | integer | s.i | 5000000 | 4 | 249 MB | 86 MB |
| integer_large_id_table | integer | s.i * 123 | 5000000 | 4 | 249 MB | 86 MB |
| bigint_short_id_table | bigint | s.i | 5000000 | 8 | 269 MB | 107 MB |
| bigint_large_id_table | bigint | s.i * 123 | 5000000 | 8 | 269 MB | 107 MB |
| text_number_short_id_table | text | s.i | 5000000 | 7 | 269 MB | 107 MB |
| text_number_large_id_table | text | s.i * 123 | 5000000 | 9 | 269 MB | 128 MB |
| numeric_short_id_table | numeric | s.i | 5000000 | 8 | 269 MB | 129 MB |
| numeric_large_id_table | numeric | s.i * 123 | 5000000 | 10 | 284 MB | 129 MB |
| binary_md5_text_table | bytea | decode( md5( s.i::text || '-text-filler'), 'hex' ) | 5000000 | 17 | 308 MB | 172 MB |
| md5_text_table | text | md5( s.i::text || '-text-filler' ) | 5000000 | 33 | 383 MB | 259 MB |
Table sizes after random bloating (2 times random update of 50% of rows)
| Table name | Indexed field type | Expression | Row count | Average id field width | Table size without index | Index size |
|---|---|---|---|---|---|---|
| integer_short_id_table | integer | s.i | 5000000 | 4 | 498 MB | 257 MB |
| integer_large_id_table | integer | s.i * 123 | 5000000 | 4 | 498 MB | 257 MB |
| bigint_short_id_table | bigint | s.i | 5000000 | 8 | 539 MB | 321 MB |
| bigint_large_id_table | bigint | s.i * 123 | 5000000 | 8 | 539 MB | 321 MB |
| text_number_short_id_table | text | s.i | 5000000 | 7 | 538 MB | 287 MB |
| text_number_large_id_table | text | s.i * 123 | 5000000 | 9 | 539 MB | 340 MB |
| numeric_short_id_table | numeric | s.i | 5000000 | 8 | 539 MB | 384 MB |
| numeric_large_id_table | numeric | s.i * 123 | 5000000 | 10 | 569 MB | 384 MB |
| binary_md5_text_table | bytea | decode( md5( s.i::text || '-text-filler'), 'hex' ) | 5000000 | 17 | 615 MB | 354 MB |
| md5_text_table | text | md5( s.i::text || '-text-filler' ) | 5000000 | 33 | 766 MB | 545 MB |
Ok, we see here, that actually it is a field byte width, that is important and for small numbers, the difference is really not so significant, but still an index on
integer (4 bytes wide) filed is little smaller then index on text representation of the same ids even for tables that keep 10000 records only (these results are not posted here).I specially was creating the tables in 2 variants,
id as a result of 1..N series, and multiplied by 1234 or 123 to make the numbers wider in text representation to see how the length of the text representation of the number is influencing the size of the index.Some more play with the
smallint type, showed that it does save space in the table itself, but the index size is the same as for integer. What was really surprising for me, that
numeric type had bigger indexes then text indexes for the same numbers.And of cause again and again -- indexes on some long text fields can be built on the md5 hash of such a text and be much more performant, when you index
bytea version of the md5 hash instead of text representation of the md5 hash itself. Though md5 hash collisions can theoretically happen, I have not experienced them yet myself in practice.
Posted by
valgog
at
17:21
3
comments
Labels: postgresql
2011-02-17
pgAdmin III macros: get table fields
Here is a simple macro that can be assigned to a key combination in
pgAdmin -> Query Window -> Macros -> Manage macros... select quote_ident(nspname) || '.' || quote_ident(relname) as table_name,
quote_ident(attname) as field_name,
format_type(atttypid,atttypmod) as field_type,
case when attnotnull then ' NOT NULL' else '' end as null_constraint,
case when atthasdef then 'DEFAULT ' ||
( select pg_get_expr(adbin, attrelid)
from pg_attrdef
where adrelid = attrelid and adnum = attnum )::text else ''
end as dafault_value,
case when nullif(confrelid, 0) is not null
then confrelid::regclass::text || '( ' ||
array_to_string( ARRAY( select quote_ident( fa.attname )
from pg_attribute as fa
where fa.attnum = ANY ( confkey )
and fa.attrelid = confrelid
order by fa.attnum
), ','
) || ' )'
else '' end as references_to
from pg_attribute
left outer join pg_constraint on conrelid = attrelid
and attnum = conkey[1]
and array_upper( conkey, 1 ) = 1,
pg_class,
pg_namespace
where pg_class.oid = attrelid
and pg_namespace.oid = relnamespace
and pg_class.oid = btrim( '$SELECTION$' )::regclass::oid
and attnum > 0
and not attisdropped
order by attrelid, attnum;
Just select a table name, that you are interested in, and press the key binding, that you selected for that macros. pgAdmin will execute the query and show the list of all the columns of that table, including default values and foreign key references...
My preferred key binding is
CTRL+1
Posted by
valgog
at
17:22
4
comments
Labels: pgadmin, postgresql
Type of NULL is important
The following SQL query shows how different can be the result of concatenating arrays with NULL values:
select '{e1,e2}'::text[] || 't'::text as normal_array_concatenation,
NULL::text[] || 't'::text as appending_element_to_existing_NULL_value_array,
NULL || 't'::text as appending_element_to_NULL_value,
'{e1,e2}'::text[] || NULL::text as appending_typed_NULL,
'{e1,e2}'::text[] || NULL::text[] as appending_typed_NULL_array;
The result of the execution (on 9.0) is:
─[ RECORD 1 ]──────────────────────────────────┬─────────────
normal_array_concatenation │ {e1,e2,t}
appending_element_to_existing_null_value_array │ {t}
appending_element_to_null_value │
appending_typed_null │ {e1,e2,NULL}
appending_typed_null_array │ {e1,e2}
That explains why one can simply initialize a new array variable in PL/pgSQL and then immediately start concatenating it with values, not pre-initializing it with an empty array (that you have to do when you want to populate a text string... you cannot just take a NULL::text variable and start concatenating strings to it, you have to first pre-inizialize it with en empty string, but with arrays you can).
Another very important issue, that is related to that beheviour of arrays, is that when creating dynamic SQL queries for EXECUTE command in PL/pgSQL you SHOULD always put explicit type casts to the variables that you quote using quote_literal() or quote_nullable() when building dynamic queries:
DO $SQL$
DECLARE
r text[];
t text := NULL;
a text[] := NULL;
BEGIN
SELECT '{e1,e2}'::text[] || a INTO r;
RAISE INFO 'array concatenate: r is %', r;
EXECUTE $$SELECT '{e1,e2}'::text[] || $$ || quote_nullable(a) || $$::text[] $$ INTO r;
RAISE INFO 'array concatenate from EXECUTE: r is %', r;
SELECT '{e1,e2}'::text[] || t INTO r;
RAISE INFO 'array append: r is %', r;
EXECUTE $$SELECT '{e1,e2}'::text[] || $$ || quote_nullable(t) || $$::text $$ INTO r;
RAISE INFO 'array append from EXECUTE: r is %', r;
/* These 2 examples will fail on the runtime
throwing exception (operator is not unique: text[] || unknown)
--EXECUTE $$SELECT '{e1,e2}'::text[] || $$ || quote_nullable(a) INTO r;
--RAISE INFO 'r is %', r;
--
--EXECUTE $$SELECT '{e1,e2}'::text[] || $$ || quote_nullable(t) INTO r;
--RAISE INFO 'r is %', r;
*/
END;
$SQL$;
Posted by
valgog
at
16:20
2
comments
Labels: postgresql
2010-11-24
Reflecting generic type parameters in Java
If you one is introspecting an object class in Java, it was always a problem to be able to find out, what is the actual type of the generic type parameter for a class field or property.
Ok, yesterday Daniel and I ware listening to a presentation about EclipseLink from a guy from Oracle (or a company, that is working with Oracle, but this is not so important). The question about Generic Type erasure was in our minds all the time, when the guy showed how EclipseLink is generating adapter classes for his model classes. Ok, we asked about the issue, and he showed us how one can extract generic type parameter information for object fields using reflection methods. This approach will work only on the Field classes actually, but this is more then enough to be able to extract actual generic parameter types for class fields :)
Here is a simple example, that I made inspired by this information:
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
public class GenericTypeParameterDemo {
static final class MyGenericType<e extends Number> {
public Map<String, E> map;
}
@Before
public void setUp() throws Exception {
}
@Test
public void extractGenericParameters() throws Exception {
Field field = MyGenericType.class.getDeclaredField("map");
Type fieldGenericType = field.getGenericType();
ParameterizedType parametrizedFieldType = (ParameterizedType) fieldGenericType;
for (Type actualParameterType : parametrizedFieldType.getActualTypeArguments()) {
if ( actualParameterType instanceof TypeVariable<?>) {
Type[] bounds = TypeVariable.class.cast(actualParameterType).getBounds();
System.out.println(actualParameterType.toString() +
" with bounds " + Arrays.toString(bounds) );
} else {
System.out.println(actualParameterType);
}
}
}
}
It is really simple, as you can see on lines 25 and 26. The trick is to really check if the result of
Field.getGenericType()is actually an instance of
ParameterizedType. One can look up also other children of Type interface, that can bring more details on the structure of your field types.
Posted by
valgog
at
10:33
5
comments
Labels: java
2010-08-08
ALTER ENUM in PostgreSQL
Note: do not use this method on PostgreSQL 9.1. This can corrupt your catalog as new enum model differentiates between even and odd OIDs for enum values. But in PostgreSQL 9.1 there is a nice ALTER TYPE command, that can alter you enum correctly.
Unfortunately PostgreSQL (9.0 and before) does not support altering ENUM types, that makes their use quite limited. Very often, it is important to have a possibility to extend already existing enumeration type and in very few cases delete or rename an element of an enumeration type.
The standard way to do that, would be to create a new enumeration type with some temporary name, convert the fields of the type of your old enumeration type to the new enumeration type you just created, then drop the old one and rename the temporary name to the original enum type. This operation is doable, but conversion of the field type is usually getting a full lock on that table and can be quite a long operation (maybe in 9.1 this will change).
By now, the only way to do it without table lock and messing up with temporary enumeration types, is to play with the catalog directly.
As internally postgres is storing OIDs of the enumeration type labels in the tables and types, where this enumeration is being used, one can actually add new labels and change the names of existing labels in the needed enumeration without a fear to destroy integrity of existing data structures, that use that enumeration labels already (i.e. OIDs of that enumeration labels).
To add one additional label in an existing ENUM one can do the following:
insert into pg_enum(enumtypid, enumlabel) select t.oid, 'NEW_ENUM_VALUE' from pg_type as t where t.typtype = 'e' and t.oid = 'existing_enum'::regtype::oid and string_to_array( split_part(version(), ' ', 2), '.' )::int[] < ARRAY[9,1] returning enumtypid::regtype::text, enumlabel;
This will add a label of NEW_ENUM_VALUE to an existing enumeration type existing_enum (be sure to put the right letter case for your enumeration type and enumeration label names).
To rename an existing value name one can do the following:
update pg_enum as e set enumlabel = 'UPDATED_ENUM_VALUE' where e.enumtypid = 'existing_enum'::regtype::oid and e.enumlabel = 'OLD_ENUM_VALUE' and string_to_array( split_part(version(), ' ', 2), '.' )::int[] < ARRAY[9,1] returning e.enumtypid::regtype::text, e.enumlabel;
Deletion of the enumeration labels can be really dangerous as one can accidentally drop an OID of the label, that is still being used in the tables. Dmitry Koterov wrote a stored procedure, that does some easiest checks on tables (not types or types of types) that use the enumeration that you want to change. So if you really need to drop an enumeration label from an existing enumeration, think twice first and then read the post with the source of this stored procedure :)
Posted by
valgog
at
14:57
5
comments
Labels: postgresql
2010-05-28
Merging and manipulating arrays in PostgreSQL
Though, strictly speaking, using arrays in relational databases is kind of not correct, still we use arrays, especially in stored procedures.
Actually PostgreSQL provides us with quite an number of nice array manipulation functions. That can be used at least starting from PostgreSQL 8.0. But what about set operations. How to sort an array in Postgres? How to merge arrays without repetitions? How to remove duplicate elements keeping the order of the appearance in the original array?
If you are working with the integer based arrays, there is quite an old and effective module intarray that allows us to perform some basic set operations on the integer based arrays.
But what to do with not integer based arrays? Or if we want to do some relatively complicated operation on integer bases array? SQL is a set manipulation language, so that means that actually, we can use internal SQL mechanics to manipulate, merge and sort arrays. But for that, first we have to be able to convert an array into kind of a table, so that we can use usual SQL manipulation on it's elements.
generate_series(), generate_subscripts() and unnest() are the most important functions in our case. Unfortunately generate_subscripts() and unnest() are only available starting from PostgreSQL 8.4, but one can easily create a small helper unnest() function using generate_series(), that will be not as efficient as the build in one, but still it is quite good trade off.
CREATE OR REPLACE FUNCTION unnest(a anyarray)
RETURNS SETOF anyelement AS
$BODY$
/**
* Unnests given array into a table
*/
/* -- testing
select unnest(ARRAY[1,2,3,4,5]) as i
select * from unnest(ARRAY[1,2,3,4,5]) as u(i)
*/
select ($1)[s.i]
from generate_series( array_lower($1, 1), array_upper($1, 1 ) ) as s(i);
$BODY$
LANGUAGE 'sql' IMMUTABLE STRICT;
A good thing about
unnest() written in SQL and not PL/pgSQL is that one can use such a set retuning function directly after SELECT not necessarily pushing it in the list of used tables after FROM clause. The reason was explained by Tom Lane some time back in one of the newsgroup threads and is related to the implementation drawbacks of the PL/pgSQL. Ok, so now I will simply give several examples of how to use these functions.
| Merge 2 or more text arrays excluding duplicates: |
|---|
select ARRAY( select unnest(ARRAY[ 'a', 'b', 'c' ]) union select unnest(ARRAY[ 'c', 'd', 'e' ]) ) |
UNION ALL instead, we are doing just the same as array_cat(), but slower :)Note, that
UNION actually sorts the elements of the result set fist, to eliminate the duplicates, so the resulting array will be sorted usually. But one cannot relay on that behavior and if you really need a sorted array on the output, you have to use ORDER BY explicitly.| Merge 2 or more text arrays excluding duplicates and sorting elements: |
|---|
select ARRAY( select unnest(ARRAY[ 'a', 'b', 'c' ]) as e union select unnest(ARRAY[ 'c', 'd', 'e' ]) as e order by e ) |
| Trim or rewrite text array elements: |
|---|
select ARRAY(
select btrim(lower(regexp_replace(unnest(ARRAY['a a','B b','c C ']),
$$\s+$$,
' ',
'g')
)
) as e
) |
By now, with this syntax we cannot actually filter some elements out of the original array.
| Filter elements from an array depending on element properties: |
|---|
select ARRAY(
select a.e
from unnest(ARRAY[0,4,2,5,4,1,6,9,8,7]) as a(e)
where a.e % 2 = 0
)
|
unnest() to FROM list to be able to name the virtual table, that unnest() creates as well as name the element column so that we can reference it in the where clause. Again there are not guaranties here that our array will be contain elements in the same order that it was in the original one. The problem is that all these queries do not know anything about the original position of the elements in the original array. To solve this problem we need element indexes to be visible inside the query. One can do it using a plain old
generate_series() or not so old generate_subscripts() (one actually should prefer the later one if you are on the PostgreSQL 8.4+) to unnest our original array. You cannot use the unnest() directly as there is not way to determine the position or index other then using window functions like row_number(), but this is much much slower on practice. | Filtering elements from an array depending on element position (index): |
|---|
select ARRAY(
select (ARRAY[0,4,2,5,4,1,6,9,8,7])[s.i]
from generate_series(array_lower(ARRAY[0,4,2,5,4,1,6,9,8,7], 1),
array_upper(ARRAY[0,4,2,5,4,1,6,9,8,7], 1) ) as s(i)
where s.i % 2 = 0
order by s.i
)
|
But using
generate_series() or generate_subscripts() shows a small problem actually. We have to push the original array at least 2 times in the query. Inside a PL/pgSQL code the arrays are usually replaced with the variable names, and the queries do not look so bulky there, but there is a possibility to create an additional helper function that will unnest an array returning not only a element itself, but also it's index in the original array:CREATE OR REPLACE FUNCTION array_enumerate(IN a anyarray,
OUT i integer, OUT e anyelement)
RETURNS SETOF record AS
$BODY$
/**
* Unnests array into a table together with element indexes
*/
/* -- testing
select * from array_enumerate(ARRAY['a', 'b', 'c']) as u
*/
select s.i, ($1)[s.i] from generate_series( array_lower($1, 1), array_upper($1, 1 ) ) as s(i)
$BODY$
LANGUAGE 'sql' IMMUTABLE STRICT;
on the PostgreSQL versions after 8.4 one can write it using generate_subscripts() like:CREATE OR REPLACE FUNCTION array_enumerate(IN a anyarray,
OUT i integer, OUT e anyelement)
RETURNS SETOF record AS
$BODY$
/**
* Unnests array into a table together with element indexes
*/
/* -- testing
select * from array_enumerate(ARRAY['a', 'b', 'c']) as u
*/
select s.i, ($1)[s.i] from generate_subscripts( $1, 1 ) as s(i)
$BODY$
LANGUAGE 'sql' IMMUTABLE STRICT;
Using generate_subscripts() should be preferable as one can create some crazy array with elements having noncontinuous indexes actually. And it should be also a little bit faster, then generate_series().This actually gives us a possibility to rewrite previous query like this:
select ARRAY(
select s.e
from utils.array_enumerate(ARRAY[0,4,2,5,4,1,6,9,8,7]) as s(i,e)
where s.i % 2 = 0
order by s.i
)
|
| Remove duplicate elements from array keeping the element appearance order: |
|---|
select ARRAY(
select s.e
from utils.array_enumerate(ARRAY[0,4,2,5,4,1,6,9,8,7]) as s(i,e)
group by s.e
order by min(s.i)
)
|
string_to_array() and array_to_string() methods, or on newer versions of PostgreSQL regexp_split_to_array() to remove the repeating space or punctuation characters during splitting directly.Practically everywhere in the queries, one could use the
array_agg() to generate the resulting arrays or, in the coming 9.0, a very efficient string_agg() to generate strings directly, without creating preliminary arrays and then using array_to_string().On my tests comparing the performance of the
ARRAY(select) and array_agg() I could not find any significant performance difference.
Posted by
valgog
at
13:59
1 comments
Labels: postgresql