Обсуждение: ORDER BY with exception

Поиск
Список
Период
Сортировка

ORDER BY with exception

От
brian
Дата:
I have a lookup table with a bunch of disciplines:

# SELECT id, name FROM discipline;
  id |        name
----+---------------------
   1 | writing
   2 | visual arts
   3 | music
   4 | dance
   5 | film and television
   6 | theatre
   7 | media arts
   8 | community
   9 | fine craft
  10 | other
(10 rows)

and a function that returns each discipline name along with the total
number of records in another table (showcase) that are related to each
discipline. Each showcase entry may have 0 or more items (showcase_item)
related to it, so ones that have no items are disregarded here. Also,
only showcases that have been accepted should be counted.

First, here's the working function:

CREATE FUNCTION getshowcasetotalsbydiscipline(OUT name text, OUT total
integer) RETURNS SETOF record
AS $$

DECLARE
  rec record;

BEGIN
  FOR rec IN
   EXECUTE 'SELECT id, name, 1 AS total FROM discipline'
   LOOP
     name := rec.name;

     SELECT INTO rec.total

       -- a showcase may be in the DB but not accepted by an admin
       SUM(CASE s.accepted WHEN TRUE THEN 1 ELSE 0 END)
       FROM showcase AS s
       WHERE s.id IN

         -- a showcase may exist with no items, so should be ignored
         (SELECT si.showcase_id FROM showcase_item AS si
           WHERE si.discipline_id = rec.id);

          -- If no showcase items have this discipline,
          -- give it a total of zero

     IF rec.total IS NULL THEN
       SELECT INTO total 0;
     ELSE
       total := rec.total;
     END IF;

   RETURN NEXT;
   END LOOP;

   RETURN;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

test=# SELECT * FROM getShowcaseTotalsByDiscipline();
         name         | total
---------------------+-------
  writing             |    130
  visual arts         |    252
  music               |    458
  dance               |    131
  film and television |    102
  theatre             |    271
  media arts          |     83
  community           |     20
  fine craft          |     78
  other               |     59
(10 rows)

Works fine, but i'd like to order the disciplines alphabetically
*except* have 'other' fall at the end. So, should i loop a second time,
after summing the totals, and keep the 'other' row aside, then add it to
the end?

(btw, the output of this function is cached until a new showcase is
accepted)

Or, should i re-order the disciplines alphabetically in the lookup
trable, keeping 'other' to be last?

I could do the latter, although it would mean a fair bit of work because
the disciplines table relates to a bunch of other stuff, as well. Also,
there's always the chance that a new discipline will be added in the
future. I suppose i could write a trigger that bumped the 'other' id
above that of the new entry, then re-relate everything else in the DB
that's connected to the 'other' discipline. But that strikes me as kind
of a hack.

The third option is to re-order the resultset in the PHP script that
displays this. But that wasn't why i chose Postgres for this app ;-)

brian

Re: ORDER BY with exception

