How to get updated columns from a table? - sql-server

I am working with SQL Server Triggers. And I need a sql query to find columns from a table whose values has been updated using of INSERTED and DELETED tables.
Can anyone help me out on this ? For ex -
DECLARE #T1 TABLE (Name NVARCHAR(MAX), LName nvarchar(max), Address1 Nvarchar(max),id int)
DECLARE #T2 TABLE (Name NVARCHAR(MAX), LName nvarchar(max), Address1 Nvarchar(max), id int)
insert into #T1 values('Ricky','Broad','a b road',1)
insert into #T1 values('Mike','Halls','m g road',2)
insert into #T2 values('Ricky_Update','Broad','a b road',1)
insert into #T2 values('Mike','Halls','m g road',2)
;WITH ChangedData AS (
SELECT d.name , d.LName FROM #T1 d
EXCEPT
SELECT i.name , i.LName FROM #T2 i
)
I tried to find out by "EXCEPT" but it's returning whole updated row. And I need only updated columns like in above example I only need-
Name column for id =1 because it's updated.

You have to do except for individual columns and then do Union.
SELECT NAME
FROM (
SELECT d.NAME
FROM #T1 d
WHERE id = 1
EXCEPT
SELECT i.NAME
FROM #T2 i
WHERE id = 1
) A
UNION
SELECT LName
FROM (
SELECT d.LName
FROM #T1 d
WHERE id = 1
EXCEPT
SELECT i.LName
FROM #T2 i
WHERE id = 1
) B

Related

How to get an xml attribute value from a table's column in a select list

DECLARE #xml AS XML
SET #xml = CONVERT(xml,'<data><UserType userID="123">employee</UserType></data>')
SELECT (SELECT d.value('#userID', 'int')
FROM #xml.nodes('//data/UserType') T(d))
I have a table where the column is like the XML above. Is it possible to get the #userID value in a select statement?
In my Users table, the column 'XmlData' is of type XML.
SELECT
userID -- u.XmlData
FROM Users u
How can I grab the userID attribute from the xml in a select statement? I know how to parse it once, but not in a select.
You knit those together with APPLY. Something like:
SELECT
u.*,
SELECT T.d.value('#userID', 'int') userID
FROM Users u
CROSS APPLY u.XmlData.nodes('/data/UserType') T(d)
Same idea like John Cappelletti, just with CTE for conversion to XML.
SQL
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, XMLData NVARCHAR(MAX));
INSERT INTO #tbl (XMLData)
VALUES
(N'<data><UserType userID="123">employee</UserType></data>');
;WITH rs AS
(
SELECT *, TRY_CAST(XMLData AS XML) AS xml_data FROM #tbl
)
SELECT ID
, col.value('#userID','INT') AS userID
FROM rs
CROSS APPLY rs.[xml_Data].nodes('/data/UserType') AS tab(col);
Output
+----+--------+
| ID | userID |
+----+--------+
| 1 | 123 |
+----+--------+
With a little twist.
Example
Declare #YourTable table (ID int,XMLData nvarchar(max))
Insert Into #YourTable values
(1,'<data><UserType userID="123">employee</UserType></data>')
Select A.ID
,C.*
From #YourTable A
Cross Apply ( values (convert(XML,XMLData) )) B(XData)
Cross Apply ( Select UserID = d.value('#userID', 'int')
From XData.nodes('/data/UserType') T(d)
) C
Returns
ID UserID
1 123
Update If only ONE User ID in the XML
Declare #YourTable table (ID int,XMLData nvarchar(max))
Insert Into #YourTable values
(1,'<data><UserType userID="123">employee</UserType></data>')
Select A.ID
,UserID = convert(XML,XMLData).value('/data[1]/UserType[1]/#userID', 'int')
From #YourTable A

SQL Server query items with IN query but keep order

