Delete data from child tables - sql

I have 2 tables:
"customers" and "addresses". A customer can have several addresses, so they have an "n:m" relationship.
For this reason, I also have the table "customer-addr".
This is how my tables look like:
+---------------+
+-----------+ | customer_addr |
| customers | +---------------+ +-----------+
+-----------+ | id | | addresses |
| id | <---> | cid | +-----------+
| name | | aid | <---> | id |
+-----------+ +---------------+ | address |
+-----------+
I need to update all customer-data incl. all addresses. For this reason I thought about deleting all existing addresses first, then updating the customer-table, and after that, I create every address new.
My question: How can I delete all existing addresses from one customer efficiently? (I have to remove rows from 2 tables).
Is there a single-statement I can use? (Without the cascade-method, this is too risky)
Or can I do it with 2 statements, without using subselects?
What's the best approach for this?
Notice that I'm using postgresql
Edit:
My whole database-design is more complex, and the address-table is not only a child from "customers" but also from "suppliers","bulkbuyers",..
Every address belongs to only one customer OR one supplier OR one bulkbuyer.
(No address is used by more than one parent / no address-sharing)
Ever customer/supplier/.. can have multiple addresses.
For this reason, the edited solution from zebediah49 won't work, because it would also delete all addresses from every supplier/bulkbuyer/...

I would use a writable CTE also called data-modifying CTE in PostgreSQL 9.1 or later:
WITH del AS (
DELETE FROM customer_addr
WHERE cid = $kill_this_cid
RETURNING aid
)
DELETE FROM addresses a
USING (SELECT DISTINCT aid FROM del) d
WHERE a.id = d.aid;
This should be fastest and safest.
If (cid, aid) is defined UNIQUE in customer_addr you don't need the DISTINCT step:
...
DELETE FROM addresses a
USING del d
WHERE a.id = d.aid;

EDIT:
Got it; this is safer because of the risk of two customers sharing an address anyway:
DELETE FROM customer_addr WHERE cid = $TARGET_CID;
DELETE FROM addresses WHERE id NOT IN (SELECT aid FROM customer_addr);
First, delete all references, then delete all unreferenced addresses.
Note that you could, for example, only do the first step, and run the "cleanup" second step at a later time.
I would suggest a two step transaction:
DELETE FROM addresses WHERE id IN (SELECT ca.aid FROM customers c LEFT JOIN customer_addr ca ON ca.cid=c.id WHERE c.name='$NAME_TO_DELETE');
DELETE FROM customer_addr WHERE cid = (SELECT id FROM customers WHERE name='$NAME_TO_DELETE');
If you have the customer ID already (EDIT: You do), you can skip most of that:
DELETE FROM addresses WHERE id IN (SELECT aid FROM customer_addr WHERE cid=$TARGET_CID);
DELETE FROM customer_addr WHERE cid = $TARGET_CID;
Wrap those with the appropriate transactional BEGIN/END, to make sure that you don't end up in an inconsistent state, and you should be set.

Related

How to get sum of values per id and update existing records in other table

I have two tables like:
ID | TRAFFIC
fd56756 | 4398
645effa | 567899
894fac6 | 611900
894fac6 | 567899
and
USER | ID | TRAFFIC
andrew | fd56756 | 0
peter | 645effa | 0
john | 894fac6 | 0
I need to get SUM ("TRAFFIC") from first table AND set column traffic to the second table where first table ID = second table ID. ID's from first table are not unique, and can be duplicated.
How can I do this?
Table names from your later comment. Chances are, you are reporting table and column names incorrectly.
UPDATE users u
SET "TRAFFIC" = sub.sum_traffic
FROM (
SELECT "ID", sum("TRAFFIC") AS sum_traffic
FROM stats.traffic
GROUP BY 1
) sub
WHERE u."ID" = sub."ID";
Aside: It's unwise to use mixed-case identifiers in Postgres. Use legal, lower-case identifiers, which do not need to be double-quoted, to make your life easier. Start by reading the manual here.
Something like this?
UPDATE users t2 SET t2.traffic = t1.sum_traffic FROM
(SELECT sum(t1.traffic) t1.sum_traffic FROM stats.traffic t1)
WHERE t1.id = t2.id;

Group by non-scalar value