От
"Josh Tolley"
Дата:
On 6/21/07, brian <brian@zijn-digital.com> wrote:
> I have a lookup table with a bunch of disciplines:
>
> # SELECT id, name FROM discipline;
>   id |        name
> ----+---------------------
>    1 | writing
>    2 | visual arts
>    3 | music
>    4 | dance
>    5 | film and television
>    6 | theatre
>    7 | media arts
>    8 | community
>    9 | fine craft
>   10 | other
> (10 rows)
>
> and a function that returns each discipline name along with the total
> number of records in another table (showcase) that are related to each
> discipline. Each showcase entry may have 0 or more items (showcase_item)
> related to it, so ones that have no items are disregarded here. Also,
> only showcases that have been accepted should be counted.
>
> First, here's the working function:
>
> CREATE FUNCTION getshowcasetotalsbydiscipline(OUT name text, OUT total
> integer) RETURNS SETOF record
> AS $$
>
> DECLARE
>   rec record;
>
> BEGIN
>   FOR rec IN
>    EXECUTE 'SELECT id, name, 1 AS total FROM discipline'
>    LOOP
>      name := rec.name;
>
>      SELECT INTO rec.total
>
>        -- a showcase may be in the DB but not accepted by an admin
>        SUM(CASE s.accepted WHEN TRUE THEN 1 ELSE 0 END)
>        FROM showcase AS s
>        WHERE s.id IN
>
>          -- a showcase may exist with no items, so should be ignored
>          (SELECT si.showcase_id FROM showcase_item AS si
>            WHERE si.discipline_id = rec.id);
>
>           -- If no showcase items have this discipline,
>           -- give it a total of zero
>
>      IF rec.total IS NULL THEN
>        SELECT INTO total 0;
>      ELSE
>        total := rec.total;
>      END IF;
>
>    RETURN NEXT;
>    END LOOP;
>
>    RETURN;
> END;
> $$ LANGUAGE plpgsql IMMUTABLE;
>
> test=# SELECT * FROM getShowcaseTotalsByDiscipline();
>          name         | total
> ---------------------+-------
>   writing             |    130
>   visual arts         |    252
>   music               |    458
>   dance               |    131
>   film and television |    102
>   theatre             |    271
>   media arts          |     83
>   community           |     20
>   fine craft          |     78
>   other               |     59
> (10 rows)
>
> Works fine, but i'd like to order the disciplines alphabetically
> *except* have 'other' fall at the end. So, should i loop a second time,
> after summing the totals, and keep the 'other' row aside, then add it to
> the end?
>
> (btw, the output of this function is cached until a new showcase is
> accepted)
>
> Or, should i re-order the disciplines alphabetically in the lookup
> trable, keeping 'other' to be last?
>
> I could do the latter, although it would mean a fair bit of work because
> the disciplines table relates to a bunch of other stuff, as well. Also,
> there's always the chance that a new discipline will be added in the
> future. I suppose i could write a trigger that bumped the 'other' id
> above that of the new entry, then re-relate everything else in the DB
> that's connected to the 'other' discipline. But that strikes me as kind
> of a hack.
>
> The third option is to re-order the resultset in the PHP script that
> displays this. But that wasn't why i chose Postgres for this app ;-)
>
> brian
>
> ---------------------------(end of broadcast)---------------------------
> TIP 2: Don't 'kill -9' the postmaster
>

It seems to me you could replace it all with one query, something like this:

SELECT discipline, COUNT(1) FROM showcase WHERE EXISTS (SELECT * FROM
showcase_item WHERE showcase_id = showcase.id LIMIT 1) GROUP BY
discipline ORDER BY (discipline != 'other'), discipline;

- Josh

Re: ORDER BY with exception

От
Michael Glaesemann
Дата:
On Jun 21, 2007, at 17:35 , brian wrote:

> I have a lookup table with a bunch of disciplines:

To answer your ordering question first:

SELECT id, name
FROM discipline
ORDER BY name = 'other'
     , name;
id |        name
----+---------------------
   8 | community
   4 | dance
   5 | film and television
   9 | fine craft
   7 | media arts
   3 | music
   6 | theatre
   2 | visual arts
   1 | writing
10 | other
(10 rows)

This relies on the fact that FALSE orders before TRUE. I don't always
remember which way, so I often have to rewrite it using <> or = to
get the behavior I want.


> and a function that returns each discipline name along with the
> total number of records in another table (showcase) that are
> related to each discipline. Each showcase entry may have 0 or more
> items (showcase_item) related to it, so ones that have no items are
> disregarded here. Also, only showcases that have been accepted
> should be counted.
>
> First, here's the working function:

