constraint to make further column data unique - sql

I have a table with some data. Now, I want to make a non unique column as unique.
But the thing is, I don't want to delete the duplicate data present in the table but want to restrict the data to be added in the table from being non unique.
To be practical:
I have a table tbl with name,age as columns.
I have data in the table as follows:
name |age
-----------------------
kaushikC |21
mohan |27
kumar |29
mohan |31
karthik |55
karthik |76
Now I want to make the name column unique without deleting the duplicate entry of 'mohan' and 'karthik'.
How to write such constraint

If you have a column in your table that allows you to identify the records that you don't want to change, such as an identity column or a create date, you can create a unique filtered index on the table, specifying in it's where clause that it should only include the other records in the table.
Suppose you have an identity column called id:
id | name |age
-----------------------
1 | kaushikC |21
2 | mohan |27
3 | kumar |29
4 | mohan |31
5 | karthik |55
6 | karthik |76
You could create a unique filtered index on this table that will only be valid for rows where the id is grater than 6:
CREATE UNIQUE INDEX UX_YourTable_Name_WhereIdGraterThanSix
ON YourTable (Name)
WHERE id > 6;
This will enable you to keep uniqueness of other names on the table - However, it will not prevent you from inserting one more duplicate for any existing name - so you could insert another mohan or another kumar to the table (but just one).
If you want to exclude all duplicates including duplicates of existing rows, your best option is probably to use an instead of trigger for insert and update:
CREATE TRIGGER tr_YourTable ON YourTable
INSTEAD OF INSERT, UPDATE
AS
BEGIN
-- the statement that fired the trigger is an update statement
IF EXISTS(select 1 FROM deleted)
BEGIN
UPDATE T
SET name = I.Name
FROM YourTable AS T
JOIN Inserted AS I
ON T.Id = I.Id
WHERE NOT EXISTS
( -- make sure the name is unique
SELECT 1
FROM YourTable AS T1
WHERE T1.Name = I.Name
AND NOT EXISTS
( -- unless it is going to be updated
SELECT 1
FROM Deleted AS D
JOIN Inserted AS I
ON D.Id = I.Id
WHERE D.Id = T1.Id
AND T1.Name = D.Name
AND D.Name <> I.Name
)
)
END
ELSE -- the statement that fired the trigger is an insert statement
BEGIN
INSERT INTO YourTable(Name)
SELECT I.Name
FROM Inserted I
WHERE NOT EXISTS
( -- make sure the name is unique
SELECT 1
FROM YourTable AS T1
WHERE T1.Name = I.Name
)
END
END

Related

Remove duplicate rows based on specific columns

