1

First let me describe the scenario:

Say I have two tables PRODUCTS and CUSTOMERS, which could be described as shown below:

PRODUCTS table:

+----------+
| PRODUCTS |
+----------+
| ID       |
| CARD_ID  |
+----------+

And CUSTOMERS table:

+-----------+
| CUSTOMERS |
+-----------+
| ID        |
| APP_ID    |
| CARD_ID   |
+-----------+

All these fields are of number type.

These two tables are fairly large and already contain good amount of data. In the CUSTOMERS table, the CARD_ID is the same CARD_ID from PRODUCTS table. There is a one-to-one relation between these tables, that is, only one CARD_ID can be used by one customer and no CARD_ID is used by more than one customer. But some customer may have not used any product yet, i.e. have null as CARD_ID.

I would like to add a back reference for CUSTOMERS table in our PRODUCTS table (for applications sake). So I did something like:

ALTER TABLE PRODUCTS ADD CUSTOMER_ID NUMBER DEFAULT -1 NOT NULL;

Now, as there are already existing data, I also need to write a migration query. That is, if a customer is using a card id C, then I need to add that customer id in C's customer id field for all such existing matches.

I have thought of using something like this, but my understanding is, the inner select query is an inner join here, so I am not sure if that will be efficient in this case.

I also though about using the "with" clause, but I think "with" only works with select queries. [UPDATE] Found this using "with" clause but is this a good way?

Help me to figure out how this should be done efficiently. For example I would not like to update the rows in PRODUCTS which is not being used by any customer at this moment.

Database: Oracle 12c.

Thanks in advance.

[EDIT] Just wanted to mention that, I ended up using PL/SQL for this. In PL/SQL this is quite easy. Also, easier for us to put that through a flyway migration system.

DECLARE

CURSOR C IS SELECT customer.APP_ID, customer.CARD_ID FROM CUSTOMERS customer INNER JOIN PRODUCTS prod on customer.CARD_ID = prod.CARD_ID;

BEGIN

FOR rec IN C
LOOP
    UPDATE PRODUCTS p SET p.CUSTOMER_ID = rec.APP_ID WHERE p.CUSTOMER_ID is null and p.CARD_ID = rec.CARD_ID;

END LOOP;

END;

Just yet another way of accomplishing stuffs. And this is suitable for my particular application. Is this the best way? May be, may be not!

Community
  • 1
  • 1
Zobayer Hasan
  • 2,187
  • 6
  • 22
  • 38
  • It would be helpful to know what problem you are trying to solve with this technique. – David Aldridge Nov 08 '15 at 12:35
  • Adding a small feature in a legacy software. I have seen similar queries on the net. Basically what I want is, update each rows of a table for each result row of a join query with another table. – Zobayer Hasan Nov 08 '15 at 12:40

2 Answers2

2

I agree that you should avoid updating the products table. I would even not add the customer_id column to it: it will not only make updates to the customers.card_id column take more time -- as it also requires an update on the products table at the same time -- it may even lead to inconsistency if the implementation is not watertight.

Instead I would suggest to create a database view that joins the customer_id with the products columns:

create view vw_products as
select      products.id, products.card_id, customers.customer_id
from        products
inner join  customers
        on  customers.card_id = products.card_id;

That way, you don't actually store the customer_id, but can select from this view instead of the original products table, getting what you want. Also, there is no migration to be done on the table data.

Even if there is a huge volume of data, this will perform well if you have indexes defined on the card_id columns in both tables.

If for some reason you really need to add the customer_id column to the products table, then update that new column as follows:

UPDATE products
SET    customer_id = (
    SELECT customer_id
    FROM   customers
    WHERE  customers.card_id = products.card_id);

NB: this database design looks counter-intuitive: a one-to-one relationship between customers and products is a guarantee for this business to go bankrupt.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Well you know when you are required to add some options on a legacy project and you are not allowed to do much change so that the already deployed application doesn't have to undergo massive changes. However, I like the idea of making a view. But I know this can be done with queries, is it possible if you can consider the tables just as table A and table B instead of customer and product? And I assure you, no one is going to go bankrupt :) – Zobayer Hasan Nov 08 '15 at 12:36
  • If with "queries" you mean an `update` statement to put the correct values in the *products.customer_id* field, then see that statement in my updated answer. If you just want a query that combines *product_id* with *customer_id* then please use the `select` that I provided in the `view` definition. If neither of these, please explain more clearly what the query needs to do. – trincot Nov 08 '15 at 12:48
  • Yes, something like that. Do I need to add an extra exists block to ensure that unmatched rows are not getting null? – Zobayer Hasan Nov 08 '15 at 12:54
  • 1
    On the contrary, you would want them to get *null*, which practically means: there is no matching *customers* record. Don't leave it at -1 as that is again counter-intuitive, and you will never be able to improve and put a foreign key constraint on that column if you leave the -1. – trincot Nov 08 '15 at 13:02
  • You have a good point there. An extra +1 for that. I am marking your answer as accepted answer. Thanks to everyone who tried to help. :) – Zobayer Hasan Nov 08 '15 at 13:03
0

smth is wrong in database design

Each customer can buy more than one product, each product can be bought by more than one customer, many-to-many relation, it would be better create new table customer_products. If you need add product - just insert it, updates will be pretty simple same as deletes

create table product(id int);
alter table PRODUCT
  add constraint product_pk primary key (ID);

create table CUSTOMERS(id int, app_id int, card_id int);
alter table CUSTOMERS
  add constraint CUSTOMERS_pk primary key (ID);

create table customer_products(product_id int, customer_id int, <...other fields if necessary>);

alter table CUSTOMER_PRODUCTS
  add constraint fk_customer_products_prod_id foreign key (PRODUCT_ID)
  references product (ID);

alter table CUSTOMER_PRODUCTS
  add constraint fk_customer_products_cust_id foreign key (customer_ID)
  references customer (ID);  
are
  • 2,535
  • 2
  • 22
  • 27
  • I can show you many cases where my situation is valid. Just because the names are customers and products doesn't mean each customer has to buy multiple products. For example, say this is a smart card, each customer can only have one card, and no two customer can share the same card. However, foreign keys can be added, but cannot do so many alteration in database. – Zobayer Hasan Nov 08 '15 at 10:40