I have a cursor that iterates through my temporary table. While it's iterating, I want to check a condition and delete some rows depending on the condition (I will be deleting rows that the iterator has not reached yet).
I tried deleting rows from the table the cursor is iterating (so the temp table), but no success, I can see them in the Messages panel (I print its name).
Is it possible to delete rows from the table a cursor is iterating in SQL-Server ? If it's not, what are my alternatives ?
Basically, the temp table contains tree-like data and depending on the value of a column, I need to delete its children (and grand-children and so on) if it does not fit a criteria.
DECLARE cursor_name CURSOR
FOR (SELECT * FROM #test) ORDER BY Path
DECLARE
#Id AS INTEGER,
#Name AS VARCHAR(MAX),
#Path AS VARCHAR(MAX)
OPEN cursor_name;
FETCH NEXT FROM cursor_name INTO #Id, #Name, #Path;
PRINT #Name
DELETE FROM #test
WHERE
Path LIKE '%76939%'
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM cursor_name INTO #Id, #Name, #Path;
PRINT #Name
END;
CLOSE cursor_name;
DEALLOCATE cursor_name;
#EDIT
Here is more detail on the problem. We have data structured like a tree list. Every item has multiple columns that specify some characteristics the row. Those characteristics can be inherited or not (if InheritanceFlag is 1, then it's inherited, if it's 0, then it is not).
So, when a user makes a change, we need to propagate the change to its children, depending on the said flag. If one of its child has the InheritanceFlag set to 0, then it won't change its value and neither will its children. I wanted to remove those rows with the cursor using the path.
Here is the data that I have. ParentID is the ID of its parent. In this case, suppose we are editing the item 76938, thus we are looking at its children. The ToEdit column is what I'm looking to create; with it, I can filter the rows and directly change the characteristic column to the new value.
+-------+----------+-------+-------------------------+-----------------+--------+
| ID | ParentID | Name | Path | InheritanceFlag | ToEdit |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76938 | NULL | 1 | (76938) | 1 | X |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76942 | 76938 | 1.1 | (76938)\(76942) | 1 | 1 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76952 | 76942 | 1.1.1 | (76938)\(76942)\(76952) | 0 | 0 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76961 | 76942 | 1.1.2 | (76938)\(76942)\(76961) | 1 | 1 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76943 | 76938 | 1.2 | (76938)\(76943) | 1 | 1 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76944 | 76938 | 1.3 | (76938)\(76944) | 0 | 0 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76946 | 76944 | 1.3.1 | (76938)\(76944)\(76946) | 1 | 0 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76947 | 76944 | 1.3.2 | (76938)\(76944)\(76947) | 0 | 0 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76948 | 76944 | 1.3.3 | (76938)\(76944)\(76948) | 1 | 0 |
+-------+----------+-------+-------------------------+-----------------+--------+
| 76945 | 76938 | 1.4 | (76938)\(76945) | 1 | 1 |
+-------+----------+-------+-------------------------+-----------------+--------+
You can delete from the underlying table and have the rows removed from future FETCHes if the cursor is DYNAMIC, and the query that defines the cursor doesn't require a spool, effectively turning it into a STATIC cursor.
In your code sorting by the unindexed VARCHAR(MAX) prevents the cursor from seeing any changes in the underlying table.
EG this
drop table if exists #test
go
create table #test(id integer, name varchar(max), path varchar(1000), index ix_path (path))
insert into #test(id,name,path) values (1,'a','0000000'),(2,'b', '0769391'),(3,'c', '1768391')
DECLARE cursor_name CURSOR DYNAMIC
FOR SELECT * FROM #test ORDER BY path
DECLARE
#Id AS INTEGER,
#Name AS VARCHAR(MAX),
#Path AS VARCHAR(MAX)
OPEN cursor_name;
FETCH NEXT FROM cursor_name INTO #Id, #Name, #Path;
PRINT #Name
print 'deleting'
DELETE FROM #test
WHERE
Path LIKE '%76939%'
WHILE 1=1
BEGIN
FETCH NEXT FROM cursor_name INTO #Id, #Name, #Path;
if ##FETCH_STATUS <> 0 break
PRINT #Name
END;
CLOSE cursor_name;
DEALLOCATE cursor_name;
outputs
(3 rows affected)
a
deleting
(1 row affected)
c
Related
In SQL Server, I've a table name URL_DATA with two column Domain and URL. Column URL contains address of different site for example "https://stackoverflow.com/questions/ask". How can I extract "stackoverflow.com" from it? OR How can I copy string "stackoverflow.com" in new column Domain?
Input Table:
| Domain | URL |
| ------ | ---------------------------------------------------- |
| | https://www.youtube.com/ |
| | https://www.youtube.com/feed/library |
| | https://www.red-gate.com/simple-talk/sql/sql-training|
| | https://internshala.com/student/dashboard |
| | https://stackoverflow.com/questions/ask |
Expected Output Table:
| Domain | URL |
| ----------------- | ---------------------------------------------------- |
| youtube.com | https://www.youtube.com/ |
| youtube.com | https://www.youtube.com/feed/library |
| red-gate.com | https://www.red-gate.com/simple-talk/sql/sql-training|
| internshala.com | https://internshala.com/student/dashboard |
| stackoverflow.com | https://stackoverflow.com/questions/ask |
I have tired using this:
update URL_DATA set domain = replace(replace(replace(domain,'',''),'https://',''),'www.','');
But unable to remove trailing characters.
I tried using this and it worked.
DECLARE #N2 VARCHAR(255);
DECLARE #N1 INT;
DECLARE C1 CURSOR FOR SELECT Domain FROM URL_DATA ;
OPEN C1
FETCH NEXT FROM C1 INTO #N2
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE URL_DATA SET Domain = REPLACE(REPLACE(REPLACE(#N2,'http://',''),'https://',''),'www.','') where CURRENT OF C1
FETCH NEXT FROM C1 INTO #N2
END
CLOSE C1
OPEN C1
FETCH NEXT FROM C1 INTO #N2
WHILE ##FETCH_STATUS = 0
BEGIN
SET #N1= CHARINDEX('/',#N2)
IF (#N1>0)
BEGIN
UPDATE URL_DATA SET Domain = SUBSTRING(#N2,1,ABS(CHARINDEX('/',#N2)-1)) where CURRENT OF C1
END
FETCH NEXT FROM C1 INTO #N2
END
CLOSE C1
DEALLOCATE C1;
First post here! I'm trying to update a stored procedure in my employer's Data Warehouse that's linking two tables on their ID's. The stored procedure is based on 2 columns in Table A. It's primary key, and a column that contains the primary keys from Table B and it's domain in one column. Note that it physically only needs Table A since the ID's from B are in there. The old code used some PATINDEX/SUBSTRING code that assumes two things:
The FK's are always 7 characters long
Domain strings look like this "#xx-yyyy" where xx has to be two characters and yyyy four.
The problem however:
We've recently outgrown the 7-digit FK's and are now looking at 7 or 8 digits
Longer domain strings are implemented (where xx may be between 2 or 15 characters)
Sometimes there is no domain string. Just some FK's, delimited the same way.
The code is poorly documented and includes some ID exceptions (not a problem, just annoying)
Some info:
The Data Warehouse follows the Data Vault method and this procedure is stored on SQL Server and is triggered by SSIS. Subsequent to this procedure the HUB and Satellites are updated so in short: I can't just create a new stored procedure but instead will try to integrate my code into the old stored procedure.
The servers is running on SQL Server 2012 so I can't use string_split
This platform is dying out so I just have to "keep it running" for this year.
An ID and domain are always seperated with one space
If a record has no foreign keys it will always have an empty string
When a record has multiple (foreign) ID's it will always use the same delimiting, even when the individual FK's have no domain string next to it. Delimiter looks like this:
"12345678 #xx-xxxx[CR][CR][CR][LF]12345679 #yy-xxxx"
I've managed to create some code that will assign row numbers and is flexible in recognising the amount of FK's.
This is a piece of the old code:
DECLARE
#MAXCNT INT = (SELECT MAX(ROW) FROM #Worktable),
#C_ID INT,
#R_ID INT,
#SOURCE CHAR(5),
#STRING VARCHAR(20),
#VALUE CHAR(20),
#LEN INT,
#STARTSTRINGLEN INT =0,
#MAXSTRINGLEN INT,
#CNT INT = 1
WHILE #CNT <= #MAXCNT
BEGIN
SELECT #LEN=LEN(REQUESTS),#STRING =REQUESTS, #C_ID =C_ID FROM #Worktable WHERE ROW = #CNT
--1 REQUEST RELATED TO ONE CHANGE
IF #LEN < 17
BEGIN
INSERT INTO #ChangeRequest
SELECT #C_ID,SUBSTRING(#STRING,0,CASE WHEN PATINDEX('%-xxxx%',#STRING) = 0 THEN #LEN+1 ELSE PATINDEX('%-xxxx%',#STRING)-4 END)
--SELECT #STRING AS STRING, #LEN AS LENGTH
END
ELSE
-- MULTIPLE REQUESTS RELATED TO ONE CHANGE
SET #STARTSTRINGLEN = 0
WHILE #STARTSTRINGLEN<#LEN
BEGIN
SET #MAXSTRINGLEN = (SELECT PATINDEX('%-xxxx%',SUBSTRING(#STRING,#STARTSTRINGLEN,#STARTSTRINGLEN+17)))+7
INSERT INTO #ChangeRequest
--remove CRLF
SELECT #C_ID,
REPLACE(REPLACE(
substring(#string,#STARTSTRINGLEN+1,#MAXSTRINGLEN )
, CHAR(13), ''), CHAR(10), '')
SET #STARTSTRINGLEN=#STARTSTRINGLEN+#MAXSTRINGLEN
IF #MAXSTRINGLEN = 0 BEGIN SET #STARTSTRINGLEN = #len END
END
SET #CNT = #CNT + 1;
END;
Since this loop is assuming fixed lengths I need to make it more flexible. My code:
(CASE WHEN LEN([Requests]) = 0
THEN 0
ELSE (LEN(REPLACE(REPLACE(Requests,CHAR(10),'|'),CHAR(13),''))-LEN(REPLACE(REPLACE(Requests,CHAR(10),''),CHAR(13),'')))+1
END)
This consistently shows the accurate number of FK's and thus the number of rows to be created. Now I need to create a loop in which to physically create these rows and split the FK and domain into two columns.
Source table:
+---------+----------------------------------------------------------------------------+
| Some ID | Other ID's |
+---------+----------------------------------------------------------------------------+
| 1 | 21 |
| 2 | 31 #xxx-xxx |
| 3 | 41 #xxx-xxx[CR][CR][CR][LF]42 #yyy-xxx[CR][CR][CR][LF]43 #zzz-xxx |
| 4 | 51[CR][CR][CR][LF]52[CR][CR][CR][LF]53 #xxx-xxx[CR][CR][CR][LF]54 #yyy-xxx |
| 5 | <empty string> |
+---------+----------------------------------------------------------------------------+
Target table:
+-----+----------------+----------------+
| SID | OID | Domain |
+-----+----------------+----------------+
| 1 | 21 | <empty string> |
| 2 | 31 | xxx-xxx |
| 3 | 41 | xxx-xxx |
| 3 | 42 | yyy-xxx |
| 3 | 43 | zzz-xxx |
| 4 | 51 | <empty string> |
| 4 | 52 | <empty string> |
| 4 | 53 | xxx-xxx |
| 4 | 54 | yyy-xxx |
| 5 | <empty string> | <empty string> |
+-----+----------------+----------------+
Currently all rows are created but every one beyond the first for each SID is empty.
So I have this Openquery in a stored procedure, where I need to return results where the values in a column are the same as the ones in a local table
exec spx_SELECT_LocalizacoesEtiquetas
GO
IF OBJECT_ID('dbo.spx_SELECT_LocalizacoesEtiquetas') IS NOT NULL
DROP PROCEDURE spx_SELECT_LocalizacoesEtiquetas
GO
CREATE PROCEDURE spx_SELECT_LocalizacoesEtiquetas
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL NVARCHAR(MAX);
SET #SQL =
'SELECT ET0109 AS Localizacao, Etiquetas
FROM OpenQuery(MACPAC, ''SELECT FET001.ET0109, FET001.ET0101 AS Etiquetas
FROM AUTO.D805DATPOR.FET001 FET001
WHERE FET001.ET0104=''''POE'''' AND FET001.ET0105=''''DIS'''''' AND FET001.ET0101 = '''''
+ (SELECT Localizacao FROM xLocalizacao WHERE InventarioID = 1 ) + ''''' ) ';
EXEC sp_executesql #SQL
END
basically it won't accept the subquery 'cause it says it has too many values.... So my question is. How can i limit the values from the subquery where the values of a column match the ones in a local table? basically a where column A in open query = column B in local table
EDIT.
Here is what I'm trying to achieve.
SubQuery returns from Local table
Column A
| A |
| B |
| C |
| D |
| E |
Open query returns
Column A Column B
| A | 0 |
| A | 0 |
| A1 | 1 |
| A | 2 |
| B | 3 |
| B | 3 |
| B1 | 4 |
Final result should Be
Final query
Column A Column B
| A | 0 |
| A | 0 |
| A | 2 |
| B | 3 |
| B | 3 |
Ok, there are two changes you need to make in your approach.
First of all, you are concatenating your sub-query to a string. No matter what, your subquery has to return a single value, not a multi-row set. So you need to use the method of your choice for having your query return a comma-separated string.
Here's one that will work on any version of SQL Server after 2005.
in other words, instead of this:
Column A
| A |
| B |
| C |
| D |
| E |
your subquery needs to return a single varchar column containing this:
'A','B','C','D','E'
The next change you need to make is using IN instead of =.
So instead of this:
AND FET001.ET0101 = '''''
+ (Your Subquery) + ''''' ) '
you need this:
AND FET001.ET0101 IN ( '
+ (Your Subquery) + ') ) '
I have table like this:
x------x---------x----------------------x-------x
| Id | ACCTNO | name2 | QTY |
x------x---------x----------------------x-------x
| 1 | 1 | here is 2014-01-13 | 10 |
| 1 | 2 | there are 2014-01-12 | 20 |
| 2 | 3 | 2014-01-08 | 40 |
| 2 | 4 | 2014-01-06 | 30 |
x------x---------x----------------------x-------x
I'm trying to get the records where English alphabets are there along with date.
Im trying to do this like:
DECLARE #ACCTNO as INT;
DECLARE #name2 as varchar(max);
declare #st varchar(max)
DECLARE #BusinessCursor as CURSOR;
SET #BusinessCursor = CURSOR FOR
SELECT ACCTNO, Name2
FROM DPBENCHA2;
OPEN #BusinessCursor;
FETCH NEXT FROM #BusinessCursor INTO #ACCTNO, #name2;
--PRINT #Name2;
WHILE ##FETCH_STATUS = 0
BEGIN
set #st = #Name2;
select SUBSTRING(#st, patindex('%[0-9][0-9]/[0-9][0-9]/[0-9][0-9]%', #st), 50)
PRINT #st;
FETCH NEXT FROM #BusinessCursor INTO #ACCTNO,#st;
END
CLOSE #BusinessCursor;
DEALLOCATE #BusinessCursor;
Here instead of getting the records that contains English alphabet along with date. I'm getting all records. How to resolve this? How can I get the records only for account number 1 and 2? Is there any other ways to do it.
I'm trying to get the records where English alphabets are there along with date.
You seem to want a simple query:
SELECT ACCTNO, Name2
FROM DPBENCHA2
WHERE name2 LIKE '%[a-zA-Z]%';
This is much simpler than a cursor. In general, you want to avoid cursors in SQL.
EDIT:
If you want to extract the date from Name2:
SELECT ACCTNO, Name2,
SUBSTRING(name2,
PATINDEX('%[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]%'),
10) as thedate
FROM DPBENCHA2
WHERE name2 LIKE '%[a-zA-Z]%';
Use as following
SUBSTRING(#st, patindex('%[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]%', #st), 50)
you can use a simple select, assuming your date format is yyyy-dd-mm or yyyy-mm-dd
select * from DPBENCHA2 where (name2 like '%[a-z]%')
and name2 like '% [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]%'
note the space in '%<space>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]%'
this will ensure that 'some alphabet 22323-12-34' will not be included in your select
I am currently using SSIS to read the data from a table, modify a column and inset it into a new table.
The modification I want to perform will occur if a previously read row has an identical value in a particular column.
My original idea was to use a c# script with a dictionary containing previously read values and a count of how many times it has been seen.
My problem is that I cannot save a dictionary as an SSIS variable. Is it possible to save a C# variable inside an SSIS script component? or is there another method I could use to accomplish this.
As an example, the data below
/--------------------------------\
| Unique Column | To be modified |
|--------------------------------|
| X5FG | 0 |
| QFJD | 0 |
| X5FG | 0 |
| X5FG | 0 |
| DFHG | 0 |
| DDFB | 0 |
| DDFB | 0 |
will be transformed into
/--------------------------------\
| Unique Column | To be modified |
|--------------------------------|
| X5FG | 0 |
| QFJD | 0 |
| X5FG | 1 |
| X5FG | 2 |
| DFHG | 0 |
| DDFB | 0 |
| DDFB | 1 |
Rather than use a cursor, just use a set based statment
Assuming SQL 2005+ or Oracle, use the ROW_NUMBER function in your source query like so. What's important to note is the PARTITION BY defines your group/when the numbers restart. The ORDER BY clause directs the order in which the numbers are applied (most recent mod date, oldest first, highest salary, etc)
SELECT
D.*
, ROW_NUMBER() OVER (PARTITION BY D.unique_column ORDER BY D.unique_column ) -1 AS keeper
FROM
(
SELECT 'X5FG'
UNION ALL SELECT 'QFJD'
UNION ALL SELECT 'X5FG'
UNION ALL SELECT 'X5FG'
UNION ALL SELECT 'DFHG'
UNION ALL SELECT 'DDFB'
UNION ALL SELECT 'DDFB'
) D (unique_column)
Results
unique_column keeper
DDFB 0
DDFB 1
DFHG 0
QFJD 0
X5FG 0
X5FG 1
X5FG 2
You can create a script component. When given the choice, select the row transformation (instead of source or destination).
In the script, you can create a global variable that you will update in the process row method.
Perhaps SSIS isn't the solution for this one task. Using a cursor with a table-valued variable you would be able to accomplish the same result. I'm not a fan of cursors in most situation, but when you need to iterate through data that depends on previous iterations or is self-reliant then it can be useful. Here's an example:
DECLARE
#value varchar(4)
,#count int
DECLARE #dictionary TABLE ( value varchar(4), count int )
DECLARE cur CURSOR FOR
(SELECT UniqueColumn FROM SourceTable s)
OPEN cur;
FETCH NEXT FROM cur INTO #value;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #innerCount int = 0
IF NOT EXISTS (SELECT 1 FROM #dictionary WHERE value = #value)
BEGIN
INSERT INTO #dictionary ( value, count )
VALUES( #value, 0 )
END
ELSE
BEGIN
SET #innerCount = (SELECT count + 1 FROM #dictionary WHERE value = #value)
UPDATE #dictionary
SET count = #innerCount
WHERE value = #value
END
INSERT INTO TargetTable ( value, count )
VALUES (#value, #innerCount)
FETCH NEXT FROM cur INTO #value;
END