I have to query specific order of string IDs example data:
| ID | RES |
---------------
| A_12 | 1.89 |
| B_27 | 4.53 |
| B_28 | 1.02 |
| C_23 | 2.67 |
A tool generated a specific order which does not follow any standard ordering rule, and I cannot change that order.
I am getting ~20000 of these rows and the RES is misaligned.
I'd like to make a simple query which would collect all needed records by a list IDs and would give me a custom defined ordered list of results.
Something like:
SELECT RES FROM TABLE1 WHERE ID IN ('A_12', 'C_23', 'B_28', 'B_27')
and I'd lke it to return
1.89
2.67
1.02
4.53
I understand IN query would not follow order as under the hood it most likely gets translated to (ID = A OR ID = B OR ID = C) query.
How do I enforce the result of the IN query to maintain my defined order? Do I need to create a temp table with one column for maintaining order? Any good solutions?
Use JOIN instead of using IN and explicitly specify your order:
DECLARE #Test TABLE (
ID VARCHAR(32),
RES DECIMAL(5,2)
)
INSERT #Test (ID, RES)
VALUES
('A_12', 1.89),
('B_27', 4.53),
('B_28', 3.54),
('C_23', 2.67)
SELECT t.ID, t.RES
FROM #Test t
JOIN (
VALUES
('A_12', 1),
('C_23', 2),
('B_28', 3),
('B_27', 4)
) o(ID, OrderId) ON t.ID = o.ID
ORDER BY o.OrderId
Instead of temp table you can use values where you specify the desired order in the additional column, like this:
declare #table1 table(id varchar(10), res decimal(10,2));
insert into #table1 (id, res)
values
('A_12', 1.89),
('B_27', 4.53),
('B_28', 3.54),
('C_23', 2.67);
select t.*
from #table1 t
join (values(1, 'A_12'), (2, 'C_23'), (3, 'B_28'), (4, 'B_27')) v(id,val)
on t.id = v.val
order by v.id;
#Table1 here is a substitute of your physical Table1.
There is no order to keep.
Returns of a select are NOT ORDERED by SQL basic definition, UNLESS YOU DEFINE AN ORDER.
So, there is no order to keep. Period.
If you want to keep one, use a temporary table / table variable for the valeus in IN (and obviously a join) and order by an order you also keep in a second field in said variable.
And no, this is not new - SQL is based on the SET theorem ever since Cobb published his famous paper back in the 1960s or so and never had order in returned results outside of side effects of implementation.
Do I need to create a temp table with one column for maintaining order
This seems to be working:
create table #tmp
(
CustomOrder int,
ID varchar(100)
)
insert into #tmp values (1, 'A_12')
insert into #tmp values (2, 'C_23')
insert into #tmp values (3, 'B_28')
insert into #tmp values (4, 'B_27')
query:
SELECT RES FROM TABLE1 INNER JOIN #tmp ON TABLE1.ID = #tmp.ID WHERE TABLE1.ID IN ('A_12', 'C_23', 'B_28', 'B_27')
ORDER BY #tmp.CustomOrder
output:
1.89
2.67
1.02
4.53
Any better and easier solution?
Just a different approach:
SELECT RES FROM TABLE1 WHERE ID IN ('A_12')
UNION ALL
SELECT RES FROM TABLE1 WHERE ID IN ('C_23')
UNION ALL
SELECT RES FROM TABLE1 WHERE ID IN ('B_28')
UNION ALL
SELECT RES FROM TABLE1 WHERE ID IN ('B_27')
I supposed that the JOIN option is more efficent than this approach. If you want to automatize this option:
DROP TABLE #TABLE1
CREATE TABLE #TABLE1(ID NVARCHAR(4), RES FLOAT)
INSERT INTO #TABLE1 VALUES('A_12',1.89)
INSERT INTO #TABLE1 VALUES('B_27',4.53)
INSERT INTO #TABLE1 VALUES('B_28',1.02)
INSERT INTO #TABLE1 VALUES('C_23',2.67)
DECLARE #ID TABLE(ID NVARCHAR(4) not null);
--HERE HAVE TO INSERT IN ORDER YOU WANT TO RETURN THE RESULTS IN THE QUERY
insert into #ID VALUES('A_12')
insert into #ID VALUES('B_27')
insert into #ID VALUES('B_28')
insert into #ID VALUES('C_23')
DECLARE #UNIONALL NVARCHAR(10) = CHAR(13) + N'UNION ALL'
DECLARE #QUERY NVARCHAR(MAX) = NULL
DECLARE #ID_SEARCH NVARCHAR(4) = NULL
DECLARE C CURSOR FAST_FORWARD FOR SELECT ID FROM #ID
OPEN C
FETCH NEXT FROM C INTO #ID_SEARCH
SET #QUERY = N'SELECT RES FROM #TABLE1 WHERE ID = ''' + #ID_SEARCH + ''' '
FETCH NEXT FROM C INTO #ID_SEARCH
WHILE ##FETCH_STATUS = 0 BEGIN
SET #QUERY = #QUERY + #UNIONALL
SET #QUERY = #QUERY + N' SELECT RES FROM #TABLE1 WHERE ID = ''' + #ID_SEARCH + ''' '
FETCH NEXT FROM C INTO #ID_SEARCH
END
EXECUTE master..sp_executesql #QUERY

Based on a variable: SQL Query to fetch data for multiple values from one column OR get all values from that column

