SQL Server increment two tables in an update - sql

First, SQL is NOT my strong point, as you'll see. I have one table that keeps track of the next item number, by some type, like so:
declare #maxs as table
(
Equip int,
NextId int
);
-- initial id values
insert into #maxs (Equip, NextId) values (400, 40);
insert into #maxs (Equip, NextId) values (500, 50);
If I create an item of type '400' then the next Id is 40, and that should be incremented to 41. In a case of a single add, that's easy enough. Our program does adds in batch, so here is my problem.
declare #t as table (Id int, Equip int, Descr varchar(20));
-- simulates the batch processing
insert into #t (Equip, Descr) values (400, 'Item 1');
insert into #t (Equip, Descr) values (400, 'Item 2');
insert into #t (Equip, Descr) values (500, 'Item 3');
-- generate the new id's in batch
UPDATE t
SET Id = (SELECT m.NextId + ROW_NUMBER() OVER (PARTITION BY t.Equip ORDER BY t.Equip))
FROM #t t
INNER JOIN #maxs m ON m.Equip = t.Equip
SELECT * FROM #t
This results in both Item 1 and Item 2 having the same Id because only 1 row is returned for 400, so ROW_NUMBER is the same for both. I need to be able to increment the NextId value in #maxs as well as update the entry in #t so that the second row that joins into the 400 value in #maxs will have the next value (almost like a x++ reference in c#). Is there a clean way to do that in SQL?
Thanks in advance.

Just go with JOIN and nested select
declare #t as table (Id int, Equip int, Descr varchar(20));
-- simulates the batch processing
insert into #t (Equip, Descr) values (400, 'Item 1');
insert into #t (Equip, Descr) values (400, 'Item 2');
insert into #t (Equip, Descr) values (500, 'Item 3');
-- generate the new id's in batch
UPDATE t
SET
Id = t.Equip + s.RowNum
FROM #t t
JOIN (select Equip,
Descr,
ROW_NUMBER() OVER (PARTITION BY Equip ORDER BY Equip) RowNum
from #t) s
on t.Equip = s.Equip and t.Descr = s.Descr
select * from #t
And if possible, try to switch from table variable to temporary table

You can do what you want with a CTE:
WITH toupdate as (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY t.Equip ORDER BY t.Equip) as seqnum
FROM #t t
UPDATE t
SET
Id = m.NextId + seqnum
FROM toupdate t INNER JOIN
#maxs m
ON m.Equip = t.Equip;
I'm not sure this is a good idea, though. It is better to use identity columns to identify each row. There are definitely some cases, though, where numbering within a group is useful as a secondary key (for instance, line items on an invoice).

Related

How to retrieve SCOPE_IDENTITY of all inserts done in INSERT INTO [table] SELECT [col1, ...] [duplicate]

This question already has answers here:
SQL Server - Return value after INSERT
(14 answers)
Closed 7 months ago.
Suppose I have a temp table with some cols one of which I have dedicated to identity column of the inserted Invoice and the others for inserting Invoice data itself. Like the following table :
CREATE TABLE #InvoiceItems
(
RowNumber INT, -- Used for inserting new invoice
SaleID INT, -- Used for inserting new invoice
BuyerID INT, -- Used for inserting new invoice
InvoiceID INT -- Intended for PK of the invoice added after inserting it
);
I use something like the following for inserting data into Invoice table
INSERT INTO [Invoice]
SELECT [col1, ...]
FROM #InvoiceItems
How can I achieve to fill the InvoiceID column while inserting table data into Invoice table using temp table? I know about SCOPE_IDENTITY() function but it returns the last inserted PK only which does not really suit my need.
I could also use a while to do this one by one but since the number of data I'm planning to insert is immense, I feel like it's not going to be the most optimized option.
Thanks for the answers in advance.
To grab multiple IDENTITY values from INSERT INTO SELECT FROM OUTPUT clause could be used:
-- temp table
CREATE TABLE #temp(col VARCHAR(100));
INSERT INTO #temp(col) VALUES ('A'), ('B'), ('C');
--target table
CREATE TABLE tab(
id INT IDENTITY,
col VARCHAR(100)
);
Main insert:
INSERT INTO tab(col)
OUTPUT inserted.id, inserted.col
SELECT col
FROM #temp;
The output could be also Inserted into another table using OUTPUT INTO:
CREATE TABLE #temp_identity(id INT);
INSERT INTO tab(col)
OUTPUT inserted.id
INTO #temp_identity
SELECT col
FROM #temp;
SELECT * FROM #temp_identity;
db<>fiddle demo
CREATE TABLE #InvoiceItems(
RowNumber INT,
SaleID INT,
BuyerID INT,
InvoiceID INT
);
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 55, 77)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 56, 78)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 57, 79)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 58, 80)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 59, 81)
DECLARE #Inserted table( RowNumber int,
SaleID INT,
BuyerID INT,
InvoiceID INT);
INSERT INTO dbo.[Invoice] (RowNumber, SaleID, BuyerID)
OUTPUT INSERTED.RowNumber, INSERTED.SaleID, INSERTED.BuyerID, INSERTED.InvoiceID
INTO #Inserted
SELECT RowNumber, SaleID, BuyerID
FROM #InvoiceItems
UPDATE ii
SET InvoiceID = ins.InvoiceID
FROM #InvoiceItems ii
JOIN #Inserted ins on ins.BuyerID = ii.BuyerID and ins.RowNumber = ii.RowNumber and ins.SaleID = ii.SaleID
SELECT * FROM #InvoiceItems

