How to avoid cross join - sql

I'm working on query which gives me history of records in db2 using
"FOR SYSTEM_TIME FROM '0001-01-01-00.00.00.000000' TO '9999-12-30-00.00.00.000000000000'".
Steps performed :
1) Added a record in 'NAME_TABLE' which contains 'SAMPLE_NAME' column.
2) Set the pk of the 'NAME_TABLE' as foreign key(NAME_FK) in another table called 'PREF_TABLE'.
3) Now, deleted that name from 'NAME_TABLE'. This results in deleting records from both the tables.
The problem I'm facing is that I'm getting cross join result.
Please help me to avoid cross joining.
Below are the transaction times for two actions performed.(insert and delete)
NAME_TABLE PREF_TABLE
15-Oct-2019 01:24:07 15-Oct-2019 01:24:17
15-Oct-2019 01:24:34 15-Oct-2019 01:24:17
15-Oct-2019 01:24:07 15-Oct-2019 01:24:34
15-Oct-2019 01:24:34 15-Oct-2019 01:24:34
Inserted into NAME_TABLE at 15-Oct-2019 01:24:07
Inserted into PREF_TABLE at 15-Oct-2019 01:24:17
deleted from both the tables at 15-Oct-2019 01:24:34
SELECT SAMPLE_NAME
FROM PREF_TABLE FOR SYSTEM_TIME FROM '0001-01-01-00.00.00.000000' TO
'9999-12-30-00.00.00.000000000000' PREN
INNER JOIN NAME_TABLE FOR SYSTEM_TIME FROM '0001-01-01-00.00.00.000000' TO
'9999-12-30-00.00.00.000000000000' PN ON PN.PK = PREN.NAME_FK
I expect the output of only two records i.e., for inserting and deleting.
But I'm getting 4 records instead.

CREATE TABLE NAME_TABLE
(
PK INT NOT NULL PRIMARY KEY,
SAMPLE_NAME INT,
sys_start TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS ROW BEGIN,
sys_end TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS ROW END,
ts_id TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS TRANSACTION START ID,
PERIOD SYSTEM_TIME (sys_start, sys_end)
) IN USERSPACE1;
CREATE TABLE NAME_TABLE_HIST LIKE NAME_TABLE IN USERSPACE1;
ALTER TABLE NAME_TABLE ADD VERSIONING USE HISTORY TABLE NAME_TABLE_HIST;
CREATE TABLE PREF_TABLE
(
FK INT NOT NULL,
SAMPLE_NAME INT,
sys_start TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS ROW BEGIN,
sys_end TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS ROW END,
ts_id TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS TRANSACTION START ID,
PERIOD SYSTEM_TIME (sys_start, sys_end),
CONSTRAINT PREF_TABLE_FK FOREIGN KEY (FK) REFERENCES NAME_TABLE (PK) ON DELETE CASCADE
) IN USERSPACE1;
CREATE TABLE PREF_TABLE_HIST LIKE PREF_TABLE IN USERSPACE1;
ALTER TABLE PREF_TABLE ADD VERSIONING USE HISTORY TABLE PREF_TABLE_HIST;
INSERT INTO NAME_TABLE (PK, SAMPLE_NAME) VALUES (1, 1);
INSERT INTO PREF_TABLE (FK, SAMPLE_NAME) VALUES (1, 1);
-- No rows in both tables
SELECT * FROM NAME_TABLE_HIST;
SELECT * FROM PREF_TABLE_HIST;
DELETE FROM NAME_TABLE;
-- One row in both tables
SELECT * FROM NAME_TABLE_HIST;
SELECT * FROM PREF_TABLE_HIST;
-- SELECT statement returns 1 row
SELECT PN.SAMPLE_NAME
FROM PREF_TABLE
FOR SYSTEM_TIME FROM '0001-01-01-00.00.00.000000' TO '9999-12-30-00.00.00.000000000000'
PREN
JOIN NAME_TABLE
FOR SYSTEM_TIME FROM '0001-01-01-00.00.00.000000' TO '9999-12-30-00.00.00.000000000000'
PN ON PN.PK = PREN.FK;
When you insert into a system temporal table, no rows inserted into the history table.
There are no duplicates so far.
You may have them, of course, if you insert / update the rows with the same ID in addition, but you should change the description of your sequence of steps to reproduce the problem and describe the desired result.
If you want to avoid duplicates, you may want to use
FOR SYSTEM_TIME AS OF 'timestamp_constant' (a "snapshot" of table contents at this time)
instead of
FOR SYSTEM_TIME FROM 'timestamp_constant1' TO 'timestamp_constant2'.
If you use the latter, I believe you should understand, that if you get more that one version of the same row, then it's expected to have such duplicates in the result set.

