Create range of ID without cursor - sql

Not sure if this or similar question is asked already but i could not find one.
The Requirement to create range of IDs while the Value is not changed. This schema can be used:
declare #mytable as table(ID int, Val int)
insert into #mytable values
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(5, 2),
(6, 2),
(7, 2),
(8, 1),
(9, 1),
(10, 1),
(11, 4),
(12, 4),
(13, 4),
(14, 4),
(15, 4),
(16, 5);
And the expected result would be
StartID EndID Val
1 3 1
4 7 2
8 10 1
11 15 4
16 16 5
Now I can achieve this by running cursor and If n case the number of records will be millions, I think, cursor will be slower. I hope it can be written using some compound query but could not figure-out how.
So I need help in writing that kind of query and needless to mention yet, it is not a school/collage project/assignment.

This is a gaps-and-islands scenario where you're trying to group records together based on the change in Val.
This is using window functions to determine when the Val changes, and assign the island_nbr.
Answer:
select min(b.ID) as StartID
, max(b.ID) as EndID
, max(b.Val) as Val
from (
select a.ID
, a.Val
, sum(a.is_chng_flg) over (order by a.ID asc) as island_nbr
from (
select m.ID
, m.Val
, case lag(m.Val, 1, m.Val) over (order by m.ID asc) when m.Val then 0 else 1 end is_chng_flg
from #mytable as m
) as a
) as b
group by b.island_nbr --forces the right records to show up
order by 1

This is a gaps-and-islands problem. But the simplest method is the difference of row numbers:
select min(id) as startId, max(id) as endId, val
from (select t.*,
row_number() over (order by id) as seqnum,
row_number() over (partition by val order by id) as seqnum_v
from #mytable t
) t
group by (seqnum - seqnum_v), val
order by startId;

Related

How to write a running total based on criteria in T-SQL

I'm building a report which gives me the total count of unique accounts within a calendar month.
However, this total is based on the number of active accounts (accounts subscribed to a service), and once their contract ends they will be excluded from the total count.
For example, Company A has subscribed to the service on 1/1/2018 and their contract ends on 1/1/2020. So Company A should be included in the total count of unique accounts for all the months their under contract until their contract ends.
End Result would look something like this:
Here is the SQl query that I have so far. How can I write the code such that it will give me this cumulative/running total. I added the columns for reference.
SELECT A.Name, CA.Name, CA.Start_Date__c, CA.End_Date__c, CA.Product_Code_CPQ__c
FROM [salesforce].[Client_Asset__c] AS CA
INNER JOIN salesforce.Account AS A
ON CA.Account__c = A.Id
WHERE Product_Code_CPQ__c IN(
'DSWPSTRSUB','DSWPESSSUB','DSWPPROSUB','DSWPHOSTSUB','DSWPMULTIHOSTSUB','DSWPOLXWRAPFPE',
'DSWPOLXWRAPSUB','WPCALENDARFORALT','WPCALHOSTINGBUN','IMWPTM','SBWPRET','SBWPRETNR','WORDPLUMWEBSUCCESS',
'WORDPWEBSUCCESS','WORDPOGS','FDSTRWORDPDESGNSUB','FDWPFPE','WORDPEMERGHOST','WORDPSUBBUN','WPOLXPLUGIN',
'POSTSTARTWORDPAF','POSTWORDPSTARTBUN','LUMWORDPSSUBBUN','WORDPLUMOGS','LUMFDSTRWPDESGNSUB',
'LUMPSTWORDPSTRBUN','LUMPOSTSTRTWORDPAF','FDWPEMERGFPE')
AND End_Date__c > GETDATE()
AND Active__c = 1
Try something like that:
CREATE TABLE #tmp ([month] INT, [group] VARCHAR(10), [value] REAL)
INSERT INTO #tmp ([month], [group], [value]) VALUES
(1, 'A', 1), (2, 'A', 5), (3, 'A', 3), (4, 'A', 2), (5, 'A', 8),
(1, 'B', 7), (2, 'B', 3), (3, 'B', 2), (4, 'B', 4), (5, 'B', 6)
SELECT c.[month], c.[group], c.current_total, r.running_total
FROM
(
SELECT [month],[group], SUM([value]) current_total
FROM #tmp
GROUP BY [month],[group]
) C JOIN
(
SELECT [month],[group], SUM([value]) OVER (partition BY [group] ORDER BY [month]) running_total
FROM #tmp
) R ON C.[month]=R.[month] AND C.[group]=R.[group]
ORDER BY 2,1
Tested on mssql 2016. Handle potential missing values yourself.

Update same data from the same table

