Updating fields in sql incrementing by 1 each time - sql

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.

Related

SQL Server Pivot/Map column values to rows

I've made schema changes/improvements to a table, but I need to ensure that I don't lose any existing data and it is 'migrated' across to the new schema and conforms to its design.
The existing schema is designed as follows:
ID FK_ID ShowChartX ShowChartY ShowChartZ
-- ----- ---------- ---------- ----------
1 2 1 0 1
The columns of ShowChartX, ShowChartY, and ShowChartZ are of type BIT (boolean).
I've now created a standalone table that keeps a record/reference of each chart. Each Chart record has a Chart_ID - the aim here is to use an ID for each type of chart instead of horizontally scaling a 'ShowChart' column for each type of chart going forward. Essentially, I would like to map all columns of 'ShowChart' to their actual Chart_ID key in the table I mention below:
The new schema would look like this:
ID FK_ID Chart_ID
-- ----- --------
1 2 1
2 2 2
I've started looking at Pivot/Unpivot, but I'm not sure if it's the correct operation. Could anyone please point me in the right direction here? Thanks in advance!
This will UNPIVOT the data. You can also, join the charts table by name in order to get the chart_id and check for differences with the new table:
DECLARE #DataSource TABLE
(
[ID] INT
,[FK_ID] INT
,[ShowChartX] BIT
,[ShowChartY] BIT
,[ShowChartZ] BIT
);
INSERT INTO #DataSource ([ID], [FK_ID], [ShowChartX], [ShowChartY], [ShowChartZ])
VALUES (1, 2, 1, 0, 1);
SELECT [ID]
,[FK_ID]
,[column] AS [chart_name]
FROM #DataSource DS
UNPIVOT
(
[value] FOR [column] IN ([ShowChartX], [ShowChartY], [ShowChartZ])
) UNPVT
WHERE [value] = 1;
For checking for differences it's pretty easy to use EXCEPT - for example:
SELECT *
FROM T1
EXCEPT
SELECT *
FROM T2;
to get records that are not including in T2 but in T1 and then the reverse:
SELECT *
FROM T2
EXCEPT
SELECT *
FROM T1;
Thanks to #gotqn for the table definition and values.
The same result can be achieved using CROSS APPLY. Here, I am deriving Chart_Id based on ChartType, as I don't have the table reference for ChartTypes. Ideally, You can join with ChartTypes to get the corresponding Chart_Id.
DECLARE #DataSource TABLE
(
[ID] INT
,[FK_ID] INT
,[ShowChartX] BIT
,[ShowChartY] BIT
,[ShowChartZ] BIT
);
INSERT INTO #DataSource ([ID], [FK_ID], [ShowChartX], [ShowChartY], [ShowChartZ])
VALUES (1, 2, 1, 0, 1);
SELECT id,
fk_id,
CASE charttype
WHEN 'ChartX' THEN 1
WHEN 'ChartY' THEN 3
WHEN 'ChartZ' THEN 2
END AS Chart_ID
FROM #DataSource
CROSS apply (VALUES('ChartX', showchartx),
('ChartY', showcharty),
('ChartZ', showchartz)) AS t(charttype, isavailable)
WHERE isavailable <> 0;
Result set
+----+-------+----------+
| ID | FK_ID | Chart_ID |
+----+-------+----------+
| 1 | 2 | 1 |
| 1 | 2 | 2 |
+----+-------+----------+

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.

SQL - List all pages in between record while maintaining ID key