Related

Automatically remove a row without foreign references

I am using sqlite3.
I have one "currencies" table, and two tables that reference the currencies table using a foreign key, as follows:
CREATE TABLE currencies (
currency TEXT NOT NULL PRIMARY KEY
);
CREATE TABLE table1 (
currency TEXT NOT NULL PRIMARY KEY,
FOREIGN KEY(currency)
REFERENCES currencies(currency)
);
CREATE TABLE table2 (
currency TEXT NOT NULL PRIMARY KEY,
FOREIGN KEY(currency)
REFERENCES currencies(currency)
);
I would like to make sure that rows in the "currencies" table that are not referenced by any row from "table1" and "table2" will be removed automatically. This should behave like some kind of ref-counted object. When the reference count reaches zero, the relevant row from the "currencies" table should be erased.
What is the "SQL way" to solve this problem?
I am willing to redesign my tables if it could lead to an elegant solution.
I prefer to avoid solutions that require extra work from the application side, or solutions that require periodic cleanup.
Create an AFTER DELETE TRIGGER in each of table1 and table2:
CREATE TRIGGER remove_currencies_1 AFTER DELETE ON table1
BEGIN
DELETE FROM currencies
WHERE currency = OLD.currency
AND NOT EXISTS (SELECT 1 FROM table2 WHERE currency = OLD.currency);
END;
CREATE TRIGGER remove_currencies_2 AFTER DELETE ON table2
BEGIN
DELETE FROM currencies
WHERE currency = OLD.currency
AND NOT EXISTS (SELECT 1 FROM table1 WHERE currency = OLD.currency);
END;
Every time that you delete a row in either table1 or table2, the trigger involved will check the other table if it contains the deleted currency and if it does not contain it, it will be deleted from currencies.
See the demo.
There is no automatic way of doing this. The reverse can be handling using cascading delete foreign key references. The reverse is that when a currency is deleted all related rows are.
You could schedule a job daily running something like:
delete from currencies c
where not exists (select 1 from table1 t1 where t1.currency = c.currency) and
not exists (select 1 from table2 t2 where t2.currency = c.currency);
If you need an automatic way for doing that, then most dbms provide a trigger mechanism. You can create a trigger on update and delete operations that run the folowing query:
you can use a left join for that:
https://www.w3schools.com/sql/sql_join_left.asp
It return a row for all rows from the left table, even if there is no corresponding row in the right table, replacing the rows form the right with null. You can then check a not null right table field for null with is null. This will filter for the rows the have no counterpart in the right table.
For example:
SELECT currencies.currency FROM currencies LEFT JOIN table1 WHERE table1.currency IS NULL
will show the relevant rows for table1.
You can do the same with table two.
This will give you two queries, that shows which rows have no couterpart.
You can then use intersect on the result, so that you have the rows that have not couterpart in either:
SELECT * FROM query1 INTERSECT SELECT * FROM query2
Now you have the list of currencies to be deleted.
You can finish this by using a subqueried delete:
DELETE FROM currencies WHERE currency IN (SELECT ...)

Update tables in one database from multiple tables in another database regularly

