SQL updating multiple rows in 1 table - sql

I have a question. I am using MS SQL Server Management Studio by the way.
I have a Dictionary table with a lot of translations. I need to copy a complete description from a languageID to another languageID.
Example below.
LanguageID | Description
2 | Some text
2 | More text
2 | Some more text
10 | *needs to be replaced
10 | *needs to be replaced
10 | *needs to be replaced
The result must be like this:
LanguageID | Description
2 | Some text
2 | More text
2 | Some more text
10 | Some text
10 | More text
10 | Some more text
The description of LanguageID 2 and 10 must be exactly the same.
My current Query runs into an error:
update tblDictionary
set Description = (Select Description from tblDictionary where
tblDictionary.LanguageID = 2)
where LanguageID = 10
Msg 512, Level 16, State 1, Line 1 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= ,
, >= or when the subquery is used as an expression. The statement has been terminated.

If all translations for LanguageID 10 must be exact the same as for languageID 2 then its easier to delete all translations for ID 10 and then insert them back again.
Something like this
delete from tblDictionary where LanguageID = 10;
insert into tblDictionary (LanguageID, Description)
select 10, d.Description
from tblDictionary d
where d.LanguageID = 2
This method also has the advantage that if there are less records with LanguageID = 10 then there are for LanguageID = 2 this will be corrected in the same process.
If you have more columns in tblDictionary than you will need to modify the insert statement off course

DECLARE #temp varchar(50)
DECLARE language_cursor CURSOR FOR
SELECT Description FROM tblDictionary
WHERE LanguageID = 2
ORDER BY Description;
OPEN language_cursor;
-- Perform the first fetch.
FETCH NEXT FROM language_cursor
into #temp;
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
update TOP (1) tblDictionary
set Description = #temp
where Description = ''
and LanguageID = 10;
FETCH NEXT FROM language_cursor
into #temp;
END
CLOSE language_cursor;
DEALLOCATE language_cursor;
Set all languageID 10 to empty first, then loop all description from languageID 2 to update into languageID 10 one by one until all empty description from languageID10 is filled.

Now if you really want an update, something like this should work, even though I think the structure of the table needs to be improved.
WITH l2 AS
(SELECT *,
ROW_NUMBER() OVER(PARTITION BY LanguageId ORDER BY Description ASC) AS No FROM tblDictionary WHERE LanguageId=2),
l10 AS
(SELECT *,
ROW_NUMBER() OVER(PARTITION BY LanguageId ORDER BY Description ASC) AS No FROM tblDictionary WHERE LanguageId=10)
UPDATE l10 SET Description = l2.Description
FROM l10
INNER JOIN l2 ON l10.No = l2.No

Related

Rule Table (With wild cards)

I have searched the web for something similar to my question, but have not found anything. I am looking for a way to apply "rules" with a SQL query.
My input data schema:
a: Int (NOT NULL)
b: Int (NOT NULL)
c: Int (NOT NULL)
The rule table schema:
a: Int (NULLABLE)
b: Int (NULLABLE)
c: Int (NULLABLE)
result: Int (NOT NULL)
There could be multiple rules which could "match" with the data. NULL represents a wildcard (could be any value). For example:
Input Data
a | b | c
1 | 2 | 3
Rules table:
a | b | c | result
1 | 2 | NULL| 99
1 | NULL| NULL| 101
1 | 2 | 3 | 203
When the rules are applied, it should match with the row which has the most matches (row 3 in this case).
I have come up with a query, which appears to be working, but it is not perfect. It can be slow if the "rules" table gets significant in size, and I'm worried there are edge cases I could be missing.
SELECT input.*,
COALESCE(rule.result, -1) as 'RuleResult'
FROM dbo.input input
OUTER APPLY (
SELECT TOP 1 result
FROM dbo.RuleTable rt
WHERE (input.a = rt.a OR rt.a IS NULL)
AND (input.b = rt.b OR rt.b IS NULL)
AND (input.c = rt.c OR rt.c IS NULL)
ORDER BY rt.a DESC, rt.b DESC, rt.c DESC
) rule
The idea is: The outer apply will run the query for each row in the input. The ORDER BY clause will set the priority of the rules, and will have the columns with the least number of NULL values at the top. The top row then becomes the result. The ORDER BY clause needs to align with the business need. If there is one rule with an 'a' value, and another with a 'b' value, an input row could match with two rules which only have a single matching condition. Then one still needs to be chosen.
My question: Is this query optimal? Am I missing anything? Are there resources about this out there I have not found? Is there a better way of doing this?
Update 1: After reading replies and discussing this with other people, here are some additional thoughts (still need to test these out):
Can we reverse the join? Basically join the rule table to the data
Can we eliminate input data that we know no rules apply to? Is this possible with NULLS (wildcard) values, or would this help?
Note: I'm still working through the thought process on this, so I may not be super clear yet.
Maybe the ORDER BY in the OUTER APPLY could use a small change
ORDER BY (iif(rt.a is null,0,1)
+iif(rt.b is null,0,1)
+iif(rt.c is null,0,1)) desc,
rt.a desc, rt.b desc, rt.c desc
Sample Data
create table input (a int, b int, c int);
insert into input values
(1,2,3),
(1,2,null),
(1,null,null),
(4,5,6);
create table RuleTable (a int, b int, c int, result int);
insert into RuleTable values
(1,2,null, 120),
(1,null,null, 100),
(1,2,3, 123),
(4,null,null, 400),
(null,5,6, 056);
Query
SELECT input.*
, COALESCE(ruled.result, -1) as RuleResult
FROM input input
OUTER APPLY (
SELECT TOP 1 result
FROM RuleTable as rt
WHERE (input.a = rt.a OR rt.a IS NULL)
AND (input.b = rt.b OR rt.b IS NULL)
AND (input.c = rt.c OR rt.c IS NULL)
ORDER BY (iif(rt.a is null,0,1)
+iif(rt.b is null,0,1)
+iif(rt.c is null,0,1)) desc,
rt.a desc, rt.b desc, rt.c desc
) ruled
ORDER BY a desc, b desc, c desc;
Result
a
b
c
RuleResult
4
5
6
56
1
2
3
123
1
2
null
120
1
null
null
100
Test on db<>fiddle here

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
)