I have a table that has registration of several CAR (repeated and not repeated), so I intend to update a field (active) of all repeated registrations leaving 1 more recent line. Exemple of table data:
I want my data to be like this:
I tried to make this code, but it is not working correctly. Because updated the first row
-----create table
create table dbo.test( id int, CAR varchar(30), ACTIVE int)
insert into dbo.test(id, CAR, ACTIVE)
values
(1, 'AAA-25-35', 0),
(2, 'LDB-25-35', 0),
(3, 'LDB-00-35', 0),
(4, 'LDB-25-35', 0),
(5, 'LDB-00-35', 0),
(6, 'LDC-10-10', 0),
(7, 'LDC-10-10', 0),
(8, 'LDB-00-35', 0)
select * from dbo.test
----update table
WITH CTE AS
(
SELECT
row_number() over (partition by CAR order by id) as t
, CAR , ACTIVE
FROM dbo.test
)
update CTE
SET ACTIVE = 1
WHERE t=1
select * from dbo.test
You could just add an EXISTS portion:
-----create table
CREATE TABLE #Test(ID INT, CAR VARCHAR(30), ACTIVE INT)
INSERT INTO #Test(ID, CAR, ACTIVE)
VALUES
(1, 'AAA-25-35', 0),
(2, 'LDB-25-35', 0),
(3, 'LDB-00-35', 0),
(4, 'LDB-25-35', 0),
(5, 'LDB-00-35', 0),
(6, 'LDC-10-10', 0),
(7, 'LDC-10-10', 0),
(8, 'LDB-00-35', 0)
----update table
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY CAR ORDER BY ID) AS t,
CAR,
ACTIVE
FROM #Test
)
UPDATE CTE
SET ACTIVE = 1
WHERE t=1
AND EXISTS (SELECT 1 FROM CTE c WHERE c.CAR = CTE.CAR GROUP BY CAR HAVING COUNT(*) > 1)
SELECT *
FROM #Test
You can use count analytical function in your query as follows:
WITH CTE AS
(
SELECT
row_number() over (partition by CAR order by id) as t
, count(*) over (partition by car) as cnt
, CAR , ACTIVE
FROM dbo.test
)
update CTE
SET ACTIVE = 1
WHERE t=1 and cnt>1

Update same data from the same table depending on a column

I have a similar case of a table, Like the case of this link Update same data from the same table
but in my case it must update depending on the column "dependency". In other words, it updates the repetitions in the tables always leaving the most recent line and for table that only have one line it does not update. My data is like this:
I want it to be updated like this:
I tryed this code:
create table dbo.test( id int, CAR varchar(30), ACTIVE int, dependency int)
insert into dbo.test(id, CAR, ACTIVE, dependency)
values
(1, 'AAA-25-35', 0,1),
(2, 'LDB-25-35', 0,2),
(3, 'LDB-00-35', 0,2),
(4, 'LDB-25-35', 0,2),
(5, 'LDB-00-35', 0,2),
(6, 'LDC-10-10', 0,2),
(7, 'LDC-10-10', 0,2),
(8, 'LDB-00-35', 0,2),
(9, 'AAA-25-35', 0,1),
(10, 'AAA-25-35', 0,3),
(11, 'AAA-25-35', 0,3),
(12, 'BBB-25-35', 0,2),
(13, 'BBB-25-35', 0,3),
(14, 'BBB-25-35', 0,3)
GO
SELECT * FROM TEST
WITH CTE AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY CAR ORDER BY ID) AS t,
CAR,
ACTIVE
FROM Test
)
UPDATE CTE
SET ACTIVE = 1
WHERE t=1
AND EXISTS (SELECT 1 FROM CTE c WHERE c.CAR = CTE.CAR GROUP BY CAR HAVING COUNT(*) > 1)
go
SELECT * FROM test
Try changing the SELECT and WHERE clauses:
WITH CTE AS (
SELECT ROW_NUMBER() OVER(PARTITION BY CAR, dependency ORDER BY ID) AS t,
LEAD(id) OVER (PARTITION BY CAR, dependency ORDER BY ID) as next_id,
CAR,
ACTIVE
FROM Test
)
UPDATE CTE
SET ACTIVE = 1
WHERE t = 1 AND next_id IS NOT NULL

SQL Server recursive self join

I have a simple categories table as with the following columns:
Id
Name
ParentId
So, an infinite amount of Categories can be the child of a category. Take for example the following hierarchy:
I want, in a simple query that returns the category "Business Laptops" to also return a column with all it's parents, comma separator or something:
Or take the following example:
Recursive cte to the rescue....
Create and populate sample table (Please save us this step in your future questions):
DECLARE #T as table
(
id int,
name varchar(100),
parent_id int
)
INSERT INTO #T VALUES
(1, 'A', NULL),
(2, 'A.1', 1),
(3, 'A.2', 1),
(4, 'A.1.1', 2),
(5, 'B', NULL),
(6, 'B.1', 5),
(7, 'B.1.1', 6),
(8, 'B.2', 5),
(9, 'A.1.1.1', 4),
(10, 'A.1.1.2', 4)
The cte:
;WITH CTE AS
(
SELECT id, name, name as path, parent_id
FROM #T
WHERE parent_id IS NULL
UNION ALL
SELECT t.id, t.name, cast(cte.path +','+ t.name as varchar(100)), t.parent_id
FROM #T t
INNER JOIN CTE ON t.parent_id = CTE.id
)
The query:
SELECT id, name, path
FROM CTE
Results:
id name path
1 A A
5 B B
6 B.1 B,B.1
8 B.2 B,B.2
7 B.1.1 B,B.1,B.1.1
2 A.1 A,A.1
3 A.2 A,A.2
4 A.1.1 A,A.1,A.1.1
9 A.1.1.1 A,A.1,A.1.1,A.1.1.1
10 A.1.1.2 A,A.1,A.1.1,A.1.1.2
See online demo on rextester