I have a table that contains these columns:
ID (varchar)
SETUP_ID (varchar)
MENU (varchar)
LABEL (varchar)
The thing I want to achieve is to remove all duplicates from the table based on two columns (SETUP_ID, MENU).
Table I have:
id | setup_id | menu | label |
-------------------------------------
1 | 10 | main | txt |
2 | 10 | main | txt |
3 | 11 | second | txt |
4 | 11 | second | txt |
5 | 12 | third | txt |
Table I want:
id | setup_id | menu | label |
-------------------------------------
1 | 10 | main | txt |
3 | 11 | second | txt |
5 | 12 | third | txt |
You can achieve this with a common table expression (cte)
with cte as (
select id, setup_id, menu,
row_number () over (partition by setup_id, menu, label) rownum
from atable )
delete from atable a
where id in (select id from cte where rownum >= 2)
This will give you your desired output.
Common Table Expression docs
Assuming a table named tbl where both setup_id and menu are defined NOT NULL and id is the PRIMARY KEY.
EXISTS will do nicely:
DELETE FROM tbl t0
WHERE EXISTS (
SELECT FROM tbl t1
WHERE t1.setup_id = t0.setup_id
AND t1.menu = t0.menu
AND t1.id < t0.id
);
This deletes every row where a dupe with lower id is found, effectively only keeping the row with the smallest id from each set of dupes. An index on (setup_id, menu) or even (setup_id, menu, id) will help performance with big tables a lot.
If there is no PK and no reliable UNIQUE (combination of) column(s), you can fall back to using the ctid. If NULL values can be involved, you need to specify how to deal with those.
Consider:
Delete duplicate rows from small table
How to delete duplicate rows without unique identifier
How do I (or can I) SELECT DISTINCT on multiple columns?
After cleaning up duplicates, add a UNIQUE constraint to prevent new dupes:
ALTER TABLE tbl ADD CONSTRAINT tbl_setup_id_menu_uni UNIQUE (setup_id, menu);
If you had an index on (setup_id, menu), drop that now. It's superseded by the UNIQUE constraint.
I have found a solution that fits me the best.
Here it is if anyone needs it:
DELETE FROM table_name
WHERE id IN
(SELECT id
FROM
(SELECT id,
ROW_NUMBER() OVER( PARTITION BY setup_id,
menu
ORDER BY id ) AS row_num
FROM table_name ) t
WHERE t.row_num > 1 );
link: https://www.postgresql.org/docs/current/queries-union.html
https://www.postgresql.org/docs/current/sql-select.html#SQL-DISTINCT
let's sat table name is a
select distinct on (setup_id,menu ) a.* from a;
Key point: The DISTINCT ON expression(s) must match the leftmost ORDER BY expression(s). The ORDER BY clause will normally contain additional expression(s) that determine the desired precedence of rows within each DISTINCT ON group.
Which means you can only order by setup_id,menu in this distinct on query scope.
Want the opposite:
EXCEPT returns all rows that are in the result of query1 but not in the result of query2. (This is sometimes called the difference between two queries.) Again, duplicates are eliminated unless EXCEPT ALL is used.
SELECT * FROM a
EXCEPT
select distinct on (setup_id,menu ) a.* from a;
You can try something along these lines to delete all but the first row in case of duplicates (please note that this is not tested in any way!):
DELETE FROM your_table WHERE id IN (
SELECT unnest(duplicate_ids[2:]) FROM (
SELECT array_agg(id) AS duplicate_ids FROM your_table
GROUP BY SETUP_ID, MENU
HAVING COUNT(*) > 1
)
)
)
The above collects the ids of the duplicate rows (COUNT(*) > 1) in an array (array_agg), then takes all but the first element in that array ([2:]) and "explodes" the id values into rows (unnest).
The outer query just deletes every id that ends up in that result.
For mysql the similar question is already answered here Find and remove duplicate rows by two columns
Try if any of the approach helps in this matter.
I like the below one for MySql:
ALTER IGNORE TABLE your_table ADD UNIQUE (SETUP_ID, MENU);
DELETE t1
FROM table_name t1
join table_name t2 on
(t2.setup_id = t1.setup_id or t2.menu = t1.menu) and t2.id < t1.id
There are many ways to find and delete all duplicate row(s) based on conditions. But I like inner join method, which works very fast even in a large amount of Data. Please check follows :
DELETE T1 FROM <TableName> T1
INNER JOIN <TableName> T2
WHERE
T1.id > T2.id AND
T1.<ColumnName1> = T2.<ColumnName1> AND T1.<ColumnName2> = T2.<ColumnName2>;
In your case you can write as follows :
DELETE T1 FROM <TableName> T1
INNER JOIN <TableName> T2
WHERE
T1.id > T2.id AND
T1.setup_id = T2. setup_id;
Let me know if you face any issue or need more help.

SQL Select Where Opposite Match Does Not Exist