Sql Server While Loop with Changing Condition

I have a User Table in my database that contains two fields
user_id
manager_id
I am trying to construct a query to list all of the manager_ids that are associated with a user_id in a hierarchical structure.
So if i give a user_id, i will get that users manager, followed by that persons manager all the way to the very top.
So far i have tried but it doesnt give what i need:
WITH cte(user_id, manager_id) as (
SELECT user_id, manager_id
FROM user
WHERE manager_id=#userid
UNION ALL
SELECT u.user_id, u.manager_id,
FROM user u
INNER JOIN cte c on e.manager_id = c.employee_id
)
INSERT INTO #tbl (manager_id)
select user_id, manager_id from cte;
If anyone can point me in the right direction that would be great.
I thought about a While loop but this may not be very efficient and im not too sure how to implement that.
OP asked for a while loop, and while (ha, pun) this may not be the best way... Ask and you shall receive. (:
Here is sample data I created (in the future, please provide this):
CREATE TABLE #temp (userID int, managerID int)
INSERT INTO #temp VALUES (1, 3)
INSERT INTO #temp VALUES (2, 3)
INSERT INTO #temp VALUES (3, 7)
INSERT INTO #temp VALUES (4, 6)
INSERT INTO #temp VALUES (5, 7)
INSERT INTO #temp VALUES (6, 9)
INSERT INTO #temp VALUES (7, 10)
INSERT INTO #temp VALUES (8, 10)
INSERT INTO #temp VALUES (9, 10)
INSERT INTO #temp VALUES (10, 12)
INSERT INTO #temp VALUES (11, 12)
INSERT INTO #temp VALUES (12, NULL)
While Loop:
CREATE TABLE #results (userID INT, managerID INT)
DECLARE #currentUser INT = 1 -- Would be your parameter!
DECLARE #maxUser INT
DECLARE #userManager INT
SELECT #maxUser = MAX(userID) FROM #temp
WHILE #currentUser <= #maxUser
BEGIN
SELECT #userManager = managerID FROM #temp WHERE userID = #currentUser
INSERT INTO #results VALUES (#currentUser, #userManager)
SET #currentUser = #userManager
END
SELECT * FROM #results
DROP TABLE #temp
DROP TABLE #results
Get rid of this column list in your CTE declaration that has nothing to do with the columns you are actually selecting in the CTE:
WITH cte(employee_id, name, reports_to_emp_no, job_number) as (
Just make it this:
WITH cte as (
I recommend recursive solution:
WITH Parent AS
(
SELECT * FROM user WHERE user_id=#userId
UNION ALL
SELECT T.* FROM user T
JOIN Parent P ON P.manager_id=T.user_id
)
SELECT * FROM Parent
To see demo, run following:
SELECT * INTO #t FROM (VALUES (1,NULL),(2,1),(3,2),(4,1)) T(user_id,manager_id);
DECLARE #userId int = 3;
WITH Parent AS
(
SELECT * FROM #t WHERE user_id=#userId
UNION ALL
SELECT T.* FROM #t T
JOIN Parent P ON P.manager_id=T.user_id
)
SELECT * FROM Parent

Stored procedure to do a two-level pivot

I am trying to create a stored procedure in Sybase Adaptive Server Anywhere that will do a double pivot of table. I will first outline with some images what I am trying to accomplish.
The problem
Raw data
Here is the raw data in the table; in the sample code that I have posted lower down this is temporary table #t1 :
First level of pivoting
The first level of pivoting involves grouping on the column rownr and pivoting on the column col, putting the resulting table into temporary table #t2 :
I have the code up to this point which I have posted lower down.
Second level of pivoting
This is the section that I am struggling with. I am now needing to pivot table #t2 grouping on the column ObjectId and replicating the columns Operation and Code for the number of rows in the grouping to produce table #t3. So the result for the example I have given would look like this:
Because two columns are being replicated (Operation and Code) the number of columns in the resulting table should equal 2 multiplied by the number of rows in the grouping with the largest number of rows. Groupings that have less than the maximum number of grouped rows will be padded with null values, as seen in the example.
The code
Here is my code that creates the first two tables, #t1 and #t2 :
begin
create table #t1(rownr int, col nvarchar(15), val nvarchar(300));
insert into #t1 values(1, 'ObjectId', 'A');
insert into #t1 values(1, 'Operation', 'Op1');
insert into #t1 values(1, 'Code', '101');
insert into #t1 values(2, 'ObjectId', 'A');
insert into #t1 values(2, 'Operation', 'Op2');
insert into #t1 values(2, 'Code', '102');
insert into #t1 values(3, 'ObjectId', 'B');
insert into #t1 values(3, 'Operation', 'Op3');
insert into #t1 values(3, 'Code', '103');
insert into #t1 values(4, 'ObjectId', 'B');
insert into #t1 values(4, 'Operation', 'Op4');
insert into #t1 values(4, 'Code', '104');
insert into #t1 values(5, 'ObjectId', 'B');
insert into #t1 values(5, 'Operation', 'Op5');
insert into #t1 values(5, 'Code', '105');
-- Create t2
select
rownr,
Max(case when col = 'ObjectId' then val end) as ObjectId,
Max(case when col = 'Operation' then val end) as Operation,
Max(case when col = 'Code' then val end) as Code
into #t2
from #t1
group by rownr
order by rownr, ObjectId;
select * from #t2;
-- Create #t3 <--- This is where I need help
end
Take note
Please note that I am trying to solve this for Sybase Adaptive Server Anywhere which does not have a pivot statement like Sql Server does, so a solution using a pivot statement will not help me.
You need each set of A, B, etc in a single temporary table with their ObjectIds, with an ordering integer 1,2,3,4 against the ObjectIDs, regardless of the string value of the op.
Getting such an ordered integer is normally easy with an IDENTITY, but you don't want it for all, you want it per A,B,C etc subset.
Thus if you can run a Cursor on each ObjectId value (A,B,C,etc.,) and get the integer ordered operations for those into a temp table, then you can pivot easily with multiple outer joins.
So:
create table #l(Id NUMERIC(8) IDENTITY, op VARCHAR(30), obj VARCHAR(300))
go
set identity_insert #l on
Get a cursor on the objectIds and loop like:
select Id = IDENTITY(8)
, t2.val op
, t1.val obj
into
existing table
#l
from #t1 t1, #t1 t2
where t1.col = 'ObjectId'
and t1.val = 'A' -- this would be the cursors value
and t1.rownr = t2.rownr
and t2.col = 'Operation'
You will then find that #l can be pivotted nicely with multipler outers, because you'll have a table like:
select * from #l order by 3,1
Id op obj
----------- ------ -----
1 Op1 A
2 Op2 A
1 Op3 B
2 Op4 B
3 Op5 B
Just in case this helps someone else, here is the code that I eventually came up with to accomplish the required double pivot:
begin
DECLARE #nr_of_columns smallint;
DECLARE #qry long varchar;
DECLARE #i SMALLINT;
DECLARE #createTable nvarchar(1000);
create table #t1(rownr int, col nvarchar(15), val nvarchar(300));
insert into #t1 values(1, 'ObjectId', 'A');
insert into #t1 values(1, 'Operation', 'Op1');
insert into #t1 values(1, 'Code', '101');
insert into #t1 values(2, 'ObjectId', 'A');
insert into #t1 values(2, 'Operation', 'Op2');
insert into #t1 values(2, 'Code', '102');
insert into #t1 values(3, 'ObjectId', 'B');
insert into #t1 values(3, 'Operation', 'Op3');
insert into #t1 values(3, 'Code', '103');
insert into #t1 values(4, 'ObjectId', 'B');
insert into #t1 values(4, 'Operation', 'Op4');
insert into #t1 values(4, 'Code', '104');
insert into #t1 values(5, 'ObjectId', 'B');
insert into #t1 values(5, 'Operation', 'Op5');
insert into #t1 values(5, 'Code', '105');
-- create t2
select
rownr,
Max(case when col = 'ObjectId' then val end) as ObjectId,
Max(case when col = 'Operation' then val end) as Operation,
Max(case when col = 'Code' then val end) as Code
into #t2
from #t1
group by rownr
order by rownr, ObjectId;
-- create #t3
-- Maximum number of column groups in result table
select max(cols) into #nr_of_columns from (SELECT count() over (partition by ObjectId) as cols from #t2) A;
-- Create temporary table #t3 to hold results
SET #i = 1;
SET #createTable = 'create table #t3(ObjectId nvarchar(300)';
while #i <= #nr_of_columns loop
set #createTable = #createTable || ', Operation' || #i || ' nvarchar(300), Code' || #i || ' nvarchar(300)';
set #i = #i + 1;
end loop;
set #createTable = #createTable || ')';
execute immediate (#createTable);
-- Pivot into #t3
for whatever as cur cursor for
select 'insert into #t3 select ' || rw as qry from
(select '''' || A.ObjectId || ''' AS ObjectId, ' || LIST(attributes) || repeat(',null,null', #nr_of_columns-A.nr_in_group) AS rw from
(SELECT ObjectId, count() over (partition by ObjectId) nr_in_group, row_number() over (partition by ObjectId order by Operation) nr, ''''||Operation||''' AS Operation' || nr || ',' || '''' || Code || ''' AS Code' || nr as attributes FROM #t2 order by ObjectId,Operation) A
group by ObjectId,#nr_of_columns, nr_in_group) B
DO
execute IMMEDIATE (qry);
end for;
-- Output #t2
select * from #t3;
end

SQL: JOIN with 'near' match

I need to do a JOIN with a 'near match'. The best way to explain this is with an example:
CREATE TABLE Car
(
Vin int,
Make nvarchar(50),
ColorID int,
)
CREATE TABLE Color
(
ColorID int,
ColorCode nvarchar(10)
)
CREATE TABLE ColorName
(
ColorID int,
Languagecode varchar(12),
ColorName nvarchar(50)
)
INSERT INTO Color Values (1, 'RED CODE')
INSERT INTO Color Values (2, 'GREEN CODE')
INSERT INTO Color Values (3, 'BLUE CODE')
INSERT INTO ColorName Values (1, 'en', 'Red')
INSERT INTO ColorName Values (1, 'en-US', 'Red, my friend')
INSERT INTO ColorName Values (1, 'en-GB', 'Red, my dear')
INSERT INTO ColorName Values (1, 'en-AU', 'Red, mate')
INSERT INTO ColorName Values (1, 'fr', 'Rouge')
INSERT INTO ColorName Values (1, 'fr-BE', 'Rouge, mon ami')
INSERT INTO ColorName Values (1, 'fr-CA', 'Rouge, mon chum')
INSERT INTO Car Values (123, 'Honda', 1)
The SPROC would look like this:
DECLARE #LanguageCode varchar(12) = 'en-US'
SELECT * FROM Car A
JOIN Color B ON (A.ColorID = B.ColorID)
LEFT JOIN ColorName C ON (B.ColorID = C.ColorID AND C.LanguageCode = #LanguageCode)
See http://sqlfiddle.com/#!6/ac24d/24 (thanks to Jake!)
Here is the challenge:
When the SPROC parameter #LanguageCode is an exact match, all is well.
I would like for it to also work for partial matches; more specifically: say for example that #LanguageCode would be 'en-NZ' then I would like the SPROC to return the value for language code 'en' (since there is no value for 'en-NZ').
As an extra challenge: if there is no match at all I would like to return the 'en' value; for example if #LanguageCode would be 'es' then the SPROC would return the 'en' value (since there is no value for 'es').
Try left(#LanguageCode, 2) + '%'
http://sqlfiddle.com/#!6/ac24d/26
About second part - you have to query table two times anyway (you can do it in one statement, but if will be like two statements in one). You also can insert data into temporary (or variable) table, check if there's no rows and then make another query
I've made a query with table function
http://sqlfiddle.com/#!6/b7be3/5
So you can write
DECLARE #LanguageCode varchar(12) = 'es'
if not exists (select * from sf_test(#LanguageCode))
select * from sf_test('en')
else
select * from sf_test(#LanguageCode)
you also can write
declare #temp table
(
Vin int,
Make nvarchar(50),
ColorCode nvarchar(10)
)
insert into #temp
select * from sf_test(#LanguageCode)
if not exists (select * from #temp)
select * from sf_test('en')
else
select * from #temp
As #Roman Pekar has said in his comment, this can indeed be done, including your additional request about falling back to en, in one statement with the help of a ranking function. Here's how you could go about it:
WITH FilteredAndRanked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (
PARTITION BY ColorID
ORDER BY CASE LanguageCode
WHEN #LanguageCode THEN 1
WHEN LEFT(#LanguageCode, 2) THEN 2
WHEN 'en' THEN 3
END
)
FROM ColorName
WHERE LanguageCode IN (
#LanguageCode,
LEFT(#LanguageCode, 2),
'en'
)
)
SELECT
...
FROM Car A
INNER JOIN Color B ON (A.ColorID = B.ColorID)
LEFT JOIN FilteredAndRanked C ON (B.ColorID = C.ColorID AND C.rnk = 1)
;
That is, the ColorName table is filtered and ranked before being used in the query, and then only the rows with the rankings of 1 are joined:
The filter for ColorName includes only rows with LanguageCode values of #LanguageCode, LEFT(#LanguageCode, 2) and 'en'.
The ranking values are assigned based on which language code each row contains: rows with LEFT(#LanguageCode, 2) are ranked after those with #LanguageCode but before the 'en' ones.

comparing two colums in sqlserver and returing the remaining data

I have two tables. First one is student table where he can select two optional courses and other table is current semester's optional courses list.
When ever the student selects a course, row is inserted with basic details such as roll number, inserted time, selected course and status as "1". When ever a selected course is de-selected the status is set as "0" for that row.
Suppose the student has select course id 1 and 2.
Now using this query
select SselectedCourse AS [text()] FROM Sample.dbo.Tbl_student_details where var_rollnumber = '020803009' and status = 1 order by var_courseselectedtime desc FOR XML PATH('')
This will give me the result as "12" where 1 is physics and 2 is social.
the second table holds the value from 1-9
For e.g course id
1 = physics
2 = social
3 = chemistry
4 = geography
5 = computer
6 = Spoken Hindi
7 = Spoken English
8 = B.EEE
9 = B.ECE
now the current student has selected 1 and 2. So on first column, i get "12" and second column i need to get "3456789"(remaining courses).
How to write a query for this?
This is not in single query but is simple.
DECLARE #STUDENT AS TABLE(ID INT, COURSEID INT)
DECLARE #SEM AS TABLE (COURSEID INT, COURSE VARCHAR(100))
INSERT INTO #STUDENT VALUES(1, 1)
INSERT INTO #STUDENT VALUES(1, 2)
INSERT INTO #SEM VALUES(1, 'physics')
INSERT INTO #SEM VALUES(2, 'social')
INSERT INTO #SEM VALUES(3, 'chemistry')
INSERT INTO #SEM VALUES(4, 'geography')
INSERT INTO #SEM VALUES(5, 'computer')
INSERT INTO #SEM VALUES(6, 'Spoken Hindi')
INSERT INTO #SEM VALUES(7, 'Spoken English')
INSERT INTO #SEM VALUES(8, 'B.EEE')
INSERT INTO #SEM VALUES(9, 'B.ECE')
DECLARE #COURSEIDS_STUDENT VARCHAR(100), #COURSEIDS_SEM VARCHAR(100)
SELECT #COURSEIDS_STUDENT = COALESCE(#COURSEIDS_STUDENT, '') + CONVERT(VARCHAR(10), COURSEID) + ' ' FROM #STUDENT
SELECT #COURSEIDS_SEM = COALESCE(#COURSEIDS_SEM , '') + CONVERT(VARCHAR(10), COURSEID) + ' ' FROM #SEM WHERE COURSEID NOT IN (SELECT COURSEID FROM #STUDENT)
SELECT #COURSEIDS_STUDENT COURSEIDS_STUDENT, #COURSEIDS_SEM COURSEIDS_SEM
try this:
;WITH CTE as (select ROW_NUMBER() over (order by (select 0)) as rn,* from Sample.dbo.Tbl_student_details)
,CTE1 As(
select rn,SselectedCourse ,replace(stuff((select ''+courseid from course_details for xml path('')),1,1,''),SselectedCourse,'') as rem from CTE a
where rn = 1
union all
select c2.rn,c2.SselectedCourse,replace(rem,c2.SselectedCourse,'') as rem
from CTE1 c1 inner join CTE c2
on c2.rn=c1.rn+1
)
select STUFF((select ''+SselectedCourse from CTE1 for xml path('')),1,0,''),(select top 1 rem from CTE1 order by rn desc)