SQL recursive logic

I have a situation where I need to configure existing client data to address a problem where our application was not correctly updating IDs in a table when it should have been.
Here's the scenario. We have a parent table, where rows can be inserted that effectively replace existing rows; the replacement can be recursive. We also have a child table, which has a field that points to the parent table. In existing data, the child table could be pointing at rows that have been replaced, and I need to correct that. I can't simply update each row to the replacing row, however, because that row could have been replaced as well, and I need the latest row to be reflected.
I was trying to find a way to write a CTE that would accomplish this for me, but I'm struggling to find a query that finds what I'm actually looking for. Here's a sample of the tables that I'm working with; the 'ShouldBe' column is what I'd like my update query to end up with, taking into account the recursive replacement of some of the rows.
DECLARE #parent TABLE (SampleID int,
SampleIDReplace int,
GroupID char(1))
INSERT INTO #parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'),
(4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
(7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')
DECLARE #child TABLE (ChildID int, ParentID int)
INSERT INTO #child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)
Desired results in child table, after the update script has been applied:
ChildID ParentID ParentID_ShouldBe
1 4 6 (4 replaced by 5, 5 replaced by 6)
2 7 9 (7 replaced by 8, 8 replaced by 9)
3 1 2 (1 replaced by 2)
4 3 3 (unchanged, never replaced)
The following returns what you are looking for:
with cte as (
select sampleid, sampleidreplace, 1 as num
from #parent
where sampleidreplace <> -1
union all
select p.sampleid, cte.sampleidreplace, cte.num+1
from #parent p join
cte
on p.sampleidreplace = cte.sampleId
)
select c.*, coalesce(p.sampleid, c.parentid)
from #child c left outer join
(select ROW_NUMBER() over (partition by sampleidreplace order by num desc) as seqnum, *
from cte
) p
on c.ParentID = p.SampleIDReplace and p.seqnum = 1
The recursive part keeps track of every correspondence (4-->5, 4-->6). The addition number is a "generation" count. We actually want the last generation. This is identified by using the row_number() function, ordering by the num in decreasing order -- hence the p.seqnum = 1.
Ok, so it took me a while and there are probably better ways to do it, but here is one option.
DECLARE #parent TABLE (SampleID int,
SampleIDReplace int,
GroupID char(1))
INSERT INTO #parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'),
(4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
(7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')
DECLARE #child TABLE (ChildID int, ParentID int)
INSERT INTO #child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)
;WITH RecursiveParent1 AS
(
SELECT SampleIDReplace, SampleID, 1 RecursionLevel
FROM #parent
WHERE SampleIDReplace != -1
UNION ALL
SELECT A.SampleIDReplace, B.SampleID, RecursionLevel + 1
FROM RecursiveParent1 A
INNER JOIN #parent B
ON A.SampleId = B.SampleIDReplace
),RecursiveParent2 AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY SampleIdReplace ORDER BY RecursionLevel DESC) RN
FROM RecursiveParent1
)
SELECT A.ChildID, ISNULL(B.ParentID,A.ParentID) ParentID
FROM #child A
LEFT JOIN ( SELECT SampleIDReplace, SampleID ParentID
FROM RecursiveParent2
WHERE RN = 1) B
ON A.ParentID = B.SampleIDReplace
OPTION(MAXRECURSION 500)
I've got a iterative SQL loop that I think sorts this out as follows:
WHILE EXISTS (SELECT * FROM #child C INNER JOIN #parent P ON C.ParentID = P.SampleIDReplace WHERE P.SampleIDReplace > -1)
BEGIN
UPDATE #child
SET ParentID = SampleID
FROM #parent
WHERE #child.ParentID = SampleIDReplace
END
Basically, the while condition compares the contents of the parent ID column in the child table and sees if there is a matching value in the SampleIDReplace column of the parent table. If there is, it goes and gets the SampleID of that record. It only stops when the join results in every SampleIDReplace being -1, meaning we have nothing else to do.
On your sample data, the above results in the expected output.
Note that I had to use temp tables rather than table variables here in order for the table to be accessible within the loop. If you have to use table variables then there would need to be a bit more surgery done.
Clearly if you have deep replacement hierarchies then you'll do quite a few updates, which may be a consideration when looking to perform the query against a production database.