I'm trying to come up with a useful way to list all pages in between the first of last page of a document into new rows while maintaining the ID number as a key, or cross reference. I have a few ways of getting pages in between, but I'm not exactly sure how to maintain the key in a programmatic way.
Example Input:
First Page Last Page ID
ABC_001 ABC_004 1
ABC_005 ABC_005 2
ABC_006 ABC_010 3
End Result:
All Pages ID
ABC_001 1
ABC_002 1
ABC_003 1
ABC_004 1
ABC_005 2
ABC_006 3
ABC_007 3
ABC_008 3
ABC_009 3
ABC_010 3
Any help is much appreciated. I'm using SQL mgmt studio.
One approach would be to set up a numbers table, that contains a list of numbers that you may possibly find in the column content:
CREATE TABLE numbers( idx INTEGER);
INSERT INTO numbers VALUES(1);
INSERT INTO numbers VALUES(2);
...
INSERT INTO numbers VALUES(10);
Now, assuming that all page values have 7 characters, with the last 3 being digits, we can JOIN the original table with the numbers table to generate the missing records:
SELECT
CONCAT(
SUBSTRING(t.First_Page, 1, 4),
REPLICATE('0', 3 - LEN(n.idx)),
n.idx
) AS [ALl Pages],
t.id
FROM
mytable t
INNER JOIN numbers n
ON n.idx >= CAST(SUBSTRING(t.First_Page, 5, 3) AS int)
AND n.idx <= CAST(SUBSTRING(t.Last_Page, 5, 3) AS int)
This demo on DB Fiddle with your sample data returns:
ALl Pages | id
:-------- | -:
ABC_001 | 1
ABC_002 | 1
ABC_003 | 1
ABC_004 | 1
ABC_005 | 2
ABC_006 | 3
ABC_007 | 3
ABC_008 | 3
ABC_009 | 3
ABC_010 | 3
To find all pages from First Page to Last Page per Book ID, CAST your page numbers from STRING to INTEGER, then add +1 to each page number until you reach the Last Page.
First, turn your original table into a table variable with the Integer data types using a TRY_CAST.
DECLARE #Book TABLE (
[ID] INT
,[FirstPage] INT
,[LastPage] INT
)
INSERT INTO #Book
SELECT [ID]
,TRY_CAST(RIGHT([FirstPage], 3) AS int) AS [FirstPage]
,TRY_CAST(RIGHT([LastPage], 3) AS int) AS [LastPage]
FROM [YourOriginalTable]
Set the maximum page that your pages will increment to using a variable. This will cap out your results to the correct number of pages. Otherwise your table would have many more rows than you need.
DECLARE #LastPage INT
SELECT #LastPage = MAX([LastPage]) FROM #Book
Turning a three-column table (ID, First Page, Last Page) into a two-column table (ID, Page) will require an UNPIVOT.
We're tucking that UNPIVOT into a CTE (Common Table Expression: basically a smart version of a temporary table (like a #TempTable or #TableVariable, but which you can only use once, and is a little more efficient in certain circumstances).
In addition to the UNPIVOT of your [First Name] and [Last Name] columns into a tall table, we're going to append every other combination of page number per ID using a UNION ALL.
;WITH BookCTE AS (
SELECT [ID]
,[Page]
FROM (SELECT [ID]
,[FirstPage]
,[LastPage]
FROM #Book) AS bp
UNPIVOT
(
[Page] FOR [Pages] IN ([FirstPage], [LastPage])
) AS up
UNION ALL
SELECT [ID], [Page] + 1 FROM BookCTE WHERE [Page] + 1 < #LastPage
)
Now that your data is held in a table format using a CTE with all combinations of [ID] and [Page] up to the maximum page in your #Book table, it's time to join your CTE with the #Book table.
SELECT DISTINCT
cte.ID
,cte.Page
FROM BookCTE AS cte
INNER JOIN #Book AS bk
ON bk.ID = cte.ID
WHERE cte.Page <= bk.[LastPage]
ORDER BY
cte.ID
,cte.Page
OPTION (MAXRECURSION 10000)
See also:
How to generate a range of numbers between two numbers (I based my code off of #Jayvee's answer)
Assigning variables using SET vs SELECT
SQL Server UNPIVOT
SQL Server CTE Basics
Recursive CTEs Explained
Note: will update with re-integrating string portion of FirstPage and LastPage (which I assume is based on book title). Stand by.

SQL updating multiple rows in 1 table

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

Add a rownumber based on the sequence of values provided

SELECT Code, Value FROM dbo.Sample
Output:
Code Value
Alpha Pig
Beta Horse
Charlie Dog
Delta Cat
Echo Fish
I want to add a sequence column by specifying a list of Codes and sort the list based on the order specified in the IN clause.
SELECT Code, Value FROM dbo.Sample
WHERE Code in ('Beta', 'Echo', 'Alpha')
I could declare a variable at the top to specify the Codes if that is easier.
The key is that I want to add the row number based on the order that I specify them in.
Output:
Row Code Value
1 Beta Horse
2 Echo Fish
3 Alpha Pig
Edit: I realized after that my Codes are all a fixed length which makes a big difference in how it could be done. I marked the answer below as correct, but my solution is to use a comma-separated string of values:
DECLARE #CodeList TABLE (Seq int, Code nchar(3))
DECLARE #CodeSequence varchar(255)
DECLARE #ThisCode char(3)
DECLARE #Codes int
SET #Codes = 0
-- string of comma-separated codes
SET #CodeSequence = 'ZZZ,ABC,FGH,YYY,BBB,CCC'
----loop through and create index and populate #CodeList
WHILE #Codes*4 < LEN(#CodeSequence)
BEGIN
SET #ThisCode = SUBSTRING(#CodeSequence,#Codes*4+1,3)
SET #Codes = #Codes + 1
INSERT #CodeList (Seq, Code) VALUES (#Codes, #ThisCode)
END
SELECT Seq, Code from #CodeList
Here are the only 2 ways I've seen work accurately:
The first uses CHARINDEX (similar to Gordon's, but I think the WHERE statement is more accurate using IN):
SELECT *
FROM Sample
WHERE Code IN ('Beta','Echo','Alpha')
ORDER BY CHARINDEX(Code+',','Beta,Echo,Alpha,')
Concatenating the comma with code should ensure sub-matches don't affect the results.
Alternatively, you could use a CASE statement:
SELECT *
FROM Sample
WHERE Code in ('Beta','Echo','Alpha')
ORDER BY CASE
WHEN Code = 'Beta' THEN 1
WHEN Code = 'Echo' THEN 2
WHEN Code = 'Alpha' THEN 3
END
SQL Fiddle Demo
Updated Demo with sub-matches.
Also you can use Values as Table Source
SELECT Row, Code, Value
FROM [Sample] s JOIN (
SELECT ROW_NUMBER() OVER(ORDER BY(SELECT 1)) AS Row, Match
FROM (VALUES ('Beta'),
('Echo'),
('Alpha'))
x (Match)
) o ON s.Code = o.Match
ORDER BY Row
Demo on SQLFiddle
Here is solution for any lenght code list.
Create table with self incrementing field and code. Insert in given order. Join tables and order by ...
Some details. Please read this. You will find there function that creates table with auto increment field from string (delimited by commas), i.e.
mysql> call insertEngineer('dinusha,nuwan,nirosh');
Query OK, 1 row affected (0.12 sec)
mysql> select * from engineer;
+----+----------+
| ID | NAME |
+----+----------+
| 1 | dinusha |
| 2 | nuwan |
| 3 | nirosh |
+----+----------+
Next join your Sample table with result of above. GL
Just a lil bit of change to whats been done above to include the rownumbers as well.
SELECT CASE
WHEN Code = 'BetaBeta' THEN 1
WHEN Code = 'Beta' THEN 2
WHEN Code = 'Alpha' THEN 3
END CodeOrder,
*
FROM Sample
WHERE Code in ('BetaBeta','Beta','Alpha')
ORDER BY CodeOrder
SQL Fiddle Demo
I might be tempted to do this using string functions:
declare #list varchar(8000) = 'Beta,Echo,Alpha';
with Sample as (
select 'Alpha' as Code, 'Pig' as Value union all
select 'Beta', 'Horse' union all
select 'Charlie', 'Dog' union all
select 'Delta', 'Cat' union all
select 'Echo', 'Fish'
)
select * from Sample
where charindex(Code, #list) > 0
order by charindex(Code, #list)
If you are worried about submatches, just do the "delimiter" trick:
where #list like '%,'+Code+',%'