Trying to compare between two columns and check if there are no records that exist with the reversal between those two columns. Other Words looking for instances where 1-> 3 exists but 3->1 does not exist. If 1->2 and 2->1 exists we will still consider 1 to be part of the results.
Table = Betweens
start_id | end_id
1 | 2
2 | 1
1 | 3
1 would be added since it is a start to an end with no opposite present of 3,1. Though it did not get added until the 3rd entry since 1 and 2 had an opposite.
So, eventually it will just return names where the reversal does not exist.
I then want to join another table where the number from the previous problem has its name installed on it.
Table = Names
id | name
1 | Mars
2 | Earth
3 | Jupiter
So results will just be the names of those that don't have an opposite.
You can use a not exists condition:
select t1.start_id, t1.end_id
from the_table t1
where not exists (select *
from the_table t2
where t2.end_id = t1.start_id
and t2.start_id = t1.end_id);
I'm not sure about your data volume, so with your ask, below query will supply desired result for you in Sql Server.
create table TableBetweens
(start_id INT,
end_id INT
)
INSERT INTO TableBetweens VALUES(1,2)
INSERT INTO TableBetweens VALUES(2,1)
INSERT INTO TableBetweens VALUES(1,3)
create table TableNames
(id INT,
NAME VARCHAR(50)
)
INSERT INTO TableNames VALUES(1,'Mars')
INSERT INTO TableNames VALUES(2,'Earth')
INSERT INTO TableNames VALUES(3,'Jupiter')
SELECT *
FROM TableNames c
WHERE c.id IN (
SELECT nameid1.nameid
FROM (SELECT a.start_id, a.end_id
FROM TableBetweens a
LEFT JOIN TableBetweens b
ON CONCAT(a.start_id,a.end_id) = CONCAT(b.end_id,b.start_id)
WHERE b.end_id IS NULL
AND b.start_id IS NULL) filterData
UNPIVOT
(
nameid
FOR id IN (filterData.start_id,filterData.end_id)
) AS nameid1
)

Oracle -- Update the exact column referenced in the ON clause

I think this requirement is rarely encountered so I couldn't search for similar questions.
I have a table that needs to update the ID. For example ID 123 in table1 is actually supposed to be 456. I have a separate reference table built that stores the mapping (e.g. old 123 maps to new id 456).
I used the below query but apparently it returned error 38104, columns referenced in the ON clause cannot be updated.
MERGE INTO table1
USING ref_table ON (table1.ID = ref_table.ID_Old)
WHEN MATCHED THEN UPDATE SET table.ID = ref_table.ID_New;
Is there other way to achieve my purpose?
Thanks and much appreciated for your answer!
Use the ROWID pseudocolumn:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TABLE1( ID ) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL;
CREATE TABLE REF_TABLE( ID_OLD, ID_NEW ) AS
SELECT 1, 4 FROM DUAL UNION ALL
SELECT 2, 5 FROM DUAL;
MERGE INTO TABLE1 dst
USING ( SELECT t.ROWID AS rid,
r.id_new
FROM TABLE1 t
INNER JOIN REF_TABLE r
ON ( t.id = r.id_old ) ) src
ON ( dst.ROWID = src.RID )
WHEN MATCHED THEN
UPDATE SET id = src.id_new;
Query 1:
SELECT * FROM table1
Results:
| ID |
|----|
| 4 |
| 5 |
| 3 |
You can't update a column used in the ON clause in a MERGE. But if you don't need to make other changes that MERGE allows like WHEN NOT MATCHED or deleting, etc. you can just use a UPDATE to achieve this.
You mentioned this is an ID that needs an update. Here's an example using a scalar subquery. As it is an ID, this presumes UNIQUE ID_OLD values in REF_TABLE. I wasn't sure if Every row needs an update or only a sub-set, so set the update here to only update rows that have a value in REF_TABLE.
CREATE TABLE TABLE1(
ID NUMBER
);
CREATE TABLE REF_TABLE(
ID_OLD NUMBER,
ID_NEW NUMBER
);
INSERT INTO TABLE1 VALUES (1);
INSERT INTO TABLE1 VALUES (2);
INSERT INTO TABLE1 VALUES (100);
INSERT INTO REF_TABLE VALUES (1,10);
INSERT INTO REF_TABLE VALUES (2,20);
Initial State:
SELECT * FROM TABLE1;
ID
1
2
100
Then make the UPDATE
UPDATE TABLE1
SET TABLE1.ID = (SELECT REF_TABLE.ID_NEW
FROM REF_TABLE
WHERE REF_TABLE.ID_OLD = ID)
WHERE TABLE1.ID IN (SELECT REF_TABLE.ID_OLD
FROM REF_TABLE);
2 rows updated.
And check the change:
SELECT * FROM TABLE1;
ID
10
20
100