Given a one-to-many relationship between Person and Item
Person Item
------- ------
Id <-----. Id
Name `---- PersonId
Label
Where there are may people and Item.Label takes few distinct values, it might make sense to adopt an equivalent schema:
Person List Item
-------- ------ ------
Id .--> Id <--. Id
ListId --` `-- ListId
Name Label
That way many people can share the same list.
The migration from second schema to the first is trivial. My question is, how to migrate from the first schema to the second?
The challenge is to pick exactly one representative Person for each possible outcome of
SELECT Label FROM Item WHERE PersonId = ?
I was able to solve the problem by using FOR XML present in MS SQL server. That is,
SELECT P.Id, (SELECT Label FROM Item WHERE PersonId = P.Id FOR XML) list
FROM Person P
and then simply SELECT MIN(P.Id) FROM ... GROUP BY list to collect representatives. I'm unsatisfied with this workaround though and wish to find a more pure solution.
edit:
SELECT p.Id, q.Id FROM Person p, Person q
WHERE NOT EXISTS ( --symmetric difference between
(SELECT Label FROM Item WHERE PersonId = p.Id) --and
(SELECT Label FROM Item WHERE PersonId = q.id))
Should be the equivalence relation of Persons, for which representatives need to be found. I still wouldn't know how to finish, and this does seem rather inefficient.
It depends! I suggest you to stick your model to your business logic.
If people own pre-mades sets of items it makes senses to create a table to hold that logic.
Consider people can own just "home edition", "pro edition" or "std edition".
It makes sense to create a relational table between Edition_Items that way that edition can contain items (A,B),(A,B,C,D) and (A,C) for example.
And you can make a relational table between People and Edition it owns. At your scenario if that editions are "customized" editions, even if you got two to contain the same set of items you can consider they are different sets (just because they are owned by different people).
So that "Assembled Set" table can be used as a relational table between people and items.
Edit:
OP comment enforces my last statement.
So your "List" table can be a relational table between People and items.
|People | |List| |List_Item| |Item|
|-------| |----| |---------| |----|
|P1, L1 | | L1 | | L1, I1 | |I1 |
|P2, L2 | | L2 | | L1, I2 | |I2 |
| L3 | | L2, I1 | |I3 |
| L4 | | L2, I1 |
Seeing it you can ask, why keep a List table? That's use full if that List got some properties like: isDeleted, Description, CreateTime, etc
And the final question is? We put a reference of list on people or a reference of people in the list (or create another relational table?)
It depenses on:
1) People List is a 1-1 relation?
2) Who comes first? (egg and chicken problem?)
That's usually better questioning: Who can exist without the other.

Recursively duplicating entries

I am attempting to duplicate an entry. That part isn't hard. The tricky part is: there are n entries connected with a foreign key. And for each of those entries, there are n entries connected to that. I did it manually using a lookup to duplicate and cross reference the foreign keys.
Is there some subroutine or method to duplicate an entry and search for and duplicate foreign entries? Perhaps there is a name for this type of replication I haven't stumbled on yet, is there a specific database related title for this type of operation?
PostgreSQL 8.4.13
main entry (uid is serial)
uid | title
-----+-------
1 | stuff
department (departmentid is serial, uidref is foreign key for uid above)
departmentid | uidref | title
--------------+--------+-------
100 | 1 | Foo
101 | 1 | Bar
sub_category of department (textid is serial, departmentref is foreign for departmentid above)
textid | departmentref | title
-------+---------------+----------------
1000 | 100 | Text for Foo 1
1001 | 100 | Text for Foo 2
1002 | 101 | Text for Bar 1
You can do it all in a single statement using data-modifying CTEs (requires Postgres 9.1 or later).
Your primary keys being serial columns makes it easier:
WITH m AS (
INSERT INTO main (<all columns except pk>)
SELECT <all columns except pk>
FROM main
WHERE uid = 1
RETURNING uid AS uidref -- returns new uid
)
, d AS (
INSERT INTO department (<all columns except pk>)
SELECT <all columns except pk>
FROM m
JOIN department d USING (uidref)
RETURNING departmentid AS departmentref -- returns new departmentids
)
INSERT INTO sub_category (<all columns except pk>)
SELECT <all columns except pk>
FROM d
JOIN sub_category s USING (departmentref);
Replace <all columns except pk> with your actual columns. pk is for primary key, like main.uid.
The query returns nothing. You can return pretty much anything. You just didn't specify anything.
You wouldn't call that "replication". That term usually is applied for keeping multiple database instances or objects in sync. You are just duplicating an entry - and depending objects recursively.
Aside about naming conventions:
It would get even simpler with a naming convention that labels all columns signifying "ID of table foo" with the same (descriptive) name, like foo_id. There are other naming conventions floating around, but this is the best for writing queries, IMO.

'Implicit' JOIN based on schema's foreign keys?

Hello all :) I'm wondering if there is way to tell the database to look at the schema and infer the JOIN predicate:
+--------------+ +---------------+
| prices | | products |
+--------------+ +---------------+
| price_id (PK)| |-1| product_id(PK)|
| prod_id |*-| | weight |
| shop | +---------------+
| unit_price |
| qty |
+--------------+
Is there a way (preferably in Oracle 10g) to go from:
SELECT * FROM prices JOIN product ON prices.prod_id = products.product_id
to:
SELECT * FROM pricesIMPLICIT JOINproduct
The closest you can get to not writing the actual join condition is a natural join.
select * from t1 natural join t2
Oracle will look for columns with identical names and join by them (this is not true in your case). See the documentation on the SELECT statement:
A natural join is based on all columns in the two tables that have the same name. It selects rows from the two tables that have equal values in the relevant columns. If two columns with the same name do not have compatible data types, then an error is raised
This is very poor practice and I strongly recommend not using it on any environment
You shouldnt do that. Some db systems allow you to but what if you modify the fk's (i.e. add foreign keys)? You should always state what to join on to avoid problems. Most db systems won't even allow you to do an implicit join though (good!).

Save new id into old table

I want to move data from these old tables
restaurant_id | restaurant_nm | more data
bar_id | bar_nm | more data
To
venue_id | venue_nm
I'll add field venue_id to the old tables
Then I want to run a query similar to this:
INSERT INTO `venue` (SELECT null, `restaurant_nm` FROM `restaurant`)
However, while do the copy I want the new id to be stored into the old table. Is this possible with pure mysql?
Edit The old restaurants can be chains (multiple messy joe's), the only thing that identifies them 100% is the id
You could temporarily store the old ID in the new table (in an extra column) and then do an UPDATE on the old table. That's two lines of 'pure SQL.'
restaurant_id |restaurant_name | v_id
venue_id | venue_name | rest_id
INSERT INTO `venue` (SELECT null, `restaurant_nm`, `restaurant_id` FROM `restaurant`)
and then
UPDATE restaurant r
INNER JOIN venue v
ON r.restaurant_id = v.rest_id
SET r.v_id = v.venue_id
Interested to see what a more elegant solution might be.