Related
I have a table with the following sample data:
Name Value
-----------------------
Year 1990
Year 1991
Year 1992
Cash 2000
Cash 4000
Cash 6000
Tax Paid
Tax Unpaid
Tax Pending
Liability 3500
Liability 8500
Liability 9500
I want that data in the following format:
Name Value1 Value2 Value3
-----------------------------------------------------
Year 1990 1991 1992
Cash 2000 4000 6000
Tax Paid Unpaid Pending
Liability 3500 8500 9500
Please note, the number of columns in result table should be dynamic i.e., number of value columns = number of records for an item in original table.
In the example, each item has 3 records so there will be 3 value columns in result table.
Please guide.
Update:
I used below query and it returned data in correct format, but still clueless how to identify number of columns in resultant table. In below example fixed 5 columns are there (name, value1, value2, value3, value4).
with cte as
(
select name ci,value,
row_number() over(partition by [name] order by value) as rn
from TABLE
)
select distinct ci as [name],
(select ct.value from cte ct where ct.ci=cte.ci and ct.rn=1) value1,
(select ct.value from cte ct where ct.ci=cte.ci and ct.rn=2) value2,
(select ct.value from cte ct where ct.ci=cte.ci and ct.rn=3) value3,
(select ct.value from cte ct where ct.ci=cte.ci and ct.rn=4) value4
from cte
create table #t
(
Name varchar(50),
Value varchar(50)
)
insert into #t(Name, Value)
values
('Year', '1990'),
('Year', '1991'),
('Year', '1992'),
('Cash', '2000'),
('Cash', '4000'),
('Cash', '6000'),
('Tax', ' Paid'),
('Tax', ' Unpaid'),
('Tax', ' Pending'),
('Liability', '3500'),
('Liability', '8500'),
('Liability', '9500'),
('Liability', '1500'),
('Liability', '2500'),
('Liability', '4500');
declare #sql nvarchar(max) = stuff(
(
select ',[value'+cast(rownum as varchar(20))+']'
from
(
select top((select max(cnt) from (select count(*) as cnt from #t group by Name) as t)) row_number() over(order by a.v) as rownum
from
(
values(cast(null as bit)),(null),(null),(null),(null),(null),(null),(null),(null),(null)
) as a(v)
cross join
(
values(cast(null as bit)),(null),(null),(null),(null),(null),(null),(null),(null),(null)
) as b(v)
cross join
(
values(cast(null as bit)),(null),(null),(null),(null),(null),(null),(null),(null),(null)
) as c(v)
) as nums
order by rownum
for xml path('')), 1, 1, N'');
select #sql = N'
select *
from
(
select *, ''value''+cast(row_number() over(partition by Name order by (select null)) as varchar(20)) as colname
from #t
) as t
pivot
(
max(value) for colname in (' + #sql + N')
) as unpv
';
exec(#sql);
--
drop table #t;
I have a situation where I have to update a incremental value to a column based on the value on the same column from the previous row for the same group of records.
Rule for updating 'COUNT' column is:
For the very 1st row of a particular REFNO,
If Amount 1 = Amount 2 then
COUNT = 1
Else
COUNT = 0
For all other rows (excluding the 1st row) of a particular REFNO:
If Amount 1 = Amount 2 then
COUNT = COUNT from previous row for the same REFNO + 1
Else
COUNT = COUNT from previous row for the same REFNO
So the result should look like below:
Though the sample data which I have shown has only 14 records the actual table I am updating is going to have few million rows in them. So I am searching for a solution which will do a set based update rather than row by row processing !!
You can update from a CTE that uses window functions to calculate the number.
The SQL below first calculates a row_number for the equal amounts.
Then for the others that don't have an equal amount, the maximum of the previous row_number is taken.
WITH CTE AS
(
SELECT *,
(CASE
WHEN [Amount 1] = [Amount 2]
THEN rn
ELSE MAX(rn) OVER (PARTITION BY [REFNO] ORDER BY [ROW ID] ASC ROWS UNBOUNDED PRECEDING)
END) as rnk
FROM (
SELECT
[ROW ID], [REFNO], [Amount 1], [Amount 2], [COUNT],
(CASE
WHEN [Amount 1] = [Amount 2]
THEN ROW_NUMBER() OVER (PARTITION BY [REFNO], IIF([Amount 1] = [Amount 2],0,1) ORDER BY [ROW ID] ASC)
ELSE 0
END) AS rn
FROM PAYMENT
) q
)
UPDATE CTE
SET [COUNT] = rnk;
A test on db<>fiddle here
Try it's
declare #t table (
rowid int identity,
refno int,
amount1 int,
amount2 int
)
insert into #t(refno,amount1,amount2) values (1000000,100,200)
insert into #t(refno,amount1,amount2) values (1000000,250,250)
insert into #t(refno,amount1,amount2) values (1000000,300,300)
insert into #t(refno,amount1,amount2) values (1000000,400,400)
insert into #t(refno,amount1,amount2) values (1000010,400,100)
insert into #t(refno,amount1,amount2) values (1000010,200,100)
insert into #t(refno,amount1,amount2) values (1000010,100,300)
insert into #t(refno,amount1,amount2) values (1000021,400,400)
insert into #t(refno,amount1,amount2) values (1000021,200,100)
insert into #t(refno,amount1,amount2) values (1000032,200,200)
insert into #t(refno,amount1,amount2) values (1000032,300,300)
insert into #t(refno,amount1,amount2) values (1000033,200,100)
insert into #t(refno,amount1,amount2) values (1000033,200,100)
select rowid,refno,amount1,amount2,rw-1 as count
from (
select
row_number() over(partition by amount1,amount2 order by rowid) rw,*
from #t) as src
This code works for that particular set, but no guarantee that there will be a situation when it won't work:
CREATE TABLE #tmp(
RowID INT IDENTITY(1,1),
RefNo INT,
Amount1 INT,
Amount2 INT
)
INSERT INTO #tmp(RefNo,Amount1,Amount2)
SELECT * FROM (VALUES
(100000,100,200),
(100000,250,250),
(100000,300,300),
(100000,400,400),
(100000,400,100),
(100010,200,100),
(100010,100,300),
(100010,400,400),
(100021,200,100),
(100021,200,200),
(100032,300,300),
(100032,200,100),
(100033,200,100),
(100033,200,100)) AS x(a,b,c)
;WITH Try1 AS (SELECT t1.*, [Count] =
CASE WHEN t1.Amount1 != t1.Amount2 AND
(t2.RowId IS NULL OR t2.Amount1 != t2.Amount2) THEN 0
WHEN t1.Amount1 != t1.Amount2 AND t2.Amount1 = t2.Amount2 THEN t2.RowId
WHEN t1.Amount1 = t1.Amount2 AND t2.RowId IS NULL THEN t1.RowId
WHEN t1.Amount1 = t1.Amount2 AND t2.RowId IS NOT NULL THEN t1.RowId
END
, NextRefNo = CASE WHEN t2.RowId IS NULL THEN 1 ELSE 0 END
FROM #tmp AS t1
OUTER APPLY ( SELECT * FROM #tmp AS t2
WHERE t2.RowId = t1.RowID - 1 AND t2.RefNo = t1.RefNo) AS t2)
, Try2 AS (SELECT RowID, RefNo, Amount1, Amount2, [Count]
, NextRefNo = ISNULL(t2.NextRefNo,0)
FROM Try1 AS t1
OUTER APPLY ( SELECT NextRefNo FROM Try1 AS t2
WHERE t2.[Count] > 0 AND t2.NextRefNo = 1
AND t2.RefNo = t1.RefNo ) AS t2)
SELECT RowID, RefNo, Amount1, Amount2
, [Count] = DENSE_RANK() OVER(PARTITION BY RefNo ORDER BY [Count]) - 1 + NextRefNo
FROM Try2
ORDER BY RowID;
I have this:
SELECT NEWID() as id,
'OwnerReassign' as name,
1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId,
GETDATE() as CreatedAt,
'{"EntityName":"account","Ids":["'+CAST(AccountId as varchar(50))+'"],"OwnerId":"0C01C994-1205-E511-988E-26EE4189191B"}' as [Parameters]
FROM Account
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
Within the parameter field is an id (0C01C994-1205-E511-988E-26EE4189191B). Is it possible it could sequentially assign a different id from a list for each row? There are 5 id's in total.
What i'm trying to get to is this result set equally split between the 5 different id's.
Thanks
You can add one more NEWID() in the sub query and handle in the SELECT as below:
SELECT id, [name], TypeId, OrgName, SystemUserId, StatusId, CreatedAt,
'{"EntityName":"account","Ids":["' + AccountId +'"],"OwnerId":"' + ParamId + '"}' as [Parameters]
FROM (
SELECT NEWID() as id,
'OwnerReassign' as name,
1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId,
GETDATE() as CreatedAt,
CAST(NEWID() AS VARCHAR (36)) as ParamId,
CAST(AccountId as varchar(50)) as AccountId
FROM Account
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
) A
You can use something like the following. Basically, use a row number for both your IDs and your data table to update, then do a MOD (%) operation with the amount of ID's you want to assign, so your data table to update is split into N groups. Then use that group ID to assign each ID.
IF OBJECT_ID('tempdb..#IDsToAssign') IS NOT NULL
DROP TABLE #IDsToAssign
CREATE TABLE #IDsToAssign (
IDToAssign VARCHAR(100))
-- 3 IDs example
INSERT INTO #IDsToAssign (
IDToAssign)
SELECT IDToAssign = NEWID()
UNION ALL
SELECT IDToAssign = NEWID()
UNION ALL
SELECT IDToAssign = NEWID()
DECLARE #AmountIDsToAssign INT = (SELECT COUNT(1) FROM #IDsToAssign)
IF OBJECT_ID('tempdb..#Account') IS NOT NULL
DROP TABLE #Account
CREATE TABLE #Account (
PrimaryKey INT PRIMARY KEY,
AssignedID VARCHAR(100))
-- 10 Rows example
INSERT INTO #Account (
PrimaryKey)
VALUES
(100),
(200),
(351),
(154),
(194),
(345),
(788),
(127),
(124),
(14)
;WITH DataRowNumber AS
(
SELECT
A.*,
RowNumber = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#Account AS A
),
IDsRowNumbers AS
(
SELECT
D.IDToAssign,
RowNumber = ROW_NUMBER() OVER (ORDER BY D.IDToAssign)
FROM
#IDsToAssign AS D
),
NewIDAssignation AS
(
SELECT
R.*,
IDRowNumberAssignation = (R.RowNumber % #AmountIDsToAssign) + 1
FROM
DataRowNumber AS R
)
UPDATE A SET
AssignedID = R.IDToAssign
FROM
NewIDAssignation AS N
INNER JOIN IDsRowNumbers AS R ON N.IDRowNumberAssignation = R.RowNumber
INNER JOIN #Account AS A ON N.PrimaryKey = A.PrimaryKey
SELECT
*
FROM
#Account AS A
ORDER BY
A.AssignedID
/* Results:
PrimaryKey AssignedID
----------- ------------------------------------
124 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
194 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
351 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
788 2A58A573-EDCB-428E-A87A-6BFCED265A9C
200 2A58A573-EDCB-428E-A87A-6BFCED265A9C
127 2A58A573-EDCB-428E-A87A-6BFCED265A9C
14 2A58A573-EDCB-428E-A87A-6BFCED265A9C
100 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
154 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
345 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
*/
The ordering of the ROW_NUMBER() function will determine how ID's are assigned.
You could potentially do this by using the ROW_NUMBER() field in a subquery; for example:
SELECT NEWID() as id, 'OwnerReassign' as name, 1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId, GETDATE() as CreatedAt,
case B / ##ROWCOUNT
when 0 then '0C01C994-1205-E511-988E-26EE4189191B'
when 1 then '12345677-1205-E511-988E-26EE4189191B'
when 2 then '66666666-1205-E511-988E-26EE4189191B'
etc...
end
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY A.Id)
FROM Account A
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
) AS B
If you want the system to pick those values then you could put then in their own temporary table, too.
I need a help on writing an optimal query for the below problem. Have attached the query I have with me but it is highly utilizing resources.
Below is the code to achieve above said logic. Please suggest some optimal way to achieve the same
-- drop table #me
create table #ME (memid int , EffectiveDate datetime , termdate datetime)
Insert into #ME values ('123','3-Dec-16','10-Jan-17')
Insert into #ME values ('123','11-Jan-17','6-Feb-17')
Insert into #ME values ('123','7-Feb-17','5-Mar-17')
Insert into #ME values ('123','8-Mar-17','15-Apr-17')
Insert into #ME values ('123','16-Apr-17','24-May-17')
--drop table #dim
select * from #ME
declare #StartDate datetime , #CutoffDate datetime
select #StartDate= min(effectivedate),#CutoffDate = max(termdate) From #me where termdate<>'9999-12-31 00:00:00.000'
SELECT d
into #dim
FROM
(
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
-- on my system this would support > 5 million days
ORDER BY s1.[object_id]
) AS x
) AS y;
--drop table #MemEligibilityDateSpread
select MemID, D As DateSpread Into #MemEligibilityDateSpread From #Dim dim JOIN #me ME on dim.d between ME.effectivedate and me.termdate
--drop table #DateClasified
WITH CTE AS
(
SELECT MEmID,
UniqueDate = DateSpread,
DateGroup = DATEADD(dd, - ROW_NUMBER() OVER (PARTITION BY Memid ORDER BY Memid,DateSpread), DateSpread)
FROM #MemEligibilityDateSpread
GROUP BY Memid,DateSpread
)
--===== Now, if we find the MIN and MAX date for each DateGroup, we'll have the
-- Start and End dates of each group of contiguous daes. While we're at it,
-- we can also figure out how many days are in each range of days.
SELECT Memid,
StartDate = MIN(UniqueDate),
EndDate = MAX(UniqueDate)
INTO #DateClasified
FROM cte
GROUP BY Memid,DateGroup
ORDER BY Memid,StartDate
select ME.MemID,ME.EffectiveDate,ME.TermDate,DC.StartDate,DC.EndDate from #DateClasified dc join #me ME ON Me.MemID = dc.MemID
and (ME.EffectiveDate BETWEEN DC.StartDate AND DC.EndDate
OR ME.TermDate BETWEEN DC.StartDate AND DC.EndDate)
In cte0 and cte1, we create an ad-hoc tally/calendar table. Once we have that, it is a small matter to calculate and group by Island.
Currently, the tally is has a max of 10,000 days (27 years), but you can easily expand the tally table by adding , cte0 N5
;with cte0(N) as (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N))
,cte1(R,D) as (Select Row_Number() over (Order By (Select Null))
,DateAdd(DD,-1+Row_Number() over (Order By (Select Null)),(Select MinDate=min(EffectiveDate) From #ME))
From cte0 N1, cte0 N2, cte0 N3, cte0 N4)
Select MemID
,EffectiveDate
,TermDate
,SinceFrom = Min(EffectiveDate) over (Partition By Island)
,Tildate = Max(TermDate) over (Partition By Island)
From (
Select *,Island = R - Row_Number() over (Partition By MemID Order by TermDate)
From #ME A
Join cte1 B on D Between EffectiveDate and TermDate
) A
Group By MemID,Island,EffectiveDate,TermDate
Order By 1,2
Returns
MemID EffectiveDate TermDate SinceFrom Tildate
123 2016-12-03 2017-01-10 2016-12-03 2017-03-05
123 2017-01-11 2017-02-06 2016-12-03 2017-03-05
123 2017-02-07 2017-03-05 2016-12-03 2017-03-05
123 2017-03-08 2017-04-15 2017-03-08 2017-05-24
123 2017-04-16 2017-05-24 2017-03-08 2017-05-24
Edit - Now if you want a compressed dataset
Select MemID
,EffectiveDate = Min(EffectiveDate)
,TermDate = Max(TermDate)
From (
Select *,Island = R - Row_Number() over (Partition By MemID Order by TermDate)
From #ME A
Join cte1 B on D Between EffectiveDate and TermDate
) A
Group By MemID,Island
Order By 1,2
Returns
MemID EffectiveDate TermDate
123 2016-12-03 2017-03-05
123 2017-03-08 2017-05-24
i have a Stored Procedure called "Patient Ledger Report" where i need to show the day to day transaction details and balance amount of the patients.i was providing you one sampled data from below code how the data was inserting into my temporary table in my sp.
create table #Patient_ledger (PATIENT_NAME varchar(250),PATIENT_NBR bigint,BILLNO varchar(250),BILLAMOUNT bigint,
PAID_AMOUNT bigint)
Insert into #Patient_ledger (Patient_name ,Patient_nbr ,billno ,billamount ,
paid_amount )
select 'ABC',1,'DUE_BILL_ABC_1',100,50
union all
select 'ABC',1,'DUE_BILL_ABC_2',160,90
UNION ALL
select 'ABC',1,'DEPOSIT_BILL_ABC',0,60
UNION ALL
select 'XYZ',2,'DEPOSIT_BILL_XYZ',0,70
UNION ALL
select 'XYZ',2,'DUE_BILL_XYZ_1',100,30
SELECT * FROM #Patient_ledger
Drop table #Patient_ledger
How i want to show the data in my report.
PATIENT_NUMBER BILLNO BILLAMOUNT PAID_AMOUNT BALANCE
1 DUE_BILL_ABC_1 100 50 50 --(100-50)
1 DUE_BILL_ABC_2 160 90 120 --(160-90 +50(Here 50 is prev balance amount of same patient))
1 DEPOSIT_BILL_ABC 0 40 80 ---( 120-40=80)
2 DEPOSIT_BILL_XYZ 0 70 0
2 DUE_BILL_XYZ_1 100 30 0 --Here Balance is zero because patient has deposited some
--amount before bill (70-100+30=0)
Note: Balance amount should deduct when deposits are paid by that particual patient.
I have tried like below it may help you
SELECT Patient_nbr,
billno,
billamount,
PAID_AMOUNT,
CASE
WHEN RNO > 1 THEN Sum(billamount - PAID_AMOUNT)
OVER(
PARTITION BY Patient_nbr
ORDER BY RNO)
ELSE Iif(( billamount - PAID_AMOUNT ) < 0, 0, billamount - PAID_AMOUNT)
END
FROM (SELECT *,
Row_number()
OVER(
PARTITION BY Patient_nbr
ORDER BY Patient_nbr) AS RNO
FROM #Patient_ledger) A
If you are able to put there also order discriminator, it could seems like this: (I consider also fact that there can be more DUE/DEPOSITS for one PATIENT_NBR)
IF OBJECT_ID('tempdb..#Patient_ledger') IS NOT NULL DROP TABLE #Patient_ledger
CREATE TABLE #Patient_ledger
(ID INT IDENTITY,
PATIENT_NAME varchar(250),
PATIENT_NBR bigint,
BILLNO varchar(250),
BILLAMOUNT bigint,
PAID_AMOUNT bigint)
Insert into #Patient_ledger (PATIENT_NAME ,PATIENT_NBR ,BILLNO ,BILLAMOUNT ,
PAID_AMOUNT )
select 'ABC',1,'DUE_BILL_ABC_1',100,50
union all
select 'ABC',1,'DUE_BILL_ABC_2',160,90
UNION ALL
select 'ABC',1,'DEPOSIT_BILL_ABC',0,40
UNION ALL
select 'XYZ',2,'DEPOSIT_BILL_XYZ',0,70
UNION ALL
select 'XYZ',2,'DUE_BILL_XYZ_1',100,30
;WITH CTE AS (
SELECT PATIENT_NBR,
BILLNO,
PAID_AMOUNT,
BILLAMOUNT,
BILLAMOUNT-PAID_AMOUNT AS BALANCE,
ROW_NUMBER() OVER (PARTITION BY PATIENT_NBR ORDER BY ID) AS RN
FROM #Patient_ledger)
SELECT a.PATIENT_NBR,
a.BILLNO,
a.BILLAMOUNT,
a.PAID_AMOUNT,
CASE WHEN ISNULL(LAG(a.BALANCE + ISNULL(x.ADDS,0)) OVER (PARTITION BY a.PATIENT_NBR ORDER BY a.RN),0) + a.BILLAMOUNT - a.PAID_AMOUNT < 0
THEN 0
ELSE a.BALANCE + ISNULL(x.ADDS,0)
END AS FINAL_BALANCE
FROM CTE a
CROSS APPLY (SELECT SUM(BALANCE) AS ADDS
FROM CTE f
WHERE f.PATIENT_NBR = a.PATIENT_NBR AND f.RN < a.RN) x
Try this and tell me if it work with other sample data too.
create table #Patient_ledger (PATIENT_NAME varchar(250),PATIENT_NBR bigint
,BILLNO varchar(250),BILLAMOUNT bigint,PAID_AMOUNT bigint)
Insert into #Patient_ledger (Patient_name ,Patient_nbr ,billno
,billamount ,paid_amount )
select 'ABC',1,'DUE_BILL_ABC_1',100,50
union all
select 'ABC',1,'DUE_BILL_ABC_2',160,90
UNION ALL
select 'ABC',1,'DEPOSIT_BILL_ABC',0,40
UNION ALL
select 'XYZ',2,'DEPOSIT_BILL_XYZ',0,70
UNION ALL
select 'XYZ',2,'DUE_BILL_XYZ_1',100,30
SELECT PATIENT_NBR PATIENT_NUMBER
,BILLNO
,BILLAMOUNT
,PAID_AMOUNT
,CASE
WHEN billamount = 0
AND lag((BILLAMOUNT - PAID_AMOUNT), 1, 0) OVER (
PARTITION BY PATIENT_NBR ORDER BY PATIENT_NBR
) = 0
THEN 0
ELSE SUM((BILLAMOUNT - PAID_AMOUNT)) OVER (
PARTITION BY PATIENT_NBR ORDER BY PATIENT_NBR ROWS UNBOUNDED PRECEDING
)
END Balance
FROM #Patient_ledger
Drop table #Patient_ledger