Removing Duplicate Sets of Rows

Data
CREATE TABLE #tbl_LinkedInvoices(
InvoiceNbr varchar(50)
, AssociatedInvoiceNbr varchar(50)
, RowNbr int
, AssociatedRowNbr int
)
INSERT INTO #tbl_LinkedInvoices(
InvoiceNbr, AssociatedInvoiceNbr, RowNbr, AssociatedRowNbr)
VALUES
('A0001', 'A1001', 1, 4),
('A0002', 'A2002', 2, 5),
('A0002', 'A3002', 3, 6),
('A1001', 'A0001', 4, 1),
('A2002', 'A0002', 5, 2),
('A3002', 'A0002', 6, 3)
SELECT * FROM #tbl_LinkedInvoices
Challenge/Goal
tbl_LinkedInvoices is meant to identify the AssociatedInvoiceNbrs b an InvoiceNbr a is linked to. As such, a set can appear multiple times in the table since (a, b) = (b,a). To address these reappearances RowNbr and AssociatedRowNbr fields are added to give grouped sequences.
With the identified duplicate rows, remove duplicate row, preserving a single unique record in the table. Current script yields error, expect there might be a better way to write the query.
Script
Use a counter to check if the duplicate row still exists, if it does delete that row, till FALSE.
DECLARE #RowCounter int
DECLARE #RemoveRow int
SET #RowCounter = 1
IF EXISTS (SELECT
RowNbr
FROM #tbl_LinkedInvoices WHERE RowNbr = (SELECT AssociatedRowNbr FROM #tbl_LinkedInvoices)
)
BEGIN
SET #RemoveRow = (SELECT RowNbr FROM #tbl_LinkedINvoices
WHERE RowNbr = (
SELECT AssociatedRowNbr FROM #tbl_LinkedInvoices WHERE RowNbr =#RowCounter ))
BEGIN
DELETE FROM #tbl_LinkedInvoices
WHERE
RowNbr = #RemoveRow
END
BEGIN
SET #RowCounter = #RowCounter + 1
END
END
Error
Msg 512, Level 16, State 1, Line 212
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
If I followed you correctly, you can perform the deletion of "mirror" records in a single statement, without using the additional computed columns:
delete t
from #tbl_LinkedInvoices t
where exists (
select 1
from #tbl_LinkedInvoices t1
where
t1.AssociatedInvoiceNbr = t.InvoiceNbr
and t1.InvoiceNbr = t.AssociatedInvoiceNbr
and t1.AssociatedInvoiceNbr > t.AssociatedInvoiceNbr
)
This removes mirror records while retaining the one whose InvoiceNbr is smaller than AssociatedInvoiceNbr.
Demo on DB Fiddle with your sample data.
After the delete statement is executed, the content of the table is:
InvoiceNbr | AssociatedInvoiceNbr | RowNbr | AssociatedRowNbr
:--------- | :------------------- | -----: | ---------------:
A0001 | A1001 | 1 | 4
A0002 | A2002 | 2 | 5
A0002 | A3002 | 3 | 6

SQLite: How to update rows with a sequence of numbers?

In SQLIte I would like to renumber the values in a specific column with a sequence of numbers.
For example the relevance-column in these rows:
relevance | value
-------------------
3 | value1
5 | valueb
8 | valuex
9 | valueaa
must be updated starting from 1 with increment 1:
relevance | value
-------------------
1 | value1
2 | valueb
3 | valuex
4 | valueaa
What I'm looking for, is something like this:
-- first set all to startvalue
UPDATE MyTable SET relevance = 0;
-- then renumber:
UPDATE MyTable SET relevance = (some function to increase by 1 to the previous row);
I tried this, but its not increasing, seems like Max is not evaluating on each row:
UPDATE MyTable SET relevance = (SELECT Max(relevance ))+1;
First create a temporary table where you will insert the column relevance from your table and with ROW_NUMBER() window function another column with the new sequence and then update from this temporary table:
drop table if exists temp.tmp;
create temporary table tmp as
select relevance, row_number() over (order by relevance) rn
from MyTable;
update MyTable
set relevance = (
select rn from temp.tmp
where temp.tmp.relevance = MyTable.relevance
);
drop table temp.tmp;
See the demo.

Updating fields in sql incrementing by 1 each time

I am trying to construct an SQL query that would take a set of rows and renumber a field on all rows, starting at where they all match a session ID.
e.g.
before change:
SessionID | LineNumber | LineContents
----------------------------------------------
74666 | 1 | example content
74666 | 2 | some other content
74666 | 3 | another line
74666 | 4 | final line
after change (user has deleted line 2 so the 'LineNumber' values have updated to reflect the new numbering (i.e. line '3' has now become line '2' etc.):
SessionID | LineNumber | LineContents
----------------------------------------------
74666 | 1 | example content
74666 | 2 | another line
74666 | 3 | final line
So reflecting this in NON proper syntax would be something along these lines
i = 0;
UPDATE tbl_name
SET LineNumber = i++;
WHERE SessionID = 74666;
Searches a lot for this with no luck, any help is great :)
Using Row_Number() function and CTE:
;WITH CTE AS (
SELECT SessionID, LineNumber, LineContents,
Row_Number() OVER(PARTITION BY SessionID ORDER BY LineNumber) Rn
FROM Table1
)
UPDATE CTE
SET LineNumber = Rn
WHERE SessionID = 74666;
Fiddle Demo
You can use ROW_NUMBER ( MS SQL ) or ROWNUM ( Oracle ) or similar inside your UPDATE statement.
Check this
Or this
You have 2 main ways to do that.
The first "low level" way is this one (SQL Fiddle here):
DELETE FROM TestTable
WHERE SESSIONID = 74666 AND LineNumber = 3;
UPDATE TestTable SET LineNumber = LineNumber-1
WHERE SESSIONID = 74666 AND LineNumber > 3
select * from TestTable -- check the result
Here we're assuming you know both LineNumber and SessionID.
The other way is through t-SQL Triggers, a little more complex but it helps you if you don't know info about the rows you're deleting. Give it a try.
CREATE TABLE Trial (
-- ID INT,
SessionID INT ,
LineNumber INT ,
LineContent NVARCHAR(100)
)
INSERT INTO dbo.trial
VALUES ( 74666, 1, 'example content' ) ,
( 74666, 2, 'some other content' ),
( 74666, 4, 'another line' ),
( 74666, 5, 'final line' )
You can last deleted LineNumber value and can use that id in your update statement to update rest of the LineNumbers , for instance here Linenumber 3 is deleted so ,
UPDATE dbo.trial SET LineNumber = LineNumber -1 WHERE LineNumber > 3
I would be really inclined to handle this differently if at all possible, and have the Linenumber generated on the fly, to avoid having to maintain a column, e.g.:
CREATE TABLE dbo.T
(
LineNumberID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
SessionID INT NOT NULL,
LineContents NVARCHAR(MAX) NOT NULL
);
GO
INSERT dbo.T (SessionID, LineContents)
VALUES
(74666, 'example content'),
(74666, 'some other content'),
(74666, 'another line'),
(74666, 'final line');
GO
CREATE VIEW dbo.V
AS
SELECT LinenumberID,
SessionID,
Linenumber = ROW_NUMBER() OVER(PARTITION BY SessionID ORDER BY LinenumberID),
LineContents
FROM dbo.T;
GO
In this your View gives you what you need, and if I delete as follows:
SELECT *
FROM dbo.V;
DELETE dbo.V
WHERE SessionID = 74666
AND Linenumber = 3;
SELECT *
FROM dbo.V;
You get the output:
LINENUMBERID SESSIONID LINENUMBER LINECONTENTS
1 74666 1 example content
2 74666 2 some other content
3 74666 3 another line
4 74666 4 final line
LINENUMBERID SESSIONID LINENUMBER LINECONTENTS
1 74666 1 example content
2 74666 2 some other content
4 74666 3 final line
Example on SQL Fiddle
So you maintain your sequential linenumber field without actually having to update a field. This of course only works if you can rely on a field (such as CreatedDate, or an ID column) to order by. Otherwise you will have to maintain it using triggers, and update statements as suggested in other answers.