Problem Statement :
when #a has a single word(Ex. 'name1') OR comma separated string (Example 'name1,name2,name3') then the query should return the manager names of employees with name1 and name2 and name3
when #a has an empty string then return the manager names of all the employees in the emp_master table
I have defined a stored procedure where I pass a variable.
This variable can be a comma separated string, a single word or an empty string.
If the string is comma separated then I split that string and get values based on the return table of split statement
else
I get the related value of the non comma separated data using normal subquery
I have tried to achieve this in the following way
Declare #a varchar(50)= ''
select emp.Name from
emp_master emp
where
(LEN(#a)=0 AND emp.Name in
(
SELECT DISTINCT [Name] FROM
[dbo].[Emp_Master] WHERE [EmpId] IN
(
SELECT
DISTINCT [MGR_ID]
FROM [dbo].[Emp_Master]
)
)
)
OR
emp.Name in (Select * from [dbo].[SplitString](#a, ','))
Details for the above sample:
[dbo].[SplitString] - custom written function : returns a table of split values. So
Select * from [dbo].SplitString
will return
SplitTable
----------
name1
name2
name3
and
Select * from [dbo].[SplitString](',','name1')
will return
SplitTable
----------
name1
[dbo].[Emp_Master] contains data for all the employees
[MGR_ID] is the column which has the employeeID of the employee manager
#a is the input variable
The Database is MS SQL 2008
My current solution(the above insane query) solves my purpose but it is very slow, it would be helpful to get an optimized and faster working solution for the problem
Emp_master Table has 400 000 rows, 30 columns
There are 18 000 managers in that table
CREATE NONCLUSTERED INDEX ix ON dbo.Emp_Master ([MGR_ID])
GO
DECLARE #a VARCHAR(50) = ''
DECLARE #t TABLE (val VARCHAR(50) PRIMARY KEY WITH(IGNORE_DUP_KEY=ON))
INSERT INTO #t
SELECT item = t.c.value('.', 'INT')
FROM (
SELECT txml = CAST('<r>' + REPLACE(#a, ',', '</r><r>') + '</r>' AS XML)
) r
CROSS APPLY txml.nodes('/r') t(c)
SELECT /*DISTINCT*/ [Name]
FROM dbo.Emp_Master e1
WHERE (
#a = ''
AND
e1.[EmpId] IN (SELECT DISTINCT MGR_ID FROM dbo.Emp_Master)
)
OR (
#a != ''
AND
e.Name IN (SELECT * FROM #t)
)
OPTION(RECOMPILE)
TRY THIS
CREATE NONCLUSTERED INDEX IX_MGR_ID_Emp_Master ON dbo.Emp_Master ([MGR_ID])
GO
Create Procedure searchname (#a varchar(255))
as
IF (#a = '')
BEGIN
EXEC Searchname1 #a
END
ELSE
BEGIN
EXEC Searchname2 #a
END
GO
Create Procedure Searchname1 (#a varchar(255))
AS
SELECT DISTINCT [Name] FROM
[dbo].[Emp_Master] m1 WHERE
exists
(
SELECT
*
FROM [dbo].[Emp_Master] m2
WHERE
m1.[EmpId]= m2.[MGR_ID]
)
GO
Create Procedure Searchname2 (#a varchar(max))
AS
Select #a = ' SELECT '''+replace( #a,',',''' Union ALL SELECT ''')+' '''
Create table #names (name varchar(255))
insert into #names
EXEC ( #a )
select emp.Name from
emp_master emp
WHERE
emp.Name in( Select name FRom #names)
option (recompile)
IF YOU ARE ALREADY DEALING WITH SQL INJECTION AT APPLICATION LEVEL
THEN
ALTER procedure [dbo].[Searchname2] (#a varchar(max))
AS
select #a = ''''+replace ( #a,',',''',''')+''''
DECLARE #sql NVARCHAR(MAX) = N'
select distinct emp.Name from
emp_master emp
WHERE
emp.Name in( '+#a+')'
EXEC (#sql)

How to autoincrement the id without identity?

I'm trying do to a bulk insert from another table in sql server. My query is currently like that :
INSERT INTO Table1(Id, Value)
SELECT ??, Value
FROM Table2;
Now, my problem is obviously by what I replace ??. Id is an integer column without an identity property. I would like that for each inserted row, Id take the current max(Id) + 1.
Can I do that directly in my insert command?
If you were using a newer version of SQL Server (2008+) you could try ROW_NUMBER():
DECLARE #BASE INT
SET #BASE = (SELECT IsNull(MAX(ID),0) FROM Table1)
INSERT INTO Table1(Id, Value)
SELECT
#BASE + ROW_NUMBER() OVER (ORDER BY Value) ID,
Value
FROM Table2;
SQL Fiddle
Since you are using SQL Server 2000, you could try like bellow:
DECLARE #BASE INT
SET #BASE = (SELECT IsNull(MAX(ID),0) FROM Table1)
INSERT INTO Table1(Id, Value)
SELECT
#BASE + (SELECT COUNT(*) FROM Table2 AS i2 WHERE i2.Value <= a.Value),
a.Value
FROM Table2 a
But it will only works if Value in Table2 is unique
SQL Fiddle
If Table2 has a primary key (field PK), then you could use:
INSERT INTO Table1(Id, Value)
SELECT
#BASE + (SELECT COUNT(*) FROM Table2 AS i2 WHERE i2.PK <= a.PK),
a.Value
FROM Table2 a
Here is one wicked way.
We create a temp table with identity to generate new ids. This way we avoid the while loop.
DECLARE #CurrentMaxID INT,
#DynamicQuery NVARCHAR(MAX)
--TODO : Acquired table lock here on table1
SELECT #FirstNextID = ISNULL(MAX(Id), 0)
FROM Table1 --WITH(TABLOCK)
CREATE TABLE #TempTableWithID( Table2Id INT,
Table1FuturId INT IDENTITY(1, 1))
INSERT INTO #TempTableWithID(Table2Id)
SELECT Id --Here we use identity to generate table1 futur id
FROM Table2
INSERT INTO Table1(Id, value)
SELECT Temp.Table1FuturId + #FirstNextID,
Table2.Value
FROM Table2
INNER JOIN #TempTableWithID AS Temp ON Table2.Id = Temp.Table2Id
--TODO : release table lock here on table1
DROP TABLE #TempTableWithID
If I'm understanding you correctly, this should work.
CREATE TABLE #tbl1 (ID int, Value float)
CREATE TABLE #tbl2 (ID int, Value float)
INSERT INTO #tbl2 values (4, 2.0)
INSERT INTO #tbl2 values (8, 3.0)
INSERT INTO #tbl2 values (6, 4.0)
INSERT INTO #tbl1 values (1,1.0)
INSERT INTO #tbl1 values (3,3)
INSERT INTO #tbl1 values (9,3)
/*meat and potatoes start*/
INSERT INTO #tbl1(Id, Value)
SELECT (SELECT MAX(ID) FROM #tbl1) + ROW_NUMBER() OVER (ORDER BY Value) ID, Value
FROM #tbl2;
/*meat and potatoes end*/
Select * From #tbl1
drop table #tbl1
drop table #tbl2
Why not IDENT_CURRENT() ?
SELECT IDENT_CURRENT('yourtablename')
It gives you the next ID reference. But this only works if the ID column has IDENTITY turned on.
OR you can try a SEQUENCE and the NEXT VALUE FOR.
i.e.
CREATE TABLE Test.TestTable
(CounterColumn int PRIMARY KEY,
Name nvarchar(25) NOT NULL) ;
GO
INSERT Test.TestTable (CounterColumn,Name)
VALUES (NEXT VALUE FOR Test.CountBy1, 'Syed') ;
GO
SELECT * FROM Test.TestTable;
GO

How to concatenate using in sql server

I have a table where the data are like
Data
a
b
c
I need to write a SQL query to bring the following output
Data
abc
How to do the same by using in SQL Server 2000
Thanks
I don't know how/if it can be done with XML RAW. This approach works in SQL2000 though.
DECLARE #Data varchar(8000)
set #Data =''
select #Data = #Data + Data
FROM #t
ORDER BY Data
SELECT #Data
Edit Oh I've just seen your other question where Cade gave you a link. Doesn't KM's answer on that link work for you?
KM's test query
--combine parent and child, children are CSV onto parent row
CREATE TABLE #TableA (RowID int, Value1 varchar(5), Value2 varchar(5))
INSERT INTO #TableA VALUES (1,'aaaaa','A')
INSERT INTO #TableA VALUES (2,'bbbbb','B')
INSERT INTO #TableA VALUES (3,'ccccc','C')
CREATE TABLE #TableB (RowID int, TypeOf varchar(10))
INSERT INTO #TableB VALUES (1,'wood')
INSERT INTO #TableB VALUES (2,'wood')
INSERT INTO #TableB VALUES (2,'steel')
INSERT INTO #TableB VALUES (2,'rock')
INSERT INTO #TableB VALUES (3,'plastic')
INSERT INTO #TableB VALUES (3,'paper')
SELECT
a.*,dt.CombinedValue
FROM #TableA a
LEFT OUTER JOIN (SELECT
c1.RowID
,STUFF(REPLACE(REPLACE(
(SELECT
', ' + TypeOf as value
FROM (SELECT
a.RowID,a.Value1,a.Value2,b.TypeOf
FROM #TableA a
LEFT OUTER JOIN #TableB b ON a.RowID=b.RowID
) c2
WHERE c2.rowid=c1.rowid
ORDER BY c1.RowID, TypeOf
FOR XML RAW
)
,'<row value="',''),'"/>','')
, 1, 2, '') AS CombinedValue
FROM (SELECT
a.RowID,a.Value1,a.Value2,b.TypeOf
FROM #TableA a
LEFT OUTER JOIN #TableB b ON a.RowID=b.RowID
) c1
GROUP BY RowID
) dt ON a.RowID=dt.RowID

Resources