Prevent B & A entry in SQL when A & B already exists

I have a table that contains two rows: AccountID and PartnerAccountID. I need to prevent duplicates across both columns. Meaning, if an entry exists:
| AccountID | PartnerAccountID |
| 1 | 2 |
I need to make sure that the following can't also exist:
| AccountID | PartnerAccountID |
| 2 | 1 |
Any way to do that in a constraint?
It would be nice if you could create a unique index on an expression:
create unique index unq_t_AccountID_PartnerAccountID
on t((case when AccountID < PartnerAccountID then AccountId else PartnerAccountID end),
(case when AccountID < PartnerAccountID then PartnerAccountIDelse AccountId end)
);
But you can do almost the same thing by creating the columns as computed columns and then creating the index:
alter table t add minid as (case when AccountID < PartnerAccountID then AccountId else PartnerAccountID end);
alter table t add maxid as (case when AccountID < PartnerAccountID then PartnerAccountIDelse AccountId end);
create unique index unq_t_minid_maxid on t(minid, maxid);
An approach could also be to create an instead-of trigger which simply ignores such duplicates across two columns; The advantage would be that transactions do not need to be aborted if the information is already stored (yet the other way round). Here is an attempt of how such a trigger could look like:
CREATE TRIGGER tr_t ON t
INSTEAD OF INSERT
AS
BEGIN
INSERT t
SELECT AccountID, PartnerAccountID
FROM inserted
where not exists (select * from t2 where t2.AccountID = ParnterAccountID and t2.ParnterAccountId = AccountID);
END

Update single data row from table to another

How can I update a complete data row by using a data row from another table.
Example:
Table A
ID | NAME | ... |
----------------------------
1 | Test | ... |
2 | Test2 | ... |
Table B
ID | NAME | ... |
----------------------------
1 | Test97 | ... |
So I want to copy the content of a single row of Table B to Table A and override existing values. I do not want to name all columns. The contents of table A and B are redundant.
Summarize:
I want an equivalent to the following INSERT Statement as an UPDATE Statement:
INSERT INTO destTable
VALUES (SELECT * FROM TABLE2)
FROM srcTable
Any hint, even telling me that its not possible, is very appricated.
you can update a set of columns (you still have to list the columns once):
SQL> UPDATE table_a
2 SET (ID, NAME, etc)
3 = (SELECT * FROM table_b WHERE table_b.id = table_a.id)
4 WHERE table_a.id IN (SELECT ID FROM table_b);
1 row updated
Like so:
UPDATE suppliers
SET supplier_name = ( SELECT customers.name
FROM customers
WHERE customers.customer_id = suppliers.supplier_id)
WHERE EXISTS
( SELECT customers.name
FROM customers
WHERE customers.customer_id = suppliers.supplier_id);
You want to use an Oracle MERGE statement. With this statement, it inserts if a match doesn't exist and updates if it already exists.
Here is a site with an example.
MERGE INTO bonuses b
USING (
SELECT employee_id, salary, dept_no
FROM employee
WHERE dept_no =20) e
ON (b.employee_id = e.employee_id)
WHEN MATCHED THEN
UPDATE SET b.bonus = e.salary * 0.1
DELETE WHERE (e.salary < 40000)
WHEN NOT MATCHED THEN
INSERT (b.employee_id, b.bonus)
VALUES (e.employee_id, e.salary * 0.05)
WHERE (e.salary > 40000);