SQL Select Where Opposite Match Does Not Exist - sql

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
)

Related

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

SQL Server find unique combinations

I have a table
rate_id service_id
1 1
1 2
2 1
2 3
3 1
3 2
4 1
4 2
4 3
I need to find and insert in a table the unique combinations of sevice_ids by rate_id...but when the combination is repeated in another rate_id I do not want it to be inserted
In the above example there are 3 combinations
1,2 1,3 1,2,3
How can I query the first table to get the unique combinations?
Thanx!
Try doing something like this:
DECLARE #TempTable TABLE ([rate_id] INT, [service_id] INT)
INSERT INTO #TempTable
VALUES (1,1),(1,2),(2,1),(2,3),(3,1),(3,2),(4,1),(4,2),(4,3)
SELECT DISTINCT
--[rate_id], --include if required
(
SELECT
CAST(t2.[service_id] AS VARCHAR) + ' '
FROM
#TempTable t2
WHERE
t1.[rate_id] = t2.[rate_id]
ORDER BY
t2.[rate_id]
FOR XML PATH ('')
) AS 'Combinations'
FROM
#TempTable t1
I put the values in a table variable just for ease of testing the SELECT query.

how to remove duplicate records but need to keep their child table rows and tag them to survived row

can you please help me in writing the query for both table
database : sql server
master_table
primary name
1 a
2 a
3 a
4 b
5 b
6 c
7 c
foreign
key
reference
above table
1 aa
2 aaa
3 aaaa
4 bb
5 bbb
6 cc
7 ccc
expected output
now I need to remove duplicate from above table based upon name
after removing duplicates name
master_table
primary name
1 a
4 b
6 c
to remove duplicate records but need to keep their child table rows and tag them to survived row
foreign
key
reference
above table
fk name_city
1 aa
1 aaa
1 aaaa
4 bb
4 bbb
6 cc
6 ccc
can you please help me in writing the query for both table
database : sql server
Thanks Gordon Linoff for reply
Let me add more detail
how I think it can be done
added rownum to master_table based upon duplicated on name
primary name row_num
1 a 1
2 a 2
3 a 3
4 b 1
5 b 2
6 c 1
7 c 2
foreign
key
reference
above
table
fk name_city (map_name |get primarykey from above
based | table with joining condition
upon |map_name=name
matching |and rownum = 1)
fk
with
primary )
-----------------------------------------------------------------------
1 aa a 1
2 aaa a 1
3 aaaa a 1
4 bb b 4
5 bbb b 4
6 cc c 6
7 ccc c 6
Please suggest if this is the right way
Thanks a lot for your time and kind
CREATE TABLE #master (ID INT, Name VARCHAR(50)); --DROP TABLE #master
INSERT INTO #master VALUES (1, 'a')
INSERT INTO #master VALUES (2, 'a')
INSERT INTO #master VALUES (3, 'a')
INSERT INTO #master VALUES (4, 'b')
INSERT INTO #master VALUES (5, 'b')
INSERT INTO #master VALUES (6, 'c')
INSERT INTO #master VALUES (7, 'c')
-- create temporary mapping table
;WITH cte AS
(
SELECT ID, MIN(ID) OVER (PARTITION BY Name) AS [MinID]
FROM #master
)
SELECT *
INTO #TempMapping -- DROP TABLE #TempMapping
FROM cte
WHERE cte.ID <> cte.MinID;
-- check to make sure that the IDs mapped as expected
SELECT * FROM #TempMapping;
-- change FKed values to their respective MIN mappings
UPDATE nc
SET nc.fk = tmp.MinID
FROM name_city nc
INNER JOIN #TempMapping tmp
ON tmp.ID = nc.fk;
-- remove non-MIN IDs from master now that nothing references them
DELETE mstr
FROM #master mstr
INNER JOIN #TempMapping tmp
ON tmp.ID = mstr.ID;
If there are a lot of rows in the [name_city] table or concurrency issues (i.e. blocking), then the #TempMapping table should probably be a real table (e.g. "dbo.TempMasterMappings") instead of a temp table. At that point, you can do this one ID at a time in a loop to keep the transactions smaller and quicker. Just replace the UPDATE and DELETE queries above with the following (which can even be run from a stored procedure). This method will work for any number of millions of rows (assuming that there is an index on the [fk] field, which there should be anyway).
DECLARE #BatchSize INT; -- this can be an input param for a proc
SET #BatchSize = 5000;
DECLARE #CurrentIDtoChange INT,
#CurrentNewID INT;
BEGIN TRY
WHILE (1 = 1)
BEGIN
SELECT TOP (1)
#CurrentIDtoChange = map.ID,
#CurrentNewID = map.MinID
FROM dbo.TempMasterMappings map
ORDER BY map.ID ASC;
IF (##ROWCOUNT = 0)
BEGIN
DROP TABLE dbo.TempMasterMappings; -- clean up!
BREAK; -- exit outer loop
END;
WHILE (1 = 1)
BEGIN
UPDATE TOP (#BatchSize) nc
SET nc.fk = #CurrentNewID
FROM dbo.name_city nc
WHERE nc.fk = #CurrentIDtoChange
OPTION (OPTIMIZE FOR UNKNOWN);
IF (##ROWCOUNT = 0)
BEGIN
DELETE mstr -- clean up PK record
FROM dbo.[Master] mstr
WHERE mstr.ID = #CurrentIDtoChange;
DELETE tmm -- remove ID as it is fully migrated!
FROM dbo.TempMasterMappings tmm
WHERE tmm.ID = #CurrentIDtoChange;
BREAK; -- exit inner loop
END;
END;
END TRY
BEGIN CATCH
DECLARE #Message NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR(#Message, 16, 1);
END CATCH;
You need to replace all the ids in the second table with the minimum matching id in the first, if I understand correctly.
This query should return the result set you want:
select mt.minid, name_city
from (select t.*, min(id) over (partition by name) as minid
from master_table t
) mt join
table2 t2
on t2.id = t.id;
It is unclear from the question whether you just want to get the right output, or whether you want to modify the tables. Updating the tables would basically be changing the above select to a similar update query and then deleting the extra rows from the master table.

In postgresql, how can I fill in missing values within a column?

I'm trying to figure out how to fill in values that are missing from one column with the non-missing values from other rows that have the same value on a given column. For instance, in the below example, I'd want all the "1" values to be equal to Bob and all of the "2" values to be equal to John
ID # | Name
-------|-----
1 | Bob
1 | (null)
1 | (null)
2 | John
2 | (null)
2 | (null)
`
EDIT: One caveat is that I'm using postgresql 8.4 with Greenplum and so correlated subqueries are not supported.
CREATE TABLE bobjohn
( ID INTEGER NOT NULL
, zname varchar
);
INSERT INTO bobjohn(id, zname) VALUES
(1,'Bob') ,(1, NULL) ,(1, NULL)
,(2,'John') ,(2, NULL) ,(2, NULL)
;
UPDATE bobjohn dst
SET zname = src.zname
FROM bobjohn src
WHERE dst.id = src.id
AND dst.zname IS NULL
AND src.zname IS NOT NULL
;
SELECT * FROM bobjohn;
NOTE: this query will fail if more than one name exists for a given Id. (and it won't touch records for which no non-null name exists)
If you are on a postgres version >-9, you could use a CTE to fetch the source tuples (this is equivalent to a subquery, but is easier to write and read (IMHO). The CTE also tackles the duplicate values-problem (in a rather crude way):
--
-- CTE's dont work in update queries for Postgres version below 9
--
WITH uniq AS (
SELECT DISTINCT id
-- if there are more than one names for a given Id: pick the lowest
, min(zname) as zname
FROM bobjohn
WHERE zname IS NOT NULL
GROUP BY id
)
UPDATE bobjohn dst
SET zname = src.zname
FROM uniq src
WHERE dst.id = src.id
AND dst.zname IS NULL
;
SELECT * FROM bobjohn;
UPDATE tbl
SET name = x.name
FROM (
SELECT DISTINCT ON (id) id, name
FROM tbl
WHERE name IS NOT NULL
ORDER BY id, name
) x
WHERE x.id = tbl.id
AND tbl.name IS NULL;
DISTINCT ON does the job alone. Not need for additional aggregation.
In case of multiple values for name, the alphabetically first one (according to the current locale) is picked - that's what the ORDER BY id, name is for. If name is unambiguous you can omit that line.
Also, if there is at least one non-null value per id, you can omit WHERE name IS NOT NULL.
If you know for a fact that there are no conflicting values (multiple rows with the same ID but different, non-null names) then something like this will update the table appropriately:
UPDATE some_table AS t1
SET name = (
SELECT name
FROM some_table AS t2
WHERE t1.id = t2.id
AND name IS NOT NULL
LIMIT 1
)
WHERE name IS NULL;
If you only want to query the table and have this information filled in on the fly, you can use a similar query:
SELECT
t1.id,
(
SELECT name
FROM some_table AS t2
WHERE t1.id = t2.id
AND name IS NOT NULL
LIMIT 1
) AS name
FROM some_table AS t1;

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);