Forum OpenACS Q&A: SQL: how do you do this?

Collapse
Posted by Jade Rubick on
I have a data modelling question. I have a table like this:
create table foo_vf (
       id                       integer primary key,
       from_v                   not null references user_groups,
       to_f                     not null references f,
       default_q                references foo_vf_quantity,
       selected_q               references foo_vf_quantity,
       unique (from_v, to_f)
);

create table foo_vf_quantity (
       id                       integer primary key
       quantity                 number not null, 
       cost                     number,
       notes                    varchar(2000),
       vf                       not null references foo_vf,
       last_user_modifying      not null references users,
       last_modified            date not null
);
Obviously, this doesn't work. I can't create the first table because it references the second table, and I can't create the second table because it references the first.

Is this bad practice?

Should I create the table, and then alter it to add the additional columns?

BTW, I know this isn't in normal form, etc... In this particular application, it seems appropriate.

Perhaps I should put the selected_f and default_f columns in another table?

What do you recommend?

Collapse
Posted by David Walker on
Try as I might I can't seem to understand what you are trying to do.

It looks like an items and an item_quantity table with default_q and selected_q representing the default quantity and selected quantity and I can't fathom why that would need to reference the quantity table. I would presume the quantity table would contain the current quantity.

Perhaps you can expand on this a bit.
Collapse
Posted by Michael Bryzek on
Circular referential constraints certainly have their uses. The way to create the constraints in Oracle is as you describe, though you can just add the constraint later:
create table foo_vf_quantity (
       id                       integer primary key,
       quantity                 number not null, 
       cost                     number,
       notes                    varchar(2000),
       vf                       integer not null,
       last_user_modifying      not null references users,
       last_modified            date not null
);

create table foo_vf (
       id                       integer primary key,
       from_v                   not null references user_groups,
       to_f                     not null references f,
       default_q                references foo_vf_quantity,
       selected_q               references foo_vf_quantity,
       unique (from_v, to_f)
);

alter table foo_vf_quantity add foreign key (vf) references foo_vf(id);
Regarding selected and default columns - without understanding the problem you are modelling, it seems odd to have both a default and a selected value in the foo_vf table, esp. w/out any explicit constraints on their values.

Again, without understanding much about what you're really trying to do, another possible way to structure the tables would be to add a status column to the foo_vf_quantity table. Now, you can remove the reference from foo_vf to the quantity table. This schema would look like:


create table foo_vf (
       id                       integer primary key,
       from_v                   not null references user_groups,
       to_f                     not null references f,
       unique (from_v, to_f)
);

create table foo_vf_quantity (
       id                       integer primary key,
       quantity                 number not null, 
       cost                     number,
       notes                    varchar(2000),
       vf                       integer not null references foo_vf,
       last_user_modifying      not null references users,
       last_modified            date not null,
       status                   varchar(10) check (status in ('default', 'selected'))
);
Collapse
Posted by Jade Rubick on
Very helpful. Thank you!

What I am trying to do is build a listing of freight tables, FROM
somewhere, TO somewhere. The quantity shipped and the price
are related, and it's okay if deleting a price also removed the
quantity.

The first table is a FROM and TO location. The second table is a
listing of prices and quantities shipped.

The issue is that they would like to have a record for arbitrary
quantities. So if they ship 3000 pounds, it will be one price, and
5000 pounds will be another price. They'd then like to be able to
list all of these.

Of the possible shipping quantities, they'd like to have one be
the "default" shipping quantity and price, and one be the currently
"selected" shipping quantity and price.

Does adding in the circular reference make sense in this case?
Putting the status in the quantity (second) table wouldn't make
sense, because only zero or one of the many freight quantities
should be "selected" and zero or one of the freight quantities
should be "default".

Something about it looked really awkward to me, so I asked you
guys for feedback.

Thanks for your help!

Collapse
Posted by Jonathan Ellis on
I'm still confused...

Seems to me that the _vf to _vf_quantity relationship is many-to-one.  So I don't understand why you'd need the _vf_quantity to have a key back to the _vf table, unless it were an entirely different _vf from the one already keying to it but that doesn't seem to be the case from your description.  (If it is instead many-to-many you'd need a separate mapping table but you already know that which is why I'm confused. :)

second, what kind of state is selected_q maintaining vs default_q?  Is this like the "user default" and "factory default?"  if so selected_q should probably be in a table keyed off user to provide more flexibility.  But if you really want one global value it is fine as it is.

Collapse
Posted by David Walker on
Are you designing tables to look up rates? or tables to record
freight shipped and the cost of that?
Collapse
Posted by Daryl Biberdorf on
Jade,

You've posed an interesting problem. It's made an eensy bit more complicated in that the names of columns and tables don't really communicate what it's for.