I have 2 databases in SQL Server, DB1 has multiple tables and some of the tables are updated with new records continuously. DB2 has only 1 table which should contain all the combined info from the multiple tables in DB1, and needs to be updated every 2 hours.
For example, DB1 has 3 tables: "ProductInfo", "StationRecord", "StationInfo". The first 2 tables both have a timestamp column that indicates when a record is created (i.e. the two tables are updated asynchronously, ONLY when a product passes all stations in "StationRecord" will "ProductInfo" be updated with a new product), and the last table is fixed.
The tables are as follows:
USE DB1
GO
CREATE TABLE ProductInfo
ProductID bigint Primary Key NOT NULL
TimeCreated datetime
ProductName nvarchar(255)
CREATE TABLE StationRecord
RecordID bigint Primary Key NOT NULL
TimeCreated datetime
ProductID bigint NOT NULL
StationID bigint
CREATE TABLE StationInfo
StationID bigint Primary Key NOT NULL
BOM_used nvarchar(255)
DB2 has only 1 table which contains a composite PK of "ProductID" & "StationID", as follows:
CREATE TABLE DB2.BOMHistory AS
SELECT
DB1.ProductInfo.ProductID
DB1.ProductInfo.TimeCreated AS ProductCreated
DB1.StationInfo.StationID
DB1.StationInfo.BOM_used
FROM DB1.ProductInfo
JOIN DB1.StationRecord
ON DB1.ProductInfo.ProductID = DB1.StationRecord.ProductID
JOIN DB1.StationInfo
ON DB1.StationRecord.StationID = DB1.StationInfo.StationID
constraint PK_BOMHistory Primary Key (ProductID,StationID)
I figured out the timing portion which is to use create a job with some pre-set schedules, and the job is to execute a stored procedure. The problem is how to write the stored procedure properly, which has to do the following things:
wait for the last product to pass all stations (and the "stationInfo" table is updated fully)
find all NEW records generated in this cycle in the tables in DB1
combine the information of the 3 tables in DB1
insert the combined info into DB2.BOMHistory
Here's my code:
ALTER Procedure BOMHistory_Proc
BEGIN
SELECT
DB1.ProductInfo.ProductID,
DB1.ProductInfo.TimeCreated AS ProductCreated
DB1.StationInfo.StationID,
DB1.StationInfo.BOM_used
into #temp_BOMList
FROM DB1.ProductInfo
JOIN DB1.StationRecord
ON DB1.ProductInfo.ProductID = DB1.StationRecord.ProductID
JOIN DB1.StationInfo
ON DB1.StationRecord.StationID = DB1.StationInfo.StationID
ORDER BY ProductInfo.ProductID
END
SELECT * from #temp_BOMList
INSERT INTO DB2.BOMHistory(ProductID, ProductCreated, StationID, BOM_used)
SELECT DISTINCT (ProductID, stationID)
FROM #temp_BOMList
WHERE (ProductID, stationID) NOT IN (SELECT ProductID, stationID FROM DB2.BOMHistory)
The Condition in the INSERT statement is not working, please provide some advice.
Also, should I use a table variable or a temp table for this application?
Try:
INSERT INTO DB2.BOMHistory(ProductID, ProductCreated, StationID, BOM_used)
SELECT DISTINCT tb.ProductID, tb.ProductCreated, tb.StationID, tb.BOM_used
FROM #temp_BOMList tb
WHERE NOT EXISTS
(SELECT * FROM DB2.BOMHistory WHERE ProductID = tb.ProductID AND StationID = tb.StationID)

Why PostgreSQL CTE with DELETE is not working?

