Queriespart of SQL for Web Nerds by Philip Greenspun |
dual
table for times when you're
interested in a constant or a function:
... or to test your knowledge of three-valued logic (see the "Data Modeling" chapter):SQL> select 'Hello World' from dual; 'HELLOWORLD ----------- Hello World SQL> select 2+2 from dual; 2+2 ---------- 4 SQL> select sysdate from dual; SYSDATE ---------- 1999-02-14
(any expression involving NULL evaluates to NULL).SQL> select 4+NULL from dual; 4+NULL ----------
There is nothing magic about the dual
table for these
purposes; you can compute functions using the bboard
table
instead of dual
:
but not everyone wants 55010 copies of the same result. Theselect sysdate,2+2,atan2(0, -1) from bboard; SYSDATE 2+2 ATAN2(0,-1) ---------- ---------- ----------- 1999-01-14 4 3.14159265 1999-01-14 4 3.14159265 1999-01-14 4 3.14159265 1999-01-14 4 3.14159265 ... 1999-01-14 4 3.14159265 1999-01-14 4 3.14159265 1999-01-14 4 3.14159265 55010 rows selected.
dual
table is predefined during Oracle installation and,
though it is just a plain old table, it is guaranteed to contain only
one row because no user will have sufficient privileges to insert or
delete rows from dual
.
select * from users;
would retrieve all the information from every row of the
users
table. That's good for toy systems but in any
production system, you'd be better off starting with
You don't really want to look at 7352 rows of data, but you would like to see what's in the users table, start off by asking SQL*Plus to query Oracle's data dictionary and figure out what columns are available in theSQL> select count(*) from users; COUNT(*) ---------- 7352
users
table:
The data dictionary is simply a set of built-in tables that Oracle uses to store information about the objects (tables, triggers, etc.) that have been defined. Thus SQL*Plus isn't performing any black magic when you typeSQL> describe users Name Null? Type ------------------------------- -------- ---- USER_ID NOT NULL NUMBER(38) FIRST_NAMES NOT NULL VARCHAR2(100) LAST_NAME NOT NULL VARCHAR2(100) PRIV_NAME NUMBER(38) EMAIL NOT NULL VARCHAR2(100) PRIV_EMAIL NUMBER(38) EMAIL_BOUNCING_P CHAR(1) PASSWORD NOT NULL VARCHAR2(30) URL VARCHAR2(200) ON_VACATION_UNTIL DATE LAST_VISIT DATE SECOND_TO_LAST_VISIT DATE REGISTRATION_DATE DATE REGISTRATION_IP VARCHAR2(50) ADMINISTRATOR_P CHAR(1) DELETED_P CHAR(1) BANNED_P CHAR(1) BANNING_USER NUMBER(38) BANNING_NOTE VARCHAR2(4000)
describe
; it is simply querying
user_tab_columns
, a view of some of the tables in Oracle's
data dictionary. You could do the same explicitly, but it is a little
cumbersome.
Here we've had to make sure to put the table name ("USERS") in all-uppercase. Oracle is case-insensitive for table and column names in queries but the data dictionary records names in uppercase. Now that we know the names of the columns in the table, it will be easy to explore.column fancy_type format a20 select column_name, data_type || '(' || data_length || ')' as fancy_type from user_tab_columns where table_name = 'USERS' order by column_id;
TheSQL> select email from users where email like '%mit.edu'; EMAIL ------------------------------ philg@mit.edu andy@california.mit.edu ben@mit.edu ... wollman@lcs.mit.edu ghomsy@mit.edu hal@mit.edu ... jpearce@mit.edu richmond@alum.mit.edu andy_roo@mit.edu kov@mit.edu fletch@mit.edu lsandon@mit.edu psz@mit.edu philg@ai.mit.edu philg@martigny.ai.mit.edu andy@californnia.mit.edu ty@mit.edu teadams@mit.edu 68 rows selected.
email like '%mit.edu'
says "every row where the email
column ends in 'mit.edu'". The percent sign is Oracle's wildcard
character for "zero or more characters". Underscore is the wildcard for
"exactly one character":
Suppose that we notice in the above report some similar email addresses. It is perhaps time to try out the ORDER BY clause:SQL> select email from users where email like '___@mit.edu'; EMAIL ------------------------------ kov@mit.edu hal@mit.edu ... ben@mit.edu psz@mit.edu
Now we can see that this users table was generated by grinding over pre-ArsDigita Community Systems postings starting from 1995. In those bad old days, users typed their email address and name with each posting. Due to typos and people intentionally choosing to use different addresses at various times, we can see that we'll have to build some sort of application to help human beings merge some of the rows in the users table (e.g., all three occurrences of "philg" are in fact the same person (me)).SQL> select email from users where email like '%mit.edu' order by email; EMAIL ------------------------------ andy@california.mit.edu andy@californnia.mit.edu andy_roo@mit.edu ... ben@mit.edu ... hal@mit.edu ... philg@ai.mit.edu philg@martigny.ai.mit.edu philg@mit.edu
We've combined two restrictions in the WHERE clause with an AND. We can add another restriction with another AND:SQL> select count(*) from users where registration_date >= '1998-09-01' and registration_date < '1998-10-01'; COUNT(*) ---------- 920
OR and NOT are also available within the WHERE clause. For example, the following query will tell us how many classified ads we have that either have no expiration date or whose expiration date is later than the current date/time.SQL> select count(*) from users where registration_date >= '1998-09-01' and registration_date < '1998-10-01' and email like '%mit.edu'; COUNT(*) ---------- 35
select count(*) from classified_ads where expires >= sysdate or expires is null;
Conceptually, for each row in theselect user_id, email from users where 0 < (select count(*) from classified_ads where classified_ads.user_id = users.user_id); USER_ID EMAIL ---------- ----------------------------------- 42485 twm@meteor.com 42489 trunghau@ecst.csuchico.edu 42389 ricardo.carvajal@kbs.msu.edu 42393 gon2foto@gte.net 42399 rob@hawaii.rr.com 42453 stefan9@ix.netcom.com 42346 silverman@pon.net 42153 gallen@wesleyan.edu ...
users
table Oracle is
running the subquery against classified_ads
to see how many
ads are associated with that particular user ID. Keep in mind that this
is only conceptually; the Oracle SQL parser may elect to
execute this query in a more efficient manner.
Another way to describe the same result set is using EXISTS:
This may be more efficient for Oracle to execute since it hasn't been instructed to actually count the number of classified ads for each user, but only to check and see if any are present. Think of EXISTS as a Boolean function thatselect user_id, email from users where exists (select 1 from classified_ads where classified_ads.user_id = users.user_id);
Because of the JOIN restriction,select users.user_id, users.email, classified_ads.posted from users, classified_ads where users.user_id = classified_ads.user_id order by users.email, posted; USER_ID EMAIL POSTED ---------- ----------------------------------- ---------- 39406 102140.1200@compuserve.com 1998-09-30 39406 102140.1200@compuserve.com 1998-10-08 39406 102140.1200@compuserve.com 1998-10-08 39842 102144.2651@compuserve.com 1998-07-02 39842 102144.2651@compuserve.com 1998-07-06 39842 102144.2651@compuserve.com 1998-12-13 ... 41284 yme@inetport.com 1998-01-25 41284 yme@inetport.com 1998-02-18 41284 yme@inetport.com 1998-03-08 35389 zhupanov@usa.net 1998-12-10 35389 zhupanov@usa.net 1998-12-10 35389 zhupanov@usa.net 1998-12-10
where users.user_id =
classified_ads.user_id
, we only see those users who have posted
at least one classified ad, i.e., for whom a matching row may be found
in the classified_ads
table. This has the same effect as
the subquery above.
The order by users.email, posted
is key to making sure that
the rows are lumped together by user and then printed in order of
ascending posting time.
classified_ads
table.
The plus sign afterselect users.user_id, users.email, classified_ads.posted from users, classified_ads where users.user_id = classified_ads.user_id(+) order by users.email, posted; ... USER_ID EMAIL POSTED ---------- ----------------------------------- ---------- 52790 dbrager@mindspring.com 37461 dbraun@scdt.intel.com 52791 dbrenner@flash.net 47177 dbronz@free.polbox.pl 37296 dbrouse@enter.net 47178 dbrown@cyberhighway.net 36985 dbrown@uniden.com 1998-03-05 36985 dbrown@uniden.com 1998-03-10 34283 dbs117@amaze.net 52792 dbsikorski@yahoo.com ...
classified_ads.user_id
is our
instruction to Oracle to "add NULL rows if you can't meet this JOIN
constraint".
rooms
and
reservations
. The top level page is supposed to show
a user what reservations he or she is current holding:
This produces an unacceptable page because the rooms are referred to by an ID number rather than by name. The name information is in theselect room_id, start_time, end_time from reservations where user_id = 37
rooms
table, so we'll have to turn this into a JOIN.
We're in a world of hurt because Oracle is now going to join every row inselect room_id, start_time, end_time from reservations, rooms where user_id = 37
rooms
with every row in reservations
where
the user_id
matches that of the logged-in user.
select room_id, start_time, end_time from reservations, rooms where user_id = 37 and reservations.room_id = rooms.room_id
reservations
and rooms
contain columns
called "room_id". So we need to prefix the room_id
column
in the SELECT list with "reservations.". Note that we don't have to prefix
start_time
and end_time
because these columns
are only present in reservations
.
select reservations.room_id, start_time, end_time from reservations, rooms where user_id = 37 and reservations.room_id = rooms.room_id
room_name
to the list of columns for which we're querying.
select reservations.room_id, start_time, end_time, rooms.room_name from reservations, rooms where user_id = 37 and reservations.room_id = rooms.room_id