Hi stackoverflow database design experts!
I'm facing a design problem in my database, and I've not found any similar issue in Stackoverflow, hence this question.
I have an image table, containing image data and it's primary key. In my design, each image can be referenced multiple time accross multiple tables.
Here is a representation of the database:
-------------------- -------------------------------------------
| image | | table1 |
|--------------------| |-------------------------------------------|
| id_image | data | | id_table1 | id_image | data |
|----------|---------| |-----------|----------|--------------------|
| 1 | Image 1 | | 1 | 1 | References image 1 |
| 2 | Image 2 | | 2 | 3 | References image 3 |
| 3 | Image 3 | -------------------------------------------
--------------------
-------------------------------------------
| table2 |
|-------------------------------------------|
| id_table2 | id_image | data |
|-----------|----------|--------------------|
| 1 | 2 | References image 2 |
| 2 | 2 | References image 2 |
| 3 | 3 | References image 3 |
-------------------------------------------
Here are the tables detail:
image table
id_image auto-incremented primary key
data image data
table1 table
id_table1 auto-incremented primary key
id_image foreign key referencing image.id_image
data table1 data
table2 table
id_table2 auto-incremented primary key
id_image foreign key referencing image.id_image
data table2 data
I want my database to behave as follows:
If I delete the table1 row with id_table1 = 1, the image row with id_image = 1 must be deleted (no other references to this image)
If I then delete the table2 row with id_table2 = 1, no image should be deleted (because the image with id_image = 2 is still referenced by the table2 row with id_table2 = 2)
If I then delete the table2 row with id_table2 = 2, the image row with id_image = 2 must be deleted (no other references to this image)
If I then delete the table1 row with id_table1 = 2, no image should be deleted (because the image with id_image = 3 is still referenced by the table2 row with id_table2 = 3)
If I then delete the table2 row with id_table2 = 3, the image row with id_image = 3 must be deleted (no other references to this image)
I've already tried some cascading delete, by inverting the foreign keys (i.e. image table containing id_table1 and id_table2 foreign keys), but if an image is referenced in 2 other tables, removing one referenced table entry also removes the image, which i do not want to happen.
I've also tried to define triggers, but this approach is more complex than I thought: each time I have to check among all foreign keys to id_image to see if there is another reference to the image to delete. This sample contains 2 foreign keys, but in the database I'm designing there will be more than 10...
I feel like there is a simple solution to this simple problem, anyone here to help me?
Thanks!
Right away because of your first requirement:
If I delete the table1 row with id_table1 = 1, the image row with
id_image = 1 must be deleted (no other references to this image)
I can tell you that you can only accomplish this with a TRIGGER. The reason is because you want to automatically delete from the Parent table when a row is deleted from the Child table.
The reverse (Delete child when parent is deleted) can be done with Cascading Foreign Keys, but not this.
You will need to put Triggers on both child tables to enforce the logic you want.
I came up with a better design yesterday. It still uses triggers (as Tab Alleman said), but those are much simplier to define:
-------------------- ---------------------------
| image | | image_proxy |
|--------------------| |---------------------------|
| id_image | data | | id_image_proxy | id_image |
|----------|---------| |----------------|----------|
| 1 | Image 1 | | 1 | 1 |
| 2 | Image 2 | | 2 | 3 |
| 3 | Image 3 | | 3 | 2 |
-------------------- | 4 | 2 |
| 5 | 3 |
---------------------------
-------------------------------------------------
| table1 |
|-------------------------------------------------|
| id_table1 | id_image_proxy | data |
|-----------|----------------|--------------------|
| 1 | 1 | References image 1 |
| 3 | 2 | References image 3 |
-------------------------------------------------
-------------------------------------------------
| table2 |
|-------------------------------------------------|
| id_table2 | id_image_proxy | data |
|-----------|----------------|--------------------|
| 1 | 3 | References image 2 |
| 2 | 4 | References image 2 |
| 3 | 5 | References image 3 |
-------------------------------------------------
As you can see in the schema above, I've introduced a new table: image_proxy:
id_image_proxy auto-incremented primary key
id_image foreign key referencing image.id_image
Also, table1 and table2 reference now an image_proxy entry instead of an image entry.
With this design, the triggers are now:
After deleting entries in table1, delete corresponding entries in image_proxy.
After deleting entries in table2, delete corresponding entries in image_proxy.
After deleting entries in image_proxy, delete entries in image that are not referenced anymore in image_proxy.
I don't know if this design is the best for this issue, nor if triggers usage is safe, that's why I'll keep an eye on this post if there is any better answer or relevent comment!
Related
I have a record that holds 2 license "keys" (actually GUIDs). When a request comes to our service it includes a key (GUID) in the request. I then do a query looking for a record that has this value in either the column Key1 or Key2.
The purpose of this is users will use Key1 for everything. Then they discover that Key1 has become public. So they switch to Key2 and then after 15 minutes, change the value of Key1. Now the old Key1 value is of no use.
By having the 2 keys, it allows the switch over with no downtime.
I need any key value to be unique. Not that any pair of values is unique. Not that a value in Key1 is unique in all rows for Key 1. But that a new value is unique in all rows.Key1 and rows.Key2.
Is there a way to force this in Sql Server. Or do I need to do this myself with a select before doing an insert or update?
-------------------------------------------------------------------------------------------
| LicenseId | ApiKey1 | APiKey2 |
| 1 | af53d192-7fa3-4be0-b3d4-7efe17a397b5 | 1a87cc4a-1941-4af7-aeaa-bf9690f47eef |
| 2 | 5bbc2d06-ed6f-4444-aa22-73820dd6f3f6 | c2bdd9d9-fd47-4727-83f8-02ed0e7537e1 |
| 3 | 8acfa8b4-aa4b-41a7-9d3d-b6ba1eac838e | 30c18f2d-5d89-4e5d-8e8e-2d2b647d6ab6 |
-------------------------------------------------------------------------------------------
I need to insure if I am going to create record LicenseId = 4, that if it has ApiKey2 = 'af53d192-7fa3-4be0-b3d4-7efe17a397b5', that the insert will fail because that guid is ApiKey1 for LicenseId = 1.
The most natural way to enforce this in the database is to put all keys in a single column. Eg
create table ApiKeys
(
LicenceId int,
KeyId int check (KeyId in (0,1)),
constraint pk_ApiKeys primary key (LicenceId,KeyId),
KeyGuid uniqueidentifier unique
)
Arguably having both the keys on the same row violates 1NF, and certainly your desire for uniqueness across the two column strongly suggests that they belong to a single domain.
So instead of storing ApiKey1 and ApiKey2 on the same row, you store them on two separate rows.
So instead of
---------------
| LicenseId | ApiKey1 | APiKey2 |
| 1 | af53d192-7fa3-4be0-b3d4-7efe17a397b5 | 1a87cc4a-1941-4af7-aeaa-bf9690f47eef |
| 2 | 5bbc2d06-ed6f-4444-aa22-73820dd6f3f6 | c2bdd9d9-fd47-4727-83f8-02ed0e7537e1 |
| 3 | 8acfa8b4-aa4b-41a7-9d3d-b6ba1eac838e | 30c18f2d-5d89-4e5d-8e8e-2d2b647d6ab6 |
-------------------------------------------------------------------------------------------
You would have:
----------------------------------------------------------
| LicenseId | KeyId | ApiKey |
| 1 | 0 | af53d192-7fa3-4be0-b3d4-7efe17a397b5|
| 1 | 1 | 1a87cc4a-1941-4af7-aeaa-bf9690f47ee4|
| 2 | 0 | 5bbc2d06-ed6f-4444-aa22-73820dd6f3f6|
| 2 | 1 | c2bdd9d9-fd47-4727-83f8-02ed0e7537e1|
| 3 | 0 | 8acfa8b4-aa4b-41a7-9d3d-b6ba1eac838e|
| 3 | 1 | 30c18f2d-5d89-4e5d-8e8e-2d2b647d6ab6|
----------------------------------------------------------
I am trying to remove old data from a SQL Server database, given a list of ID's, but I'm trying to figure out how to get it to run faster. Currently deleting a list of 250 ID's takes around 1 hour. These ID's are attached to our 'root' objects, example below. Each of these has foreign key constraints.
Products
| productID | description | price |
+-----------------+-------------------+-------------+
| 1 | item 1 | 5.00 |
| 2 | item 2 | 5.00 |
| 3 | item 3 | 5.00 |
| ... | ... | ... |
Sales
| saleID | productID |
+-----------------+-------------------+
| 4 | 1 |
| 5 | 2 |
| 6 | 3 |
| ... | ... |
Taxes
| taxID | saleID |
+-----------------+-------------------+
| 7 | 4 |
| 8 | 5 |
| 9 | 6 |
| ... | ... |
Currently, we are just passing a list of product ID's and cascading through manually, such as
DECLARE #ProductIDsRemoval AS TABLE { id int }
INSERT INTO #ProductIDsRemoval VALUES (1)
DELETE t
FROM dbo.Taxes t
INNER JOIN dbo.Sales s ON (s.saleID = t.saleID)
INNER JOIN #ProductIDsRemoval p ON (s.productID = p.id)
DELETE s
FROM dbo.Sales s
INNER JOIN #ProductIDsRemoval p ON (s.productID = p.id)
DELETE p
FROM dbo.Products p
INNER JOIN #ProductIDsRemoval p2 ON (p.productID = p2.id)
This works fine, however my issue is that my table structure has ~70 tables and at least a couple thousand rows in each to remove, if not a couple million. Currently, my query takes anywhere from 1 to 6 hours to run, depending on the number of base ID's we're removing (my structure doesn't actually use Products/Taxes/Sales, but it's a decent analogy, and the number we're aiming to remove is ~750 base ids, which we are estimating 3-5 hours for runtime)
I've seen other Stack Overflow answers saying to drop all constraints, add the on-cascade delete, and then re-add the constraints, but this also is taking quite a long time, as I would need to 1. Drop constraints. 2. Rebuild with on-cascade. 3. run my query. 4 drop constraints. 5 re-add without on-cascade.
I've also been looking at possibly just selecting everything I need into temp tables, truncating all of the other tables, and then re-inserting all of my values back and re-setting the indexes based on the last item I added, but again I would need to edit all foreign keys, which I would prefer to not do.
I have 2 tables, ShareButton and SharePage.
ShareButton table:
+----+---------------+---------------+
| ID | Name | TotalShare |
+----+---------------+---------------+
| 1 | Facebook | 0 |
| 2 | Twitter | 0 |
+----+---------------+---------------+
SharePage table:
+----+--------------------+-------+---------------+
| ID | URL | Share | ShareButtonID |
+----+--------------------+-------+---------------+
| 1 | www.abc.xyz/page1 | 3 | 1 |
| 2 | www.abc.xyz/page1 | 14 | 2 |
| 3 | www.abc.xyz/page2 | 6 | 1 |
| 4 | www.abc.xyz/page2 | 10 | 2 |
+----+--------------------+-------+---------------+
After insert or update a record in the SharePage table, TotalShare column of ShareButton is updated
update ShareButton
set TotalShare = (sum(Share) from SharePage where "ShareButtonID" = ShareButtonID of updated/inserted record))
where ID = ShareButtonID of updated/inserted record)`
Thank for reading!
Let me start my answer by saying I agree with Mureinik. Unless you have a really bad performance hit getting the sum of shares using a simple group by query, I wouldn't recommend saving that sum in the ShareButton table.
If you really want a trigger to calculate it, I guess the simplest way to do it is this:
CREATE TRIGGER trSharePage_Changed ON SharePage
FOR UPDATE, INSERT, DELETE
AS
UPDATE buttons
SET TotalShare = SumOfShares
FROM ShareButton buttons
INNER JOIN
(
SELECT ShareButtonID, SUM(Share) As SumOfShares
FROM SharePage
GROUP BY ShareButtonID
) pages ON buttons.ID = pages.ShareButtonID
Note that this trigger will be fired after any insert, update or delete statement on table SharePage will be completed. Since it's an after trigger, you don't need to deal with the inserted and deleted tables at all.
I have a table "temp"
author | title | bibkey | Data
-----------------------------------
John | JsPaper | John2008 | 65
Kate | KsPaper | | 60
| | Data2015 | 80
From this I want to produce two tables, a 'sample_table' and a 'ref_table' like so:
sample_table:
sample_id|ref_id| data
--------------------------
1 | 1 | 65
2 | 2 | 60
3 | 3 | 80
ref_table:
ref_id | author | title | bibkey
--------------------------------------
1 | John | JsPaper | John2008
2 | Kate | KsPaper |
3 | | | Data2015
I've created both tables
CREATE TABLE ref_table ( CREATE TABLE sample_table (
ref_id serial PRIMARY KEY, sample_id serial PRIMARY KEY,
author text, ref_id integer REFERENCES ref_table(ref_id),
title text, data numeric
bibkey text );
);
And inserted the unique author,title,bibkey rows into the reference table as above. What I want to do now is do the join for the sample_table to get the ref_id's. For my insert statement i currently have:
INSERT INTO sample_table (
ref_id,data
)
SELECT ref.ref_id, t.data
FROM
temp t
LEFT JOIN
ref_table ref ON COALESCE(ref.author,'00000') = COALESCE(t.author,'00000')
AND COALESCE(ref.title,'00000') = COALESCE(t.title,'00000')
AND COALESCE(ref.bibkey,'00000') = COALESCE(t.bibkey,'00000');
However i really want to have a conditional statement in the join, rather than all 3 like I have:
IF a bibkey exists for that row, I know it is unique, and join only on that.
If bibkey is NULL, then join on both author and title for the unique pair, and not bibkey.
Is this possible?
I have 3 tables. ID and Name is the primary key for the first table.
First Table
ID | Name | Date
----------------
1 | AA | 11/02
2 | BB | 04/10
ID, Name and Option are the primary key for second table:
Second Table
ID | Name | Option | SeqNo
---------------------------
3 | DD | LOVE | 1
4 | EE | SINGLE | 1
Option is the primary key for the third table:
Third Table
Option | Status
---------------
LOVE | Y
MARRIED| Y
SINGLE | N
After I join these tables, I will get like this.
ID | Name | Option | SeqNo | Status
------------------------------------
1 | AA | NULL | NULL | NULL
2 | BB | NULL | NULL | NULL
3 | CC | LOVE | 1 | Y
4 | DD | SINGLE | 1 | N
My question is, how to change the NULL value to a value contain in another table?
As an example, The Option column must be filled in with the value inside the third table. I'm using SQL Server 2005
This link describes how to replace null values in different sql engines, including sql server -
http://www.sqlines.com/oracle/functions/nvl
Basically, the syntax you are looking for is -
ISNULL(SeqNo, 'N/A')