How to get previously changed records? - sql

How to get previously changed records for particular data?
From the below records, I have tried but didn't got idea to make it
DECLARE #l_MSISDN AS TABLE
(
ID INT IDENTITY(1,1),
Old NVARCHAR(50),
New NVARCHAR(50),
AuthDate DATETIME
)
INSERT INTO #l_MSISDN VALUES
('A','B',GETDATE()),
('B','C',GETDATE()),
('C','D',GETDATE()),
('R','T',GETDATE()),
('R','Q',GETDATE())
;WITH CTE AS
(
select New,OLD,AuthDate
from #l_MSISDN nolock
UNION ALL
SELECT * from cte
where New = OLD
)
select * from cte
order by AuthDate
Old New AuthDate
A B 2018-04-04 11:06:51.953
B C 2018-04-04 10:39:03.563
C D 2014-12-20 06:25:20.397
R T 2016-02-10 15:25:20.123
Q R 2015-09-21 15:25:20.330
I expect output as
old new Authdate
A B 2014-12-20 06:25:20.397
B C 2015-09-21 15:25:20.330
C D 2016-02-10 15:25:20.123
I'll give input as D

You weren't too far off with your attempt. You need to reference your original table in the UNION ALL as well by a JOIN:
DECLARE #Start char(1) = 'D';
SELECT *
INTO #Temp
FROM (VALUES ('A', 'B', CONVERT(datetime,'2018-04-04T11:06:51.953')),
('B', 'C', CONVERT(datetime,'2018-04-04T10:39:03.563')),
('C', 'D', CONVERT(datetime,'2014-12-20T06:25:20.397')),
('R', 'T', CONVERT(datetime,'2016-02-10T15:25:20.123')),
('Q', 'R', CONVERT(datetime,'2015-09-21T15:25:20.330'))) V (Old, New, AuthDate);
WITH rCTe AS(
SELECT T.Old,
T.New,
T.AuthDate
FROM #Temp T
WHERE T.New = #Start
UNION ALL
SELECT T.Old,
T.New,
T.AuthDate
FROM #Temp T
JOIN rCTE r ON r.Old = T.New)
SELECT r.Old,
r.New,
r.AuthDate
FROM rCTe r;
DROP TABLE #Temp;

Related

SQL Server query to extract all rows

I've two database tables, one called "Headers" and one called "Rows".
The structure is:
Header: IDPK | Description
Row: IDPK | IDPK_Header | Item_ID | Qty
I need to do a query that says: "From a Header, IDPK find another header that have the same number of rows and the same item ID and quantity".
For example:
Header Rows
IDPK Description IDPK Item_ID Qty
1 'Test1' 1 'A' 10
1 'Test1' 2 'B' 20
2 'Test2' 3 'A' 10
2 'Test2' 4 'B' 20
3 'Test3' 5 'A' 5
3 'Test3' 6 'B' 20
4 'Test4' 7 'A' 10
Header Test1 match Test2 but not Test3 and Test4
The problem is that the number of rows must be exactly the same. I try with ALL operator but without luck.
How I can do the query with an eye for the performance? The two tables can be very huge (~500.000 records).
Assuming there are no duplicates:
with r as (
select r.*, count(*) over (partition by idpk_header) as num_items
from rows r
)
select r1.idpk_header, r2.idpk_header
from r r1 join
r r2
on r1.item_id = r1.item_id and r2.qty = r1.qty and r2.num_items = r1.num_items
group by r1.idpk_header, r2.idpk_header, r1.num_items
having count(*) = r1.num_items;
Basically, this does a self-join on the items, so you only get matches. The on validates that the two have the same number of items. And the having guarantees that all match.
Note: This version returns each match of the header to itself. That is a nice check. You can of course filter this out in the on or a where clause.
If you do have duplicate items, you can simply replace r with:
select idpk_header, item_id, sum(qty) as qty,
count(*) over (partition by idpk_header) as num_items
from rows r
group by idpk_header, item_id;
I woul suggest using a forxml query in order to create the list of items per IDPK. Next I would search for matching item lists and quantities. See following example:
DECLARE #Headers TABLE(
IDPK INT,
Description NVARCHAR(100)
)
DECLARE #Rows TABLE(
IDPK INT,
ITEMID NVARCHAR(1),
Qty INT
)
INSERT INTO #Headers VALUES
(1, 'Test1'),
(2, 'Test2'),
(3, 'Test3'),
(4, 'Test4'),
(5, 'Test5')
INSERT INTO #Rows VALUES
(1, 'A', 10),
(1, 'B', 20),
(2, 'A', 10),
(2, 'B', 20),
(3, 'A', 5 ),
(3, 'B', 20),
(4, 'C', 10),
(5, 'A', 10),
(5, 'C', 20)
;
WITH cteHeaderRows AS(
SELECT IDPK
,ItemIDs=STUFF(
(
SELECT ',' + CAST(ITEMID AS VARCHAR(MAX))
FROM #Rows t2
WHERE t2.IDPK = t1.IDPK
ORDER BY ITEMID, QTY
FOR XML PATH('')
),1,1,''
)
,Qtys=STUFF(
(
SELECT ',' + CAST(Qty AS VARCHAR(MAX))
FROM #Rows t2
WHERE t2.IDPK = t1.IDPK
ORDER BY ITEMID, QTY
FOR XML PATH('')
),1,1,''
)
FROM #Rows t1
GROUP BY IDPK
),
cteFilter AS(
SELECT h1.IDPK AS IDPK1, h2.IDPK AS IDPK2
FROM cteHeaderRows h1
JOIN cteHeaderRows h2 ON h1.IDPK != h2.IDPK AND h1.ItemIDs = h2.ItemIDs AND h2.Qtys = h1.Qtys
)
SELECT DISTINCT h.IDPK, h.Description, r.ItemID, r.Qty
FROM #Headers h
JOIN cteFilter f ON f.IDPK1 = h.IDPK
JOIN #Rows r ON r.IDPK = f.IDPK1
ORDER BY 1,3,4

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 logic for the Vlookup function in excel/ How to do a Vlookup in SQL

