Custom column ordering

From: Steven Xu <stevenx(at)yorku(dot)ca>
To: pgsql-general(at)postgresql(dot)org
Subject: Custom column ordering
Date: 2016-03-01 20:54:15
Message-ID: OF4914D9AC.730EB9E5-ON85257F69.0072D4B1-85257F69.0072D4B3@yorku.ca
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

<font face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" size="2">Hi all,<br><br>I'm trying to order some rows based on port names, a text column, using some domain-specific knowledge for Netdisco, an open-source application. <br><br>In particular, I'm trying to do this without having to redo the entire design for the database. Note that in this database, there are no foreign key constraints, mostly because they weren't considered in the original inception of the software. <br><br>The rough idea of the solution I've come up with is to create a new composite data type called "<font face="Default Monospace,Courier New,Courier,monospace">port</font>" with a single text column. Then I created the relevant comparison functions, operators and operator classes under the "<font face="Default Monospace,Courier New,Courier,monospace">port_ops</font>" operator family. Then, I created a function "<font face="Default Monospace,Courier New,Courier,monospace">cast_to_port(text)</font>" that casts "<font face="Default Monospace,Courier New,Courier,monospace">text</font>" data types to "<font face="Default Monospace,Courier New,Courier,monospace">port</font>" (which simply creates a tuple with the single text value, see the code for details). Finally, I created an index on the "<font face="Default Monospace,Courier New,Courier,monospace">device_port</font>" table with "<font face="Default Monospace,Courier New,Courier,monospace">cast_to_port(port)</font>" as the indexed column using "port_ops". However, when I run "<font face="Default Monospace,Courier New,Courier,monospace">select port from device_port order by cast_to_port(port)</font>", it doesn't use the index I created and doesn't even order using the operators I created. Instead, it orders by the lexicographical ordering of the original text column.<br><br>Questions:<br>&nbsp; - Why is PostgreSQL not using the functional index I created and why is it not being ordered correctly?<br>&nbsp; - Is creating a separate data type and using a functional index on the casts to this data type the right approach to a custom ordering?<br><br>Steven<br><br><br>Details:<br>Creating the "<font face="Default Monospace,Courier New,Courier,monospace">port</font>" type:<br><font face="Default Monospace,Courier New,Courier,monospace">&nbsp; create type port as (f1 text);</font><br><br>Creating the comparator function, substituted with much shorter code as an example. I can include the original code, but it's much shorter to provide this.<br>It returns -1 if the first argument is "less than" the second argument, 0 if they are equal and 1 otherwise. In this example, "less than" means lexicographically greater, because of the negation.<br><font face="Default Monospace,Courier New,Courier,monospace">&nbsp; create or replace function port_cmp(port,port)<br>&nbsp; &nbsp; &nbsp; RETURNS integer as<br>&nbsp; $$<br>&nbsp; &nbsp; my ($a, $b) = @_;<br>&nbsp;&nbsp;&nbsp; return -( $a cmp $b ); // comparison function does the opposite of cmp<br>&nbsp; $$ language plperl;</font><br><br>Creating the "<font face="Default Monospace,Courier New,Courier,monospace">port_ops</font>" operators and operator classes for the "<font face="Default Monospace,Courier New,Courier,monospace">port</font>" type:<br><font face="Default Monospace,Courier New,Courier,monospace">CREATE OR REPLACE FUNCTION port_lt(port, port)<br>&nbsp;&nbsp;&nbsp; RETURNS boolean AS<br>$$<br>&nbsp;&nbsp;&nbsp; BEGIN<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; RETURN port_cmp($1, $2) &lt; 0;<br>&nbsp;&nbsp;&nbsp; END;<br>$$ LANGUAGE plpgsql;<br><br>CREATE OR REPLACE FUNCTION port_gt(port, port)<br>&nbsp;&nbsp;&nbsp; RETURNS boolean AS<br>$$<br>&nbsp;&nbsp;&nbsp; BEGIN<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; RETURN port_cmp($1, $2) &gt; 0;<br>&nbsp;&nbsp;&nbsp; END;<br>$$ LANGUAGE plpgsql;<br><br>CREATE OR REPLACE FUNCTION port_lte(port, port)<br>&nbsp;&nbsp;&nbsp; RETURNS boolean AS<br>$$<br>&nbsp;&nbsp;&nbsp; BEGIN<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; RETURN port_cmp($1, $2) &lt;= 0;<br>&nbsp;&nbsp;&nbsp; END;<br>$$ LANGUAGE plpgsql;<br><br>CREATE OR REPLACE FUNCTION port_gte(port, port)<br>&nbsp;&nbsp;&nbsp; RETURNS boolean AS<br>$$<br>&nbsp;&nbsp;&nbsp; BEGIN<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; RETURN port_cmp($1, $2) &gt;= 0;<br>&nbsp;&nbsp;&nbsp; END;<br>$$ LANGUAGE plpgsql;<br><br>CREATE OR REPLACE FUNCTION port_eq(port, port)<br>&nbsp;&nbsp;&nbsp; RETURNS boolean AS<br>$$<br>&nbsp;&nbsp;&nbsp; BEGIN<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; RETURN port_cmp($1, $2) = 0;<br>&nbsp;&nbsp;&nbsp; END;<br>$$ LANGUAGE plpgsql;<br><br>CREATE OPERATOR &lt; ( PROCEDURE=port_lt, LEFTARG=port, RIGHTARG=port);<br>CREATE OPERATOR &lt;= ( PROCEDURE=port_lte, LEFTARG=port, RIGHTARG=port);<br>CREATE OPERATOR &gt;= ( PROCEDURE=port_gte, LEFTARG=port,&nbsp; RIGHTARG=port);<br>CREATE OPERATOR &gt; ( PROCEDURE=port_gte, LEFTARG=port, RIGHTARG=port);</font><br><font face="Default Monospace,Courier New,Courier,monospace">CREATE OPERATOR = ( PROCEDURE=port_eq, LEFTARG=port, RIGHTARG=port);<br><br>CREATE OPERATOR CLASS port_ops DEFAULT<br>FOR TYPE port USING btree<br>AS<br>OPERATOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;&nbsp; ,<br>OPERATOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;= ,<br>OPERATOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&nbsp; ,<br>OPERATOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &gt;= ,<br>OPERATOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &gt;&nbsp; ,<br>FUNCTION&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port_cmp(port, port)<br>;</font><br><br>Creating the function to cast "<font face="Default Monospace,Courier New,Courier,monospace">text</font>" data to "<font face="Default Monospace,Courier New,Courier,monospace">port</font>" data:<br><font face="Default Monospace,Courier New,Courier,monospace">CREATE OR REPLACE FUNCTION cast_to_port(text)<br>&nbsp;RETURNS port<br>&nbsp;LANGUAGE sql<br>AS $function$<br>&nbsp; SELECT ($1);<br>$function$ IMMUTABLE;</font><br><br>Creating the index:<br><font face="Default Monospace,Courier New,Courier,monospace">CREATE INDEX idx_device_port_port_proper on device_port (cast_to_port(port) port_ops desc);</font><br><br>Sample table structure, shortened for brevity:<br><font face="Default Monospace,Courier New,Courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Table "public.device_port"<br>&nbsp;&nbsp;&nbsp; Column&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Modifiers<br>--------------+-----------------------------+------------------------<br>&nbsp;ip&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | inet&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | not null<br>&nbsp;port&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | not null</font><br><br><br><br><div></div></font>

Attachment Content-Type Size
unknown_filename text/html 7.3 KB

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Artur Zakirov 2016-03-01 21:04:36 Re: commit time in logical decoding
Previous Message Igor Neyman 2016-03-01 19:02:33 Re: multiple UNIQUE indices for FK