I don't think you really need to use a function for this. I believe
you should be able to do this all in one SQL statement, something
like (if I've understood your query and intent correctly):

SELECT discipline.name, COUNT(showcase_id) AS total
FROM discipline
LEFT JOIN (
     SELECT DISTINCT discipline_id, showcase.id as showcase_id
     FROM showcase
     JOIN showcase_item on (showcase.id = showcase_id)
     WHERE accepted) AS accepted_showcases
         ON (discipline.id = discipline_id)
GROUP BY discipline.name
ORDER BY discipline.name = 'other'
     , discipline.name;
         name         | total
---------------------+-------
community           |     0
dance               |     0
film and television |     0
fine craft          |     0
media arts          |     0
music               |     0
theatre             |     0
visual arts         |     1
writing             |     2
other               |     0
(10 rows)

This should give you the total number of showcases that have been
accepted for each discipline. (DDL and data below.)

As a general rule, it's generally better to let the server handle the
data in sets (i.e., tables) as much as possible rather than using
procedural code.

Hope this helps.

Michael Glaesemann
grzm seespotcode net

CREATE TABLE discipline
(
     id INTEGER NOT NULL UNIQUE
     , name TEXT PRIMARY KEY
);

INSERT INTO discipline (id, name) VALUES
(1, 'writing')
, (2, 'visual arts')
, (3, 'music')
, (4, 'dance')
, (5, 'film and television')
, (6, 'theatre')
, (7, 'media arts')
, (8, 'community')
, (9, 'fine craft')
, (10, 'other');

SELECT *
FROM discipline
ORDER BY name;

SELECT *
FROM discipline
ORDER BY name = 'other', name;


CREATE TABLE showcase
(
     id INTEGER NOT NULL UNIQUE
     , name TEXT PRIMARY KEY
     , discipline_id INTEGER NOT NULL
         REFERENCES discipline(id)
     , accepted BOOLEAN NOT NULL
);

INSERT INTO showcase (id, name, discipline_id, accepted)
VALUES
     (1, 'foo', 1, true)
     , (2, 'bar', 2, true)
     , (3, 'baz', 1, true)
     , (4, 'quux', 1, false)
     , (5, 'blurfl', 2, false);

CREATE TABLE showcase_item
(
     id INTEGER NOT NULL UNIQUE
     , description TEXT NOT NULL
     , showcase_id INTEGER NOT NULL
         REFERENCES showcase (id)
     , PRIMARY KEY (description, showcase_id)
);

INSERT INTO showcase_item (id, description, showcase_id)
VALUES
(1, 'a', 1)
, (2, 'b', 1)
, (3, 'c', 1)
, (4, 'd', 2)
, (5, 'e', 2)
, (6, 'f', 2)
, (7, 'g', 3)
, (8, 'h', 3)
, (9, 'i', 4)
, (10, 'j', 5);

SELECT *
FROM showcase;
id |  name  | discipline_id | accepted
----+--------+---------------+----------
   1 | foo    |             1 | t
   2 | bar    |             2 | t
   3 | baz    |             1 | t
   4 | quux   |             1 | f
   5 | blurfl |             2 | f
(5 rows)

SELECT *
FROM showcase
JOIN showcase_item ON (showcase.id = showcase_id);
id |  name  | discipline_id | accepted | id | description | showcase_id
----+--------+---------------+----------+----+-------------
+-------------
   1 | foo    |             1 | t        |  1 | a
|           1
   1 | foo    |             1 | t        |  2 | b
|           1
   1 | foo    |             1 | t        |  3 | c
|           1
   2 | bar    |             2 | t        |  4 | d
|           2
   2 | bar    |             2 | t        |  5 | e
|           2
   2 | bar    |             2 | t        |  6 | f
|           2
   3 | baz    |             1 | t        |  7 | g
|           3
   3 | baz    |             1 | t        |  8 | h
|           3
   4 | quux   |             1 | f        |  9 | i
|           4
   5 | blurfl |             2 | f        | 10 | j
|           5
(10 rows)




Re: ORDER BY with exception

От
brian
Дата:
Josh Tolley wrote:
> It seems to me you could replace it all with one query, something like
> this:
>
> SELECT discipline, COUNT(1) FROM showcase WHERE EXISTS (SELECT * FROM
> showcase_item WHERE showcase_id = showcase.id LIMIT 1) GROUP BY
> discipline ORDER BY (discipline != 'other'), discipline;
>

discipline is its own table, not a column, so i'd need to throw a join
in there. I have another table, showcase_discipline to relate from
showcases. I did it this way because there are several other tables that
rely on disciplines (one to one and many to one).

And thanks for the ORDER BY tip! I had no idea i could do that. I'll see
if i can work that in.

brian

Re: ORDER BY with exception

От
brian
Дата:
Michael Glaesemann wrote:
> On Jun 21, 2007, at 17:35 , brian wrote:
>
>> I have a lookup table with a bunch of disciplines:
>
>
> To answer your ordering question first:
>
> SELECT id, name
> FROM discipline
> ORDER BY name = 'other'
>     , name;
> id |        name
> ----+---------------------
>   8 | community
>   4 | dance
>   5 | film and television
>   9 | fine craft
>   7 | media arts
>   3 | music
>   6 | theatre
>   2 | visual arts
>   1 | writing
> 10 | other
> (10 rows)
>
> This relies on the fact that FALSE orders before TRUE. I don't always
> remember which way, so I often have to rewrite it using <> or = to  get
> the behavior I want.
>

Of course! (slaps forehead)

> I don't think you really need to use a function for this. I believe  you
> should be able to do this all in one SQL statement, something  like (if
> I've understood your query and intent correctly):
>
> SELECT discipline.name, COUNT(showcase_id) AS total
> FROM discipline
> LEFT JOIN (
>     SELECT DISTINCT discipline_id, showcase.id as showcase_id
>     FROM showcase
>     JOIN showcase_item on (showcase.id = showcase_id)
>     WHERE accepted) AS accepted_showcases
>         ON (discipline.id = discipline_id)
> GROUP BY discipline.name
> ORDER BY discipline.name = 'other'
>     , discipline.name;
>         name         | total
> ---------------------+-------
> community           |     0
> dance               |     0
> film and television |     0
> fine craft          |     0
> media arts          |     0
> music               |     0
> theatre             |     0
> visual arts         |     1
> writing             |     2
> other               |     0
> (10 rows)
>

That's bang on, Michael, thanks a bunch. I never remember to explore
joining on a select. I'm forever thinking in terms of joining on a
table. Things to study this evening.

> As a general rule, it's generally better to let the server handle the
> data in sets (i.e., tables) as much as possible rather than using
> procedural code.
>
> Hope this helps.

It helped lots, thanks again.

brian

Re: ORDER BY with exception

От
Erik Jones
Дата:
On Jun 21, 2007, at 8:08 PM, brian wrote:

> Michael Glaesemann wrote:
>> On Jun 21, 2007, at 17:35 , brian wrote:
>>> I have a lookup table with a bunch of disciplines:
>> To answer your ordering question first:
>> SELECT id, name
>> FROM discipline
>> ORDER BY name = 'other'
>>     , name;
>> id |        name
>> ----+---------------------
>>   8 | community
>>   4 | dance
>>   5 | film and television
>>   9 | fine craft
>>   7 | media arts
>>   3 | music
>>   6 | theatre
>>   2 | visual arts
>>   1 | writing
>> 10 | other
>> (10 rows)
>> This relies on the fact that FALSE orders before TRUE. I don't
>> always  remember which way, so I often have to rewrite it using <>
>> or = to  get the behavior I want.
>
> Of course! (slaps forehead)
>
>> I don't think you really need to use a function for this. I
>> believe  you should be able to do this all in one SQL statement,
>> something  like (if I've understood your query and intent correctly):
>> SELECT discipline.name, COUNT(showcase_id) AS total
>> FROM discipline
>> LEFT JOIN (
>>     SELECT DISTINCT discipline_id, showcase.id as showcase_id
>>     FROM showcase
>>     JOIN showcase_item on (showcase.id = showcase_id)
>>     WHERE accepted) AS accepted_showcases
>>         ON (discipline.id = discipline_id)
>> GROUP BY discipline.name
>> ORDER BY discipline.name = 'other'
>>     , discipline.name;
>>         name         | total
>> ---------------------+-------
>> community           |     0
>> dance               |     0
>> film and television |     0
>> fine craft          |     0
>> media arts          |     0
>> music               |     0
>> theatre             |     0
>> visual arts         |     1
>> writing             |     2
>> other               |     0
>> (10 rows)
>
> That's bang on, Michael, thanks a bunch. I never remember to
> explore joining on a select. I'm forever thinking in terms of
> joining on a table. Things to study this evening.

One way to break yourself of that habit is to stop thinking in terms
of tables when you query and replace that thinking with relations.
Queries are made on relations and tables are only one kind of
relation.  Then just remember that the results of select queries are
relations representing relationships between data in other relations
so they can themselves be used in select queries (as well as updates,
deletes and, as of 8.2, insert  and copy statements).

Erik Jones

Software Developer | Emma®
erik@myemma.com
800.595.4401 or 615.292.5888
615.292.0777 (fax)

Emma helps organizations everywhere communicate & market in style.
Visit us online at http://www.myemma.com