I have a table with 2 columns OLD_VALUE and NEW_VALUE and 5 rows. 1st row has values (A,B). Other row values can be (B,C),(C,D),(E,D),(D,F). I want to update all the old values with the new value (how a vlookup in excel would work) The Final Result Required: The newest value in the above example would be D,F. i.e. D points to F. E and C point to D. B points to C and A points to B. D pointing to F is the last and newest and there are no more successions after D,F. So (OLD_VALUE,NEW_VALUE)->(A,F), (B,F), (C,F), (D,F), (E,F). I want 5 rows with the NEW_VALUE as 'F'. The level of successions can be ranging from 1 to x.
This is the table I have used for the script:
declare #t as table(old_value char(1), new_value char(1));
insert into #t values('A','B')
insert into #t values('B','C')
insert into #t values('C','D')
insert into #t values('E','D')
insert into #t values('D','F')
This needs to be done with a recursive CTE. First, you will need to define an anchor for the CTE. The anchor in this case should be the record with the latest value. This is how I define the anchor:
select old_value, new_value, 1 as level
from #t
where new_value NOT IN (select old_value from #t)
And here is the recursive CTE I used to locate the latest value for each row:
;with a as(
select old_value, new_value, 1 as level
from #t
where new_value NOT IN (select old_value from #t)
union all
select b.old_value, a.new_value, a.level + 1
from a INNER JOIN #t b ON a.old_value = b.new_value
)
select * from a
Results:
old_value new_value level
--------- --------- -----------
D F 1
C F 2
E F 2
B F 3
A F 4
(5 row(s) affected)
I think a recursive CTE like the following is what you're looking for (where the parent is the row whose second value does not exist as a first value elsewhere). If there's no parent(s) to anchor to, this would fail (e.g. if you had A->B, B->C, C->A, you'd get no result), but it should work for your case:
DECLARE #T TABLE (val1 CHAR(1), val2 CHAR(2));
INSERT #T VALUES ('A', 'B'), ('B', 'C'), ('C', 'D'), ('E', 'D'), ('D', 'F');
WITH CTE AS
(
SELECT val1, val2
FROM #T AS T
WHERE NOT EXISTS (SELECT 1 FROM #T WHERE val1 = T.val2)
UNION ALL
SELECT T.val1, CTE.val2
FROM #T AS T
JOIN CTE
ON CTE.val1 = T.val2
)
SELECT *
FROM CTE;

Matching multiple key/value pairs in SQL

I have metadata stored in a key/value table in SQL Server. (I know key/value is bad, but this is free-form metadata supplied by users, so I can't turn the keys into columns.) Users need to be able to give me an arbitrary set of key/value pairs and have me return all DB objects that match all of those criteria.
For example:
Metadata:
Id Key Value
1 a p
1 b q
1 c r
2 a p
2 b p
3 c r
If the user says a=p and b=q, I should return object 1. (Not object 2, even though it also has a=p, because it has b=p.)
The metadata to match is in a table-valued sproc parameter with a simple key/value schema. The closest I have got is:
select * from [Objects] as o
where not exists (
select * from [Metadata] as m
join #data as n on (n.[Key] = m.[Key])
and n.[Value] != m.[Value]
and m.[Id] = o.[Id]
)
My "no rows exist that don't match" is an attempt to implement "all rows match" by forming its contrapositive. This does eliminate objects with mismatching metadata, but it also returns objects with no metadata at all, so no good.
Can anyone point me in the right direction? (Bonus points for performance as well as correctness.)
; WITH Metadata (Id, [Key], Value) AS -- Create sample data
(
SELECT 1, 'a', 'p' UNION ALL
SELECT 1, 'b', 'q' UNION ALL
SELECT 1, 'c', 'r' UNION ALL
SELECT 2, 'a', 'p' UNION ALL
SELECT 2, 'b', 'p' UNION ALL
SELECT 3, 'c', 'r'
),
data ([Key], Value) AS -- sample input
(
SELECT 'a', 'p' UNION ALL
SELECT 'b', 'q'
),
-- here onwards is the actual query
data2 AS
(
-- cnt is to count no of input rows
SELECT [Key], Value, cnt = COUNT(*) OVER()
FROM data
)
SELECT m.Id
FROM Metadata m
INNER JOIN data2 d ON m.[Key] = d.[Key] AND m.Value= d.Value
GROUP BY m.Id
HAVING COUNT(*) = MAX(d.cnt)
The following SQL query produces the result that you require.
SELECT *
FROM #Objects m
WHERE Id IN
(
-- Include objects that match the conditions:
SELECT m.Id
FROM #Metadata m
JOIN #data d ON m.[Key] = d.[Key] AND m.Value = d.Value
-- And discount those where there is other metadata not matching the conditions:
EXCEPT
SELECT m.Id
FROM #Metadata m
JOIN #data d ON m.[Key] = d.[Key] AND m.Value <> d.Value
)
Test schema and data I used:
-- Schema
DECLARE #Objects TABLE (Id int);
DECLARE #Metadata TABLE (Id int, [Key] char(1), Value char(2));
DECLARE #data TABLE ([Key] char(1), Value char(1));
-- Data
INSERT INTO #Metadata VALUES
(1, 'a', 'p'),
(1, 'b', 'q'),
(1, 'c', 'r'),
(2, 'a', 'p'),
(2, 'b', 'p'),
(3, 'c', 'r');
INSERT INTO #Objects VALUES
(1),
(2),
(3),
(4); -- Object with no metadata
INSERT INTO #data VALUES
('a','p'),
('b','q');

Identifying duplicate GROUPS of data in SQL

My question is how to identify duplicate (repeating) 'groups' of data within an SQL table. I am using SQL Server 2005 at the moment so prefer solutions based on that or ansi-sql.
Here is a sample table and expected result (below) to base this question on:
declare #data table (id nvarchar(10), fund nvarchar(1), xtype nvarchar(1))
insert into #data select 'Switch_1', 'A', 'S'
insert into #data select 'Switch_1', 'X', 'B'
insert into #data select 'Switch_1', 'Y', 'B'
insert into #data select 'Switch_1', 'Z', 'B'
insert into #data select 'Switch_2', 'A', 'S'
insert into #data select 'Switch_2', 'X', 'B'
insert into #data select 'Switch_2', 'Y', 'B'
insert into #data select 'Switch_2', 'Z', 'B'
insert into #data select 'Switch_3', 'C', 'S'
insert into #data select 'Switch_3', 'D', 'B'
insert into #data select 'Switch_4', 'C', 'S'
insert into #data select 'Switch_4', 'F', 'B'
(new data)
insert into #data select 'Switch_5', 'A', 'S'
insert into #data select 'Switch_5', 'X', 'B'
insert into #data select 'Switch_5', 'Y', 'B'
insert into #data select 'Switch_5', 'Z', 'B'
-- id fund xtype match
-- ---------- ---- ----- ---------
-- Switch_1 A S Match_1
-- Switch_1 X B Match_1
-- Switch_1 Y B Match_1
-- Switch_1 Z B Match_1
-- Switch_2 A S Match_1
-- Switch_2 X B Match_1
-- Switch_2 Y B Match_1
-- Switch_2 Z B Match_1
-- Switch_3 C S
-- Switch_3 D B
-- Switch_4 C S
-- Switch_4 F B
(new results)
-- Switch_5 A S Match_1
-- Switch_5 X B Match_1
-- Switch_5 Y B Match_1
-- Switch_5 Z B Match_1
I only want matches on an ALL or NOTHING basis (i.e. All records in the group match all records in another group - not a part match). Any match id can be used (I have used Match_1 above but can be numeric etc.)
Thanks for any help here.
(EDIT: I guess I should add that there could be any number of rows per group, not just the 2 or 4 shown in the sample above - and I'm also trying to avoid cursors)
(EDIT 2: I seem to have an issue if there are more than one matches found. The output from the SQL supplied is returning duplicate records for Switch_1 when there are more than one matches found. I have updated the sample data accordingly. Not sure if Lieven is still following this - I'm also looking at the solution and will post here if found.)
The flow of execution is as follows
q: Combine all funds and xtypes of one id into one string using an XML PATH construction
r: Select a ROW_NUMBER and the respective id's for matching groups
Select the results by LEFT JOINING #data and r
SQL Statement
;WITH q AS (
SELECT DISTINCT d.id
, DuplicateData = STUFF((SELECT ', ' + fund + xtype FROM #data WHERE id = d.id FOR XML PATH('')), 1, 2, '')
FROM #data d
)
, r AS (
SELECT id1 = q1.id
, id2 = q2.id
, rn = ROW_NUMBER() OVER (ORDER BY q1.ID)
FROM q q1
INNER JOIN q q2 ON q1.DuplicateData = q2.DuplicateData AND q1.id < q2.id
)
SELECT id
, fund
, xtype
, match = 'Match_' + CAST(r.rn AS VARCHAR(32))
FROM #data d
LEFT OUTER JOIN r ON d.id IN (r.id1, r.id2)
Results
id fund xtype match
---------- ---- ----- --------------------------------------
Switch_1 A S Match_1
Switch_1 X B Match_1
Switch_1 Y B Match_1
Switch_1 Z B Match_1
Switch_2 A S Match_1
Switch_2 X B Match_1
Switch_2 Y B Match_1
Switch_2 Z B Match_1
Switch_3 C S NULL
Switch_3 D B NULL
Switch_4 C S NULL
Switch_4 F B NULL
Here it is another query for it:
create table #temp1 (
id varchar(10),
fund nvarchar(1),
xtype nvarchar(1)
)
insert into #temp1 select 'Switch_1', 'A', 'S'
insert into #temp1 select 'Switch_1', 'X', 'B'
insert into #temp1 select 'Switch_1', 'Y', 'B'
insert into #temp1 select 'Switch_1', 'Z', 'B'
insert into #temp1 select 'Switch_2', 'A', 'S'
insert into #temp1 select 'Switch_2', 'X', 'B'
insert into #temp1 select 'Switch_2', 'Y', 'B'
insert into #temp1 select 'Switch_2', 'Z', 'B'
insert into #temp1 select 'Switch_3', 'C', 'S'
insert into #temp1 select 'Switch_3', 'D', 'B'
insert into #temp1 select 'Switch_4', 'C', 'S'
insert into #temp1 select 'Switch_4', 'F', 'B'
select t1.*, case when t2.equal = t3.total then 'True' else 'False' end as 'Match' from #temp1 t1
left outer join (select m.id, count(m2.id) as 'equal' from #temp1 m
inner join #temp1 m2 on m.Id <> m2.Id and m.fund = m2.fund and m.xtype = m2.xtype
group by m.id) t2 on t1.id = t2.id
inner join (select m3.id, count(m3.fund) as 'total' from #temp1 m3 group by m3.id) t3 on t3.id = t1.id
drop table #temp1