I was trying to delete a record from my stock table if the update in the same table results in quantity 0 using two CTEs.
The upserts are working, but the delete is not generating the result I was expecting. the quantity in stock table is changing to zero but the record is not being deleted.
Table structure:
CREATE TABLE IF NOT EXISTS stock_location (
stock_location_id SERIAL
, site_code VARCHAR(10) NOT NULL
, location_code VARCHAR(50) NOT NULL
, status CHAR(1) NOT NULL DEFAULT 'A'
, CONSTRAINT pk_stock_location PRIMARY KEY (stock_location_id)
, CONSTRAINT ui_stock_location__keys UNIQUE (site_code, location_code)
);
CREATE TABLE IF NOT EXISTS stock (
stock_id SERIAL
, stock_location_id INT NOT NULL
, item_code VARCHAR(50) NOT NULL
, quantity FLOAT NOT NULL
, CONSTRAINT pk_stock PRIMARY KEY (stock_id)
, CONSTRAINT ui_stock__keys UNIQUE (stock_location_id, item_code)
, CONSTRAINT fk_stock__stock_location FOREIGN KEY (stock_location_id)
REFERENCES stock_location (stock_location_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
This is how the statement looks like:
WITH stock_location_upsert AS (
INSERT INTO stock_location (
site_code
, location_code
, status
) VALUES (
inSiteCode
, inLocationCode
, inStatus
)
ON CONFLICT ON CONSTRAINT ui_stock_location__keys
DO UPDATE SET
status = inStatus
RETURNING stock_location_id
)
, stock_upsert AS (
INSERT INTO stock (
stock_location_id
, item_code
, quantity
)
SELECT
slo.stock_location_id
, inItemCode
, inQuantity
FROM stock_location_upsert slo
ON CONFLICT ON CONSTRAINT ui_stock__keys
DO UPDATE SET
quantity = stock.quantity + inQuantity
RETURNING stock_id, quantity
)
DELETE FROM stock stk
USING stock_upsert stk2
WHERE stk.stock_id = stk2.stock_id
AND stk.quantity = 0;
Does anyone know what's going on?
This is an example of what I'm trying to do:
DROP TABLE IF EXISTS test1;
CREATE TABLE IF NOT EXISTS test1 (
id serial
, code VARCHAR(10) NOT NULL
, description VARCHAR(100) NOT NULL
, quantity INT NOT NULL
, CONSTRAINT pk_test1 PRIMARY KEY (id)
, CONSTRAINT ui_test1 UNIQUE (code)
);
-- UPSERT
WITH test1_upsert AS (
INSERT INTO test1 (
code, description, quantity
) VALUES (
'01', 'DESC 01', 1
)
ON CONFLICT ON CONSTRAINT ui_test1
DO UPDATE SET
description = 'DESC 02'
, quantity = 0
RETURNING test1.id, test1.quantity
)
DELETE FROM test1
USING test1_upsert
WHERE test1.id = test1_upsert.id
AND test1_upsert.quantity = 0;
The second time the UPSERT command runs, it should delete the record from test1 once the quantity will be updated to zero.
Makes sense?
Here, DELETE is working in the way it was designed to work. The answer is actually pretty straightforward and documented. I've experienced the same behaviour years ago.
The reason your delete is not actually removing the data is because your where condition doesn't match with what's stored inside the table as far as what the delete statement sees.
All sub-statements within CTE (Common Table Expression) are executed with the same snapshot of data, so they can't see other statement effect on target table. In this case, when you run UPDATE and then DELETE, the DELETE statement sees the same data that UPDATE did, and doesn't see the updated data that UPDATE statement modified.
How can you work around that? You need to separate UPDATE & DELETE into two independent statements.
In case you need to pass the information about what to delete you could for example (1) create a temporary table and insert the data primary key that has been updated so that you can join to that in your latter query (DELETE based on data that was UPDATEd). (2) You could achieve the same result by simply adding a column within the updated table and changing its value to mark updated rows or (3) however you like it to get the job done. You should get the feeling of what needs to be done by above examples.
Quoting the manual to support my findings:
7.8.2. Data-Modifying Statements in WITH
The sub-statements in WITH are executed concurrently with each other
and with the main query. Therefore, when using data-modifying
statements in WITH, the order in which the specified updates actually
happen is unpredictable. All the statements are executed with the same
snapshot (see Chapter 13), so they cannot “see” one another's effects
on the target tables.
(...)
This also applies to deleting a row that was already updated in the same statement: only the update is performed
Adding to the helpful explanation above... Whenever possible it is absolutely best to break out modifying procedures into their own statements.
However, when the CTE has multiple modifying procedures that reference the same subquery and temporary tables are unideal (such as in stored procedures) then you just need a good solution.
In that case if you'd like a simple trick about how to go about ensuring a bit of order, consider this example:
WITH
to_insert AS
(
SELECT
*
FROM new_values
)
, first AS
(
DELETE FROM some_table
WHERE
id in (SELECT id FROM to_insert)
RETURNING *
)
INSERT INTO some_other_table
SELECT * FROM new_values
WHERE
exists (SELECT count(*) FROM first)
;
The trick here is the exists (SELECT count(*) FROM first) part which must be executed first before the insert can happen. This is a way (which I wouldn't consider too hacky) to enforce an order while keeping everything within one CTE.
But this is just the concept - there are more optimal ways of doing the same thing for a given context.

Update a table and insert missing records

I have a table with a foreign key column with some NULL records. I can select the records with missing column such as:
SELECT * FROM Outgoing WHERE Receipt_Id IS NULL
Now for each of these records I want to insert a new record in the table Receipts, get the inserted record's Id and set it as the value for Receipt_Id in this record.
Is this possible in a query?
It seems you are looking for inserted table
INSERT INTO Receipts (col1, col2....)
OUTPUT INSERTED.*
INTO #CreatedIds -- TEMP TABLE WHICH HOLDS RECENTLY INERTED DATA INCLUDING Receipt_Id (pk)
SELECT col1, col2....
FROM Outgoing
WHERE Receipt_Id IS NULL
To, see recently inserted records
SELECT c.*
FROM #CreatedIds c -- Note this is a table variable that you need to manual create.
Update: Since you are using Receipt table only as a sequence table. You should follow the updated approach which uses Sequences
Updated Answer:
All you need to do is to create a sequence say Receipts instead of a table with one column. And then update the Outgoing table with sequence numbers.
--create table Outgoing ( id int Primary Key IDENTITY(1,1),data nvarchar(100), record_id int);
--insert into Outgoing values ('john',NULL),('jane',NULL),('jean',NULL);
create sequence dbo.receipts as int start with 1 increment by 1;
update Outgoing
set record_id= NEXT VALUE FOR dbo.receipts
where record_id is null
select * from Outgoing
See working demo
Old Answer below
If you have ID column in both tables you can update Receipt_Id based on this column back into the Outgoing table
So you steps are :
1. insert records
DECLARE #LastRID bigint
SELECT #LastRID= MAX(Id) FROM Receipts
INSERT INTO Receipts(<col list>)
SELECT <col list> FROM Outgoing WHERE Receipt_Id IS NULL
update records based on uniqueness of all columns inserted from Outgoing to receipts using CHECKSUM function
update O
set O.Receipt_Id=R.Id
From Outgoing O
Join Receipts R
on CHECKSUM(o.<col list>)=CHECKSUM(R.<col list>)
and R.Id>#LastRID

SQL insert data into a table from another table

I'm having a problem trying to insert some values into a table. I made an empty table with the fields
id(primary key)
association_id
resource_id
I have another table with
resource_id
association_id
and another one with
id(coresponding to the association_id in the former one)
image
I want to insert the resource_id and association_id from the first populated table, where the image field of the coresponding id from the last table is not empty.
I tried this:
INSERT IGNORE INTO `logo_associations` (``,`association_id`,`resource_id`)
SELECT
``,
`a`.`association_id`,
`a`.`resource_id`
FROM doc24_associations_have_resources a
Join doc24_associations An on a.association_id = An.id
WHERE An.image<>''
but it does not work
Try this:
INSERT INTO logo_associations (association_id, resource_id)
SELECT a.association_id
,a.resource_id
FROM doc24_associations_have_resources a
LEFT JOIN doc24_associations an ON a.association_id = an.id
WHERE an.image IS NULL -- check for null with left join
This is valid for SQL Server. You do not need to select and insert the first column as it is an identity as you mention.
My experience is based on SQL Server but the SQL may be very similar
INSERT INTO DestinationTable
(association_id, resource_id)
SELECT LNK.assocication_id,
LNK.resource_id
FROM LinkTable AS LNK
INNER JOIN ImageTable AS IMG ON IMG.id = LNK.association_id
AND IMG.image IS NOT NULL
Above I assume the following:
Tables are named DestinationTable, LinkTable, and ImageTable respectively
In DestinationTable the primary key (id) is auto generated