Your original question has already been answered by Michael Bryzek -- just add the final referential constraint after BOTH tables have been created.

A couple of people have questioned why you'd ever have a circular situation like this. My own classic example of this is the notion of component->subcomponent. In the case of a car, you might have something like:

  Transmission (component)
     |
     v
  Clutch assembly--+  (component AND subcomponent)
     |             |
     v             v
  Clutch disc     Pressure plate

A person has the the option of buying an entire clutch assembly or just the clutch disc. In order to define prices (and other descriptive information, like where the parts originate, etc.) for the both components AND subcomponents while keeping the structure, you'd have to break it into at least two entities, with the circular references between them.

Back to the problem at hand, if I understand this correctly, shipments from a given origin to a given destination have a variety of prices (based on quantity) that can be assigned. One rate in the rate chart is marked as the "default" (e.g., most people send 3000 pounds of whatever at a time on that route, so we'll make that the standard). Another is what is "selected" (Jade wants to send 10,000 pounds, so we'll cut a good deal).

The problem as I see it is that the default_q attribute goes with the shipping route (foo_vf), while selected_q goes with a particular shipment along that route. If I understand this correctly, you actually need a third entity.

     create table foo_vf_quantity (
            id                       integer not null primary key,
            quantity                 number not null, 
            cost                     number,
            notes                    varchar(2000),
            vf                       integer not null,
            last_user_modifying      not null references users,
            last_modified            date not null
     );
     comment on table foo_vf_quantity is 'Pricing for available route';

     create table foo_vf (
            id                       integer primary key,
            from_v                   not null references user_groups,
            to_f                     not null references f,
            default_q                references foo_vf_quantity,
            unique (from_v, to_f)
     );
     comment on table foo_vf is 'Available routes';

     alter table foo_vf_quantity add foreign key (vf) references foo_vf(id);

     create table foo_shipment (
            shipment_id              integer not null primary key,
            route_id                 integer not null 
                                     references foo_vf( id ),
            selected_price_id        integer not null 
                                     references foo_vf_quantity( id ),
            ...
      ); 
      comment on table foo_shipment is 'Shipment for a particular route';

In this case, the selected price has moved to the foo_shipment table. One uses foo_vf to see what routes are available, foo_vf_quantity to see the pricing options for that route, and foo_shipment to actually ship something, recording the price option chosen.

It's possible I'm completely off-base in my understanding here. If so, then I apologize for muddying the waters further.

Collapse
Posted by Jade Rubick on
Wow, I guess I really muddied the waters. I should have been more clear from the get-go.

I'm tracking freight costs for particular shipping routes. The first table tracks the particular shipping route (A->B), and the second table tracks all the various volumes shipped, and the cost for sending those volumes.

Here's what I've come up with:

-- tracks costs for freight from a vendor to a facility
create table freight_vendorfacility (
       id			integer primary key,
       -- from vendor to facility
       from_vendor		not null references user_groups,
       to_facility		not null references facility,
       default_quantity		integer,
       selected_quantity	integer,
       unique (from_vendor, to_facility)
);

create table freight_vend_fac_quantity (
       id			integer primary key,
       -- thousands of pounds
       quantity			number not null, 
       cost			number,
       notes			varchar(2000),
       vendor_facility		not null references freight_vendorfacility,
       last_user_modifying	not null references users,
       last_modified		date not null
);

alter table freight_vendorfacility add foreign key (default_quantity) 
references freight_vend_fac_quantity(id);

alter table freight_vendorfacility add foreign key 
(selected_quantity) references freight_vend_fac_quantity(id);

That's the real, non-obfuscated version, at least what I have so far. I'm still working out the details. The selected and defaults are currently set globally.

You have multiple freight routes, and multiple volumes for each route. Example:

ROUTE 
A->B  

volume: 5000 lbs
cost: $1.00/lb

volume: 10000 lbs
cost: $0.98/lb

volume: 15000 lbs
cost: $0.95/lb

One of these should be the default value, and one should be the currently selected value. Both can be changed.

It seems to me that in this circumstance, using circular constraints makes sense. It's a first for me.

Collapse
Posted by Daryl Biberdorf on
Jade,

Seeing actual names of things makes it easier to keep the relationships in mind. I'm still a bit puzzled by the exact problem you're trying to solve, in the absence of some key details. For example, you mention that "second table tracks all the various volumes shipped, and the cost for sending those volumes." This would imply that the second table records actual shipments, not available prices for future shipments. In that case, I think this model is probably denormalized such that it won't represent everything.

However, as I said, I'm not sure I understand the whole thing. I'll bow out for now, in the absence of more detail. I'd hate to make things worse.