Request with While in SQL Server does not work - sql

Here is my code:
CREATE TABLE IF NOT EXISTS Artfacture
(
N int(6) unsigned NOT NULL,
Code varchar(50) NOT NULL,
Ht Numeric NOT NULL
)
INSERT INTO Artfacture (N, Code, Ht)
VALUES ('1', '1', '10'), ('1', '2', '20'),('1', '3', '30');
CREATE TABLE IF NOT EXISTS Facture
(
N int(6) unsigned NOT NULL,
Ht Numeric NOT NULL
)
INSERT INTO Facture (N, Ht)
VALUES ('1', '60');
CREATE TABLE IF NOT EXISTS Article
(
Code varchar(50) NOT NULL,
Famille varchar(50) NOT NULL
)
INSERT INTO Article (Code, Famille)
VALUES ('1', 'F1'), ('2', 'F2'), ('3', 'F3');
CREATE TABLE IF NOT EXISTS Farticle
(
Designation varchar(50) NOT NULL,
Compte varchar(50) NOT NULL
)
INSERT INTO Farticle (Designation, Compte)
VALUES ('F1', '700000'), ('F2', '710000'), ('F3', '720000');
CREATE TABLE IF NOT EXISTS Ecritures
(
Compte varchar(50) NOT NULL,
Ht numeric NOT NULL
)
My request is a trigger where adding some new rows in Table Facture, it must add rows in Ecritures with the same numbers of rows of Artfacture
declare #piece as nvarchar(50), #code as nvarchar(50)
declare #rowCount int = 0, #currentRow int = 1
select #rowCount = count(*) from ArtFacture where N = #piece;
while (#currentRow <= #rowCount)
begin
set #Code = (select code from ArtFacture where N = #piece)
set #compte = (select Compte from Farticle where Designation = (select Famille from Article where code = #code))
set #Ht = (select ht from ArtFacture where N = #piece)
insert into Ecritures (Compte,Ht)
values (#compte,#Ht)
end
I have a mistake but I do not know where?

You're making the classic mistake of trying to write procedural T-SQL instead of set-based T-SQL which SQL Server is optimised for.
If I follow your logic then the following insert removes the need for a while loop:
insert into Ecritures (Compte, Ht)
select F.Compte, AF.Ht
from Artfracture AF
inner join Article A on A.Code = AF.Code
inner join Farticle F on F.Designation = A.Famille
where N = #piece;
And if you don't want duplicates add a group by:
insert into Ecritures (Compte, Ht)
select F.Compte, AF.Ht
from Artfracture AF
inner join Article A on A.Code = AF.Code
inner join Farticle F on F.Designation = A.Famille
where N = #piece
group by F.Compte, AF.Ht;

Related

Trigger on status change

I need to write a trigger in SQL, first of all I show you my table structure
CREATE TABLE ZAMOW
(
IDZAMOW int primary key identity(1,1) not null,
IDKLIENTA int not null REFERENCES KLIENT(IDKLIENTA),
DATA DATE not null DEFAULT(GETDATE()),
STATUS char(1) CHECK(STATUS = 'P' OR STATUS = 'W' OR STATUS = 'Z' OR STATUS = 'A' )DEFAULT('P')
)
CREATE TABLE ZAMOCZESCI
(
IDZAMOW int not null REFERENCES ZAMOW(IDZAMOW),
IDCZESCI int not null REFERENCES CZESC(IDCZESCI),
ILOSC float not null
)
CREATE TABLE CZESC
(
IDCZESCI int primary key identity(1,1) not null,
NAZWA char(30) not null CHECK ((datalength([NAZWA])>(3))),
OPIS char(200) DEFAULT('Brak opisu'),
CENA decimal(10,2) not null
)
CREATE TABLE MAGACZESCI
(
IDMAGAZYNU int not null REFERENCES MAGAZYN(IDMAGAZYNU),
IDCZESCI int not null REFERENCES CZESC(IDCZESCI),
ILOSC float not null
)
I want to create a trigger that will trigger only if ZAMOW.STATUS changes to 'W' or 'Z' and then will subtract all values MAGACZESCI.ILOSC = MAGACZESCI.ILOSC - ZAMOCZESCI.ILOSC identifying id by IDCZESCI
For example if I have in table MAGACZESCI values (1,1,5) and (1,2,5) // 5 pieces of part number 1 and 2, in table ZAMOW(1,1,currentdate,'P'),
and in table ZAMOCZESCI (1,1,3), (1,2,2) 3 pieces of part 1 and 2 pieces of part 2
I want to trigger only if status changes from 'P' -> 'W' OR 'Z'
and then to change values in MAGACZESCI to (1,1,5-3) and (1,2,5-2) identifying by IDCZESCI
This example is for 2 rows but I want it to be more flexible, sometimes even for 100 or more rows
I came up with something like this
CREATE TRIGGER STATUSCHANGE
ON ZAMOW
AFTER UPDATE
AS
IF UPDATE(STATUS)
IF (ZAMOW.STATUS = 'Z' OR ZAMOW.STATUS = 'W')
DECLARE #idczesci int
DECLARE #ilosc float
DECLARE C1 CURSOR FOR SELECT ZAMOCZESCI.IDCZESCI,ZAMOCZESCI.ILOSC FROM ZAMOCZESCI WHERE ZAMOCZESCI.IDZAMOW = ZAMOW.IDZAMOW
OPEN C1
FETCH NEXT FROM C1 INTO #idczesci,#ilosc
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE MAGACZESCI
SET ILOSC = ILOSC - #ilosc
WHERE IDCZESCI = #idczesci
END
GO
But I don't know how to tell SQL that IF (ZAMOW.STATUS = 'Z' OR ZAMOW.STATUS = 'W') id for rows that are updated, it tells me couldn't bound because of multipart identifier.
This is incomplete as in your attempt you reference the columns that don't exist in the table ZAMOW yet that is what the trigger is on. I suspect that is because we have incomplete DDL as you have keys on tables like MAGAZYN that don't exist in the DDL you provide.
Anyway, this should be enough to get you where you need to be, however, I can't test it, as the columns ILOSC and idczesci don't exist in your sample DDL:
CREATE TRIGGER trg_StatusChange ON dbo.ZAMOW
AFTER UPDATE AS
BEGIN
IF UPDATE ([Status]) BEGIN
UPDATE M
SET ILOSC = ILOSC - i.ILOSC --This column doesn't exist in your table, so where is it coming from?
FROM dbo.MAGACZESCI M
JOIN inserted i ON M.IDCZESCI = i.idczesci --This column doesn't exist in your table, so where is it coming from?
JOIN deleted d ON i.IDZAMOW = d.IDZAMOW --This column doesn't exist in your table, so where is it coming from?
WHERE d.[STATUS] = 'P'
AND i.[status] != 'P';
END;
END;
..just a try...maybe you get something out of it..
drop table if exists ZAMOCZESCI_test
go
drop table if exists MAGACZESCI_test
go
drop table if exists ZAMOW_test
go
drop table if exists CZESC_test
go
CREATE TABLE ZAMOW_test
(
IDZAMOW int primary key identity(1,1) not null,
IDKLIENTA int not null, -- REFERENCES KLIENT(IDKLIENTA),
DATA DATE not null DEFAULT(GETDATE()),
STATUS char(1) CHECK(STATUS = 'P' OR STATUS = 'W' OR STATUS = 'Z' OR STATUS = 'A' )DEFAULT('P')
)
go
insert into ZAMOW_test(IDKLIENTA)
values(510), (520),(530);
go
CREATE TABLE CZESC_test
(
IDCZESCI int primary key identity(1,1) not null,
NAZWA char(30) not null CHECK ((datalength([NAZWA])>(3))),
OPIS char(200) DEFAULT('Brak opisu'),
CENA decimal(10,2) not null
)
go
insert into CZESC_test(NAZWA, CENA)
values('A123', 4), ('B567', 3), ('C009', 7),
--
('X001', 150), ('X002', 500), ('X003', 700)
;
go
CREATE TABLE ZAMOCZESCI_test
(
IDZAMOW int not null REFERENCES ZAMOW_test(IDZAMOW),
IDCZESCI int not null REFERENCES CZESC_test(IDCZESCI),
ILOSC float not null
)
go
insert into ZAMOCZESCI_test(IDZAMOW, IDCZESCI, ILOSC)
values(1, 1, 3), (1, 2, 2), (1, 3, 9),
--2nd&3rd clients
(2, 4, 60), (2, 5, 100), (2, 6, 300),
(3, 5, 150), (3, 6, 120);
go
CREATE TABLE MAGACZESCI_test
(
IDMAGAZYNU int not null,-- REFERENCES MAGAZYN(IDMAGAZYNU),
IDCZESCI int not null REFERENCES CZESC_test(IDCZESCI),
ILOSC float not null
)
go
insert into MAGACZESCI_test(IDMAGAZYNU, IDCZESCI, ILOSC)
values (1001, 1, 5), (1002, 2, 5), (1003, 3, 20),
--
(1004, 4, 200), (1005, 5, 1000), (1006, 6, 2000);
--client 2, assigned 60units of prod4, after trigger: prod4 = 200-60=140
--clients 2&3, assigned 100+150 units of prod5, after trigger: prod5 = 1000 - (100+150) = 750
-- prod6, after trigger: prod6 = 2000 - (300+120) = 1580
go
--based on this
/*
select *
from ZAMOW_test as zam --<--(is replaced by deleted&inserted)
join ZAMOCZESCI_test as zamcze on zam.IDZAMOW = zamcze.IDZAMOW --but this needs to get grouped by IDCZESCI, if multiple "orders" change status in a single update
join MAGACZESCI_test as mag on zamcze.IDCZESCI = mag.IDCZESCI
*/
go
create trigger trgUpdateZAMOW_test on ZAMOW_test for /*after*/ update
as
begin
--set nocount on
--set rowcount 0
--if status did not change from p->w or z, do nothing, return/exit
if not exists
(
select *
from deleted as d
join inserted as i on d.IDZAMOW = i.IDZAMOW
where d.STATUS = 'P'
and i.STATUS in ('W', 'Z')
)
begin
return;
end
--get the "orders" which changed from p->w||z, aggregate their products and subtract the product sums from "inventory"?
update mag
set ILOSC = mag.ILOSC - updcze.sumILOSC --what happens if mag.ILOSC - updcze.sumILOSC < 0 ??
--output deleted.*, inserted.*
from
(
select zamcze.IDCZESCI, isnull(sum(zamcze.ILOSC), 0) as sumILOSC
from deleted as d
join inserted as i on d.IDZAMOW = i.IDZAMOW
join ZAMOCZESCI_test as zamcze on d.IDZAMOW = zamcze.IDZAMOW
where d.STATUS = 'P'
and i.STATUS in ('W', 'Z')
group by zamcze.IDCZESCI
having isnull(sum(zamcze.ILOSC), 0) <> 0
) as updcze
join MAGACZESCI_test as mag on updcze.IDCZESCI = mag.IDCZESCI;
end
go
update ZAMOW_test
set STATUS = 'A' --trigger fires and returns/exits, x->A
go
update ZAMOW_test
set STATUS = 'W' --trigger fires and returns/exits, status:from A->W
go
update ZAMOW_test
set STATUS = 'P' --trigger fires, exits, status:from W->P
go
select 'before', *
from MAGACZESCI_test
update ZAMOW_test
set STATUS = 'W' --trigger fires , and updates mag, status: from P->W
select 'after', *
from MAGACZESCI_test;

'Merge Fields' - alike SQL Server function

I try to find a way to let the SGBD perform a population of merge fields within a long text.
Create the structure :
CREATE TABLE [dbo].[store]
(
[id] [int] NOT NULL,
[text] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[statement]
(
[id] [int] NOT NULL,
[store_id] [int] NOT NULL
)
CREATE TABLE [dbo].[statement_merges]
(
[statement_id] [int] NOT NULL,
[merge_field] [nvarchar](30) NOT NULL,
[user_data] [nvarchar](MAX) NOT NULL
)
Now, create test values
INSERT INTO [store] (id, text)
VALUES (1, 'Waw, stackoverflow is an amazing library of lost people in the IT hell, and i have the feeling that $$PERC_SAT$$ of the users found a solution, personally I asked $$ASKED$$ questions.')
INSERT INTO [statement] (id, store_id)
VALUES (1, 1)
INSERT INTO [statement_merges] (statement_id, merge_field, user_data)
VALUES (1, '$$PERC_SAT$$', '85%')
INSERT INTO [statement_merges] (statement_id, merge_field, user_data)
VALUES (1, '$$ASKED$$', '12')
At the time being my app is delivering the final statement, looping through merges, replacing in the stored text and output
Waw, stackoverflow is an amazing library of lost people in the IT
hell, and i have the feeling that 85% of the users found a solution,
personally I asked 12 questions.
I try to find a way to be code-independent and serve the output in a single query, as u understood, select a statement in which the stored text have been populated with user data. I hope I'm clear.
I looked on TRANSLATE function but it looks like a char replacement, so I have two choices :
I try a recursive function, replacing one by one until no merge_fields is found in the calculated text; but I have doubts about the performance of this approach;
There is a magic to do that but I need your knowledge...
Consider that I want this because the real texts are very long, and I don't want to store it more than once in my database. You can imagine a 3 pages contract with only 12 parameters, like start date, invoiced amount, etc... Everything else cant be changed for compliance.
Thank you for your time!
EDIT :
Thanks to Randy's help, this looks to do the trick :
WITH cte_replace_tokens AS (
SELECT replace(r.text, m.merge_field, m.user_data) as [final], m.merge_field, s.id, 1 AS i
FROM store r
INNER JOIN statement s ON s.store_id = r.id
INNER JOIN statement_merges m ON m.statement_id = s.id
WHERE m.statement_id = 1
UNION ALL
SELECT replace(r.final, m.merge_field, m.user_data) as [final], m.merge_field, r.id, r.i + 1 AS i
FROM cte_replace_tokens r
INNER JOIN statement_merges m ON m.statement_id = r.id
WHERE m.merge_field > r.merge_field
)
select TOP 1 final from cte_replace_tokens ORDER BY i DESC
I will check with a bigger database if the performance is good...
At least, I can "populate" one statement, I need to figure out to be able to extract a list as well.
Thanks again !
If a record is updated more than once by the same update, the last wins. None of the updates are affected by the others - no cumulative effect. It is possible to trick SQL using a local variable to get cumulative effects in some cases, but it's tricky and not recommended. (Order becomes important and is not reliable in an update.)
One alternate is recursion in a CTE. Generate a new record from the prior as each token is replaced until there are no tokens. Here is a working example that replaces 1 with A, 2 with B, etc. (I wonder if there is some tricky xml that can do this as well.)
if not object_id('tempdb..#Raw') is null drop table #Raw
CREATE TABLE #Raw(
[test] [varchar](100) NOT NULL PRIMARY KEY CLUSTERED,
)
if not object_id('tempdb..#Token') is null drop table #Token
CREATE TABLE #Token(
[id] [int] NOT NULL PRIMARY KEY CLUSTERED,
[token] [char](1) NOT NULL,
[value] [char](1) NOT NULL,
)
insert into #Raw values('123456'), ('1122334456')
insert into #Token values(1, '1', 'A'), (2, '2', 'B'), (3, '3', 'C'), (4, '4', 'D'), (5, '5', 'E'), (6, '6', 'F');
WITH cte_replace_tokens AS (
SELECT r.test, replace(r.test, l.token, l.value) as [final], l.id
FROM [Raw] r
CROSS JOIN #Token l
WHERE l.id = 1
UNION ALL
SELECT r.test, replace(r.final, l.token, l.value) as [final], l.id
FROM cte_replace_tokens r
CROSS JOIN #Token l
WHERE l.id = r.id + 1
)
select * from cte_replace_tokens where id = 6
It's not recommended to do such tasks inside sql engine but if you want to do that, you need to do it in a loop using cursor in a function or stored procedure like so :
DECLARE #merge_field nvarchar(30)
, #user_data nvarchar(MAX)
, #statementid INT = 1
, #text varchar(MAX) = 'Waw, stackoverflow is an amazing library of lost people in the IT hell, and i have the feeling that $$PERC_SAT$$ of the users found a solution, personally I asked $$ASKED$$ questions.'
DECLARE merge_statements CURSOR FAST_FORWARD
FOR SELECT
sm.merge_field
, sm.user_data
FROM dbo.statement_merges AS sm
WHERE sm.statement_id = #statementid
OPEN merge_statements
FETCH NEXT FROM merge_statements
INTO #merge_field , #user_data
WHILE ##FETCH_STATUS = 0
BEGIN
set #text = REPLACE(#text , #merge_field, #user_data )
FETCH NEXT FROM merge_statements
INTO #merge_field , #user_data
END
CLOSE merge_statements
DEALLOCATE merge_statements
SELECT #text
Here is a recursive solution.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE [dbo].[store]
(
[id] [int] NOT NULL,
[text] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[statement]
(
[id] [int] NOT NULL,
[store_id] [int] NOT NULL
)
CREATE TABLE [dbo].[statement_merges]
(
[statement_id] [int] NOT NULL,
[merge_field] [nvarchar](30) NOT NULL,
[user_data] [nvarchar](MAX) NOT NULL
)
INSERT INTO store (id, text)
VALUES (1, '$$(*)$$, stackoverflow...$$PERC_SAT$$...$$ASKED$$ questions.')
INSERT INTO store (id, text)
VALUES (2, 'Use The #_#')
INSERT INTO statement (id, store_id) VALUES (1, 1)
INSERT INTO statement (id, store_id) VALUES (2, 2)
INSERT INTO statement_merges (statement_id, merge_field, user_data) VALUES (1, '$$PERC_SAT$$', '85%')
INSERT INTO statement_merges (statement_id, merge_field, user_data) VALUES (1, '$$ASKED$$', '12')
INSERT INTO statement_merges (statement_id, merge_field, user_data) VALUES (1, '$$(*)$$', 'Wow')
INSERT INTO statement_merges (statement_id, merge_field, user_data) VALUES (2, ' #_#', 'Flux!')
Query 1:
;WITH Normalized AS
(
SELECT
store_id=store.id,
store.text,
sm.merge_field,
sm.user_data,
RowNumber = ROW_NUMBER() OVER(PARTITION BY store.id,sm.statement_id ORDER BY merge_field),
statement_id = st.id
FROM
store store
INNER JOIN statement st ON st.store_id = store.id
INNER JOIN statement_merges sm ON sm.statement_id = st.id
)
, Recurse AS
(
SELECT
store_id, statement_id, old_text = text, merge_field,user_data, RowNumber,
Iteration=1,
new_text = REPLACE(text, merge_field, user_data)
FROM
Normalized
WHERE
RowNumber=1
UNION ALL
SELECT
n.store_id, n.statement_id, r.old_text, n.merge_field, n.user_data,
RowNumber=r.RowNumber+1,
Iteration=Iteration+1,
new_text = REPLACE(r.new_text, n.merge_field, n.user_data)
FROM
Normalized n
INNER JOIN Recurse r ON r.RowNumber = n.RowNumber AND r.statement_id = n.statement_id
)
,ReverseOnIteration AS
(
SELECT *,
ReverseIteration = ROW_NUMBER() OVER(PARTITION BY statement_id ORDER BY Iteration DESC)
FROM
Recurse
)
SELECT
store_id, statement_id, new_text, old_text
FROM
ReverseOnIteration
WHERE
ReverseIteration=1
Results:
| store_id | statement_id | new_text | old_text |
|----------|--------------|------------------------------------------|--------------------------------------------------------------|
| 1 | 1 | Wow, stackoverflow...85%...12 questions. | $$(*)$$, stackoverflow...$$PERC_SAT$$...$$ASKED$$ questions. |
| 2 | 2 | Use TheFlux! | Use The #_# |
With the help of Randy, I think I've achieved what I wanted to do !
Known the fact that my real case is a contract, in which there are several statements that may be :
free text
stored text without any merges
stored text with one or
several merges
this CTE does the job !
WITH cte_replace_tokens AS (
-- The initial query dont join on merges neither on store because can be a free text
SELECT COALESCE(r.text, s.part_text) AS [final], CAST('' AS NVARCHAR) AS merge_field, s.id, 1 AS i, s.contract_id
FROM statement s
LEFT JOIN store r ON s.store_id = r.id
UNION ALL
-- We loop till the last merge field, output contains iteration to be able to keep the last record ( all fields updated )
SELECT replace(r.final, m.merge_field, m.user_data) as [final], m.merge_field, r.id, r.i + 1 AS i, r.contract_id
FROM cte_replace_tokens r
INNER JOIN statement_merges m ON m.statement_id = r.id
WHERE m.merge_field > r.merge_field AND r.final LIKE '%' + m.merge_field + '%'
-- spare lost replacements by forcing only one merge_field per loop
AND NOT EXISTS( SELECT mm.statement_id FROM statement_merges mm WHERE mm.statement_id = m.statement_id AND mm.merge_field > r.merge_field AND mm.merge_field < m.merge_field)
)
select s.id,
(select top 1 final from cte_replace_tokens t WHERE t.contract_id = s.contract_id AND t.id = s.id ORDER BY i DESC) as res
FROM statement s
where contract_id = 1
If the CTE solution with a cross join is too slow, an alternate solution would be to build a scalar fn dynamically that has every REPLACE required from the token table. One scalar fn call per record then is order(N). I get the same result as before.
The function is simple and likely not to be too long, depending upon how big the token table becomes...256 MB batch limit. I've seen attempts to dynamically create queries to improve performance backfire - moved the problem to compile time. Should not be a problem here.
if not object_id('tempdb..#Raw') is null drop table #Raw
CREATE TABLE #Raw(
[test] [varchar](100) NOT NULL PRIMARY KEY CLUSTERED,
)
if not object_id('tempdb..#Token') is null drop table #Token
CREATE TABLE #Token(
[id] [int] NOT NULL PRIMARY KEY CLUSTERED,
[token] [char](1) NOT NULL,
[value] [char](1) NOT NULL,
)
insert into #Raw values('123456'), ('1122334456')
insert into #Token values(1, '1', 'A'), (2, '2', 'B'), (3, '3', 'C'), (4, '4', 'D'), (5, '5', 'E'), (6, '6', 'F');
DECLARE #sql varchar(max) = 'CREATE FUNCTION dbo.fn_ReplaceTokens(#raw varchar(8000)) RETURNS varchar(8000) AS BEGIN RETURN ';
WITH cte_replace_statement AS (
SELECT a.id, CAST('replace(#raw,''' + a.token + ''',''' + a.value + ''')' as varchar(max)) as [statement]
FROM #Token a
WHERE a.id = 1
UNION ALL
SELECT n.id, CAST(replace(l.[statement], '#raw', 'replace(#raw,''' + n.token + ''',''' + n.value + ''')') as varchar(max)) as [statement]
FROM #Token n
INNER JOIN cte_replace_statement l
ON n.id = l.id + 1
)
select #sql += [statement] + ' END' from cte_replace_statement where id = 6
print #sql
if not object_id('dbo.fn_ReplaceTokens') is null drop function dbo.fn_ReplaceTokens
execute (#sql)
SELECT r.test, dbo.fn_ReplaceTokens(r.test) as [final] FROM [Raw] r

SQL Server - find a letter in a chain which doesn't belong to anyone

I have a query which takes itemnum and search the table to find the itemnum which doesn't have any replacementItemNum in a chain.
It is like a chain where a belong to b and b belongs c , need to find the letter in a chain which doesn't belong to anyone.
Table has 72k records.
If I pass A or B or C, I will get letter D because it doesn't have any replacement item.
If I pass D, then result would be NULL
This is how the data (chain) looks like:
ItemNum - ReplacementItemNum
----------------------------
A - B
B - C
C - D
D -
This query is taking too long; can this be re written so it doesn't take that long?
DECLARE #CountryOID INT = 250 ,
#ItemNum VARCHAR(50) = 'A';
DECLARE #LastItem VARCHAR(50);
DECLARE #NextItem VARCHAR(50);
SELECT #LastItem = ReplaceItemNum
FROM dbo.MacInventory
WHERE ItemNum = #ItemNum
AND CountryOID = #CountryOID;
WHILE #LastItem <> ''
BEGIN
SET #NextItem = #LastItem;
SELECT #LastItem = ReplaceItemNum
FROM dbo.MacInventory
WHERE ItemNum = #LastItem
AND CountryOID = #CountryOID;
END;
SELECT #NextItem;
Recursive cte to the rescue!
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
ItemNum char(1),
ReplacementItemNum char(1)
);
INSERT INTO #T (ItemNum, ReplacementItemNum) VALUES
('A', 'B'),
('B', 'C'),
('C', 'D'),
('D', NULL),
('E', 'F'), -- added some more data to make sure we don't get the wrong result...
('F', NULL);
Set your starting point:
DECLARE #StartFrom char(1) = 'A';
The recursive cte:
WITH CTE AS
(
SELECT ItemNum, ReplacementItemNum
FROM #T
WHERE ItemNum = #StartFrom
UNION ALL
SELECT T.ItemNum, T.ReplacementItemNum
FROM #T As T
JOIN CTE
ON T.ItemNum = CTE.ReplacementItemNum
)
The query:
SELECT IIF(ItemNum = #StartFrom, NULL, ItemNum) As ItemNum
FROM CTE
WHERE ReplacementItemNum IS NULL
And finally, the result:
ItemNum
D
You can see a live demo on rextester.
Do you have proper indexes on the column (CountryOID, ItemNum) in the table? Try the following recursive cte solution:
;WITH cte_item (ItemNum, ReplaceItemNum)
AS
(
SELECT ItemNum, ReplaceItemNum
FROM dbo.MacInventory
WHERE ItemNum = #ItemNum
AND CountryOID = #CountryOID
UNION
SELECT m.ItemNum, m.ReplaceItemNum
FROM dbo.MacInventory m
INNER JOIN cte_item i
ON m.ItemNum = i.ReplaceItemNum
AND m.CountryOID = #CountryOID
)
SELECT * FROM cte_item WHERE ReplaceItemNum='';

SQL insert data dynamic column name from another table

i'm trying to insert data from one table to another with dynamic column name from #array to #array2
error
The multi-part identifier "s.id" could not be bound.
SQL CODE:
DECLARE #Array TABLE
(
id int not null,
dt varchar(12) not null,
ld varchar(16) not null,
val varchar(12) not null,
ty varchar(4) not null,
PRIMARY KEY CLUSTERED (id,dt)
)
DECLARE #Array2 TABLE
(
id int not null,
dt varchar(12) not null,
ld varchar(16) not null,
min varchar(12) null,
mout varchar(4) null,
PRIMARY KEY CLUSTERED (id,dt)
)
INSERT INTO #Array VALUES
('1','2015-11-11','2015-11-11','20:08','min')
,('2','2015-11-11','2015-11-11','20:08','mout')
,('3','2015-11-11','2015-11-11','20:08','min')
,('4','2015-11-11','2015-11-11','20:08','min')
Select * from #Array s
WHERE NOT EXISTS (select s.id,s.dt,s.ld,s.ty from #Array2
WHERE id != s.id AND dt != s.dt)
INSERT INTO #Array2 (id,dt,ld,s.ty) VALUES(s.id,s.dt,s.ld,s.val)
^
dynamic column name from #Array TABLE
here is SQL Fiddle link, thanks.
I would re-write your insert along the lines of:
INSERT INTO #Array2 (id,dt,ld,s.ty)
Select s.id,s.dt,s.ld,s.ty from #Array s
left join #Array2 a2 on a2.id = s.id
where a2.id is null
Your error is coming from the fact that Array2 doesn't have a ty column defined. The fix there is to either put it in there or re-evaluate what you are putting into it. Also, thumbs up for the fiddle link :)
EDIT:
On second reading of your question, do you want to dynamically add that column to array2? If so, then that would require quite a bit of mucking around, and I would try to find another solution. Changing your schema like that on the fly is ill-advised.
EDIT2:
INSERT INTO #Array2 (id,dt,ld,min,mout)
Select
s.id,
s.dt,
s.ld,
case s.ty when 'min' then s.val else '' end,
case s.ty when 'mout' then s.val else '' end
from #Array s
left join #Array2 a2 on a2.id = s.id
where a2.id is null
EDIT3
UPDATE a2
SET
a2.dt = s.dt,
a2.ld = s.ld,
a2.min = case s.ty when 'min' then s.val else '' end,
a2.mout = case s.ty when 'mout' then s.val else '' END
FROM #Array2 a2
LEFT JOIN #Array s ON a2.id = s.id
WHERE s.id IS NOT null

how to insert multiple rows with check for duplicate rows in a short way

I am trying to insert multiple records (~250) in a table (say MyTable) and would like to insert a new row only if it does not exist already.
I am using SQL Server 2008 R2 and got help from other threads like SQL conditional insert if row doesn't already exist.
While I am able to achieve that with following stripped script, I would like to know if there is a better (short) way to do this as I
have to repeat this checking for every row inserted. Since we need to execute this script only once during DB deployment, I am not too much
worried about performance.
INSERT INTO MyTable([Description], [CreatedDate], [CreatedBy], [ModifiedDate], [ModifiedBy], [IsActive], [IsDeleted])
SELECT N'ababab', GETDATE(), 1, NULL, NULL, 1, 0
WHERE NOT EXISTS(SELECT * FROM MyTable WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
WHERE
([InstanceId] IS NULL OR [InstanceId] = 1)
AND [ChannelPartnerId] IS NULL
AND [CreatedBy] = 1)
UNION ALL
SELECT N'xyz', 1, GETDATE(), 1, NULL, NULL, 1, 0
WHERE NOT EXISTS(SELECT * FROM [dbo].[TemplateQualifierCategoryMyTest] WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
WHERE
([InstanceId] IS NULL OR [InstanceId] = 1)
AND [ChannelPartnerId] IS NULL
AND [CreatedBy] = 1)
-- More SELECT statements goes here
You could create a temporary table with your descriptions, then insert them all into the MyTable with a select that will check for rows in the temporary table that is not yet present in your destination, (this trick in implemented by the LEFT OUTER JOIN in conjunction with the IS NULL for the MyTable.Description part in the WHERE-Clause):
DECLARE #Descriptions TABLE ([Description] VARCHAR(200) NOT NULL )
INSERT INTO #Descriptions ( Description )VALUES ( 'ababab' )
INSERT INTO #Descriptions ( Description )VALUES ( 'xyz' )
INSERT INTO dbo.MyTable
( Description ,
CreatedDate ,
CreatedBy ,
ModifiedDate ,
ModifiedBy ,
IsActive ,
IsDeleted
)
SELECT d.Description, GETDATE(), 1, NULL, NULL, 1, 0
FROM #Descriptions d
LEFT OUTER JOIN dbo.MyTable mt ON d.Description = mt.Description
WHERE mt.Description IS NULL