expecting output with out using left join - sql

first table is my input and expecting output like second table with out using left join.
this is the table data
declare #table table
(customer_id int,
indicator bit,
salary numeric(22,6)
,netresult numeric(22,6))
INSERT INTO #table (
customer_id
,indicator
,salary
)
VALUES
(1,1,2000),
(1,1,3000),
(2,1,1000),
(1,0,500),
(1,1,5000),
(2,1,2000),
(2,0,100)
select * from #table order by customer_id,indicator desc
I tried in below method it works. Is there any better alternative?
SELECT a.customer_id
,a.indicator
,a.salary
,netresult=p_salary-(2*n_salary)
FROM (
SELECT customer_id
,indicator
,salary
,sum(salary) OVER (PARTITION BY customer_id) p_salary
FROM #table
) a
LEFT JOIN (
SELECT customer_id
,indicator
,salary
,sum(salary) OVER (PARTITION BY customer_id) n_salary
FROM #table
WHERE indicator = 0
) b ON a.customer_id = b.customer_id
order by customer_id,indicator desc
Expected Output

I think you want this:
select t.customer_id, t.indicator,
sum(case when indicator = 1 then salary else - salary end) over (partition by customer_id) as netresult
form #table t;
No joins are necessary.

with math
select t.customer_id, t.indicator, t.salary
, sum((( t.indicator * 2) -1) * salary) over (partition by customer_id) as netresult
from #table t;

Related

Group by with gap in date sequence ("gaps and islands")

I am trying to solve a "gaps and islands" by date issue I'm facing (kudos to Gordon Linoff helping me identify this issue). I want to group the below table by person, office and job while respecting order by person,from_date. consider the table below:
declare #temp table(person varchar(25),office varchar(25),job varchar(25),from_date date,to_date date)
insert into #temp values ('jon','ny','programmer','1/1/2020','1/3/2020');
insert into #temp values ('jon','ny','programmer','1/4/2020','1/5/2020');
insert into #temp values ('jon','dc','programmer','1/6/2020','1/7/2020');
insert into #temp values ('jon','ny','programmer','1/8/2020','1/9/2020');
insert into #temp values ('lou','ny','programmer','1/1/2020','1/3/2020');
insert into #temp values ('lou','ny','programmer','1/4/2020','1/5/2020');
insert into #temp values ('lou','dc','programmer','1/6/2020','1/7/2020');
insert into #temp values ('lou','ny','programmer','1/8/2020','1/9/2020');
the intended output is
This is a type of gaps-and-islands problem. If there are no gaps in the dates, the simplest solution is the difference of row numbers:
select person, office, job, min(from_date), max(to_date)
from (select t.*,
row_number() over (partition by person, office, job order by from_date) as seqnum,
row_number() over (partition by person, office order by from_date) as seqnum_2
from t
) t
group by person, office, job, (seqnum - seqnum_2)
This is a general solution:
WITH preceders_and_followers AS (
SELECT
b.person,
b.office,
b.job,
b.from_date,
b.to_date,
CASE
WHEN EXISTS (
SELECT
c.*
FROM
ora$ptt_tmp c
WHERE
b.person = c.person
AND b.office = c.office
AND b.job = c.job
AND ( b.from_date - 1 BETWEEN c.from_date AND c.to_date )
) THEN
1
END AS has_preceder,
CASE
WHEN EXISTS (
SELECT
c.*
FROM
ora$ptt_tmp c
WHERE
b.person = c.person
AND b.office = c.office
AND b.job = c.job
AND ( b.to_date + 1 BETWEEN c.from_date AND c.to_date )
) THEN
1
END AS has_follower
FROM
ora$ptt_tmp b
ORDER BY
1,
2,
3
)
SELECT DISTINCT
pf1.person,
pf1.office,
pf1.job,
pf1.from_date,
(
SELECT
MIN(pf2.to_date)
FROM
preceders_and_followers pf2
WHERE
pf1.person = pf2.person
AND pf1.office = pf2.office
AND pf1.job = pf2.job
AND pf2.to_date >= pf1.from_date
AND has_follower IS NULL
) to_date
FROM
preceders_and_followers pf1
WHERE
pf1.has_preceder IS NULL
ORDER BY
1,
4,
2,
3;

How to combine start and end dates without breaks in time?

I have tried many ways, but unsuccessfully, to combine Start dates and end dates where the record Id is the same and combine the where there is no break in the Date
CREATE TABLE #t (
A_ID VARCHAR(100),
BDate VARCHAR(100),
CDate VARCHAR(100)
)
INSERT INTO #T
(A_ID, BDate, CDate)
VALUES
('1000','2017/12/01','2017/12/31'),
('1000','2018/01/01','2018/03/31'),
('1000','2018/05/01','2018/05/31')
Select A_ID, bDate,cDate from
(
select BDate,A_ID,Cdate,lead(Bdate) over (order by Bdate) next_BDate from #T as t2
where exists ( select null from #T as t1
where t1.A_ID = t2.A_ID and t1.Bdate <= t2.Bdate and t1.CDate <=t2.CDate )
) as combine
where bDate < Cdate
order by BDate;
I would like to see:
1000 2017/12/01 2018/03/31 (no break in first two dates)
1000 2018/05/01 2018/05/31 (Break between 4-1-18 and 5-1-18)
This is a gaps & islands problem, depending on your actual data a solution based on bĀ“nested OLAP-functions might be more efficient that recursion:
with combine as
(
select BDate,A_ID,Cdate,
-- find the gap and flag it
case when lag(Cdate)
over (partition by A_ID
order by CDate) = dateadd(day,-1, BDate)
then 0
else 1
end as flag
from T
)
, groups as
(
Select A_ID, bDate,cDate,
-- cumulative sum over 0/1 to assign the same group number for row without gaps
sum(flag)
over (partition by A_ID
order by Bdate) as grp
from combine
)
-- group consecutive rows into one
select A_ID, min(BDate), max(CDate)
from groups
group by A_ID, grp
order by min(BDate);
How does this work for you?
declare #table table (a_id int, bdate date, cdate date, id int)
insert #table
select a_id, bdate, cdate,
case when lag(cdate, 1,cdate) over(partition by a_id order by bdate) in (cdate, dateadd(day, -1, bdate))
then 1 else 2 end id from #t
select a.a_id, min(a.bdate)bdate, max(a.cdate)cdate from #table a
left join
#table b
on a.id=b.id and a.a_id=b.a_id and b.id=1
group by a.a_id, a.id

SQL aggregation of data based on dates

I am sure this is a very stupid question and I am having a dumb moment.
Consider the following basic scenario (this is a very small scenario compared with reality which has many many dimensions and measures):
What I need to get to is the expected output.
So ALL costs between the input_Date and output_date defined in the params are included. However only the latest PID is included- defined as either:
1- where PIDs run sequentially, or overlap the latest one based on date_to as long as both aren't active at the # output date
2- where there are two PID active at the # output date show both
I can't for the life of me work out how to do this in SQL, note that is has to be non dynamic and not use any CTE unfortunately, just your basic SQL with subqueries
Obviously returning the necessary list of ID and PID is easy:
declare #input_date date ='2006-01-01'
declare #output_date date ='2006-12-31'
select a.PID, a.ID
from #tmp a
where date_from <=#output_date and date_to >=#input_date
But I can't figure out how to join this back to return the correct cost values
drop table tmp
CREATE TABLE [dbo].[tmp](
[date_from] [datetime] NOT NULL,
[date_to] [datetime] NOT NULL,
[ID] [nvarchar](25) NOT NULL,
[PID] [nvarchar](25) NOT NULL,
[cost] [float] NULL
) ON [PRIMARY]
INSERT tmp VALUES('2005-1-1','2005-1-31','10001','X123',1254.32)
INSERT tmp VALUES('2000-10-10','2006-8-21','10005','TEST01',21350.9636378758)
INSERT tmp VALUES('2006-8-22','2099-12-31','10005','TEST02',22593.4926163943)
INSERT tmp VALUES('2006-1-1','2099-12-31','10006','X01',22458.3342354444)
INSERT tmp VALUES('2006-2-8','2099-12-31','10006','X02',22480.3772331959)
INSERT tmp VALUES('2006-1-1','2006-2-7','10007','AB01',565.416874152212)
INSERT tmp VALUES('2006-2-8','2006-7-31','10007','AA05',19108.3206482165)
I've made some progress using a CTE so you can see how I would do it this way if I could:
drop table #tmp
CREATE TABLE #tmp (
[date_from] [datetime] NOT NULL,
[date_to] [datetime] NOT NULL,
[ID] [nvarchar](25) NOT NULL,
[PID] [nvarchar](25) NOT NULL,
[cost] [float] NULL
) ON [PRIMARY]
INSERT #tmp VALUES('2005-1-1','2005-1-31','10001','X123',1254.32)
INSERT #tmp VALUES('2000-10-10','2006-8-21','10005','TEST01',21350.9636378758)
INSERT #tmp VALUES('2006-8-22','2099-12-31','10005','TEST02',22593.4926163943)
INSERT #tmp VALUES('2006-1-1','2099-12-31','10006','X01',22458.3342354444)
INSERT #tmp VALUES('2006-2-8','2099-12-31','10006','X02',22480.3772331959)
INSERT #tmp VALUES('2006-1-1','2006-2-7','10007','AB01',565.416874152212)
INSERT #tmp VALUES('2006-2-8','2006-7-31','10007','AA05',19108.3206482165)
declare #input_date date ='2006-01-01'
declare #output_date date ='2006-12-31'
;with cte as (
select t.id,t.PID,t.cost,t.date_from,t.date_to ,
iif(date_To >= #output_date OR max_date_To is not null,PID,NULL) as PID2,
b.total_id_cost
from #tmp t
left join (select ID,max(date_to) as max_date_to
from #tmp
where date_from <=#output_date and date_to >=#input_date
group by ID) a
on t.ID = a.ID and t.date_to = a.max_date_to
left join (Select ID, sum(cost) as total_id_cost
from #tmp
where date_from <=#output_date and date_to >=#input_date
group by ID) b
on t.ID = b.ID
where date_from <=#output_date and date_to >=#input_date )
select distinct ID,PID2,
iif(ID in (
select ID
from cte
where PID2 IS NULL)
and ID not in (select ID
from cte
where PID IS NOT NULL
group by ID
having count (distinct PID2) >1 ), cte.total_id_cost, cost) as cost
from cte
where PID2 is not null;
so it looks like there's several problems to solve within 1 query.
We want the PID that matches the latest date. This wasn't too difficult and can be solved by joining the data with an aggregate of itself that finds the latest date
Where both PID is active i.e. overlapping from and to dates, then both must show. I found this to be more tricky. in the end I did a query to find the ones that do overlap and meet the dates, and did a count on that. then used this count as a criteria for the join on 1. so that it can conditionally pick the PID that matches the latest date
Then finally using the results from above, you can do the sum to get the cost. The resulting query is a bit of a monster, but here it is.
if it doesn't cover other scenarios not detailed, do let me know.
DECLARE #Data TABLE (date_from DATETIME, date_to DATETIME, ID INT, PID NVARCHAR(50), COST MONEY)
INSERT #Data VALUES('2005-1-1','2005-1-31','10001','X123',1254.32)
INSERT #Data VALUES('2000-10-10','2006-8-21','10005','TEST01',21350.9636378758)
INSERT #Data VALUES('2006-8-22','2099-12-31','10005','TEST02',22593.4926163943)
INSERT #Data VALUES('2006-1-1','2099-12-31','10006','X01',22458.3342354444)
INSERT #Data VALUES('2006-2-8','2099-12-31','10006','X02',22480.3772331959)
INSERT #Data VALUES('2006-1-1','2006-2-7','10007','AB01',565.416874152212)
INSERT #Data VALUES('2006-2-8','2006-7-31','10007','AA05',19108.3206482165)
declare #input_date date ='2006-01-01'
declare #output_date date ='2006-12-31'
select
a.ID,
PIDForMaxDateThatMatches.PID,
SUM(a.cost) as cost
from
#Data a
inner join (
-- number of PIDs for dates that overlap grouped by ID
select
a.ID,
-- where there's no overlap then we want the count to be 1 so that later we can use it as condition
COUNT(DISTINCT ISNULL(b.PID,'')) as NumberOfPID
from
#Data a
-- may or may not find overlaps
LEFT JOIN #data b ON
b.date_from <=#output_date and
b.date_to >=#input_date and
a.date_from <= b.date_to and
a.date_to >= b.date_from and
a.ID = b.ID and
a.PID <> b.PID
where
a.date_from <=#output_date and
a.date_to >=#input_date
group by
a.ID) as PIDCountForOverlappingMatches ON
a.ID = PIDCountForOverlappingMatches.ID
left join (
-- get the PID that matches the max date_to
select
DataForMaxDate.ID,
DataForMaxDate.date_from,
DataForMaxDate.date_to,
DataForMaxDate.PID
from
#Data as DataForMaxDate
inner join (
-- get the max date_to that matches the criteria
select
ID,
MAX(date_to) as maxDateTo
from
#Data a
where
date_from <=#output_date and
date_to >=#input_date
group by
ID) as MaxToDatePerID on
DataForMaxDate.ID = MaxToDatePerID.ID and
DataForMaxDate.date_to = MaxToDatePerID.maxDateTo) as PIDForMaxDateThatMatches on
a.ID = PIDForMaxDateThatMatches.ID AND
-- if there's no overlapping dates the PID count would be 1, which we'll take the PID that matches the max(date_to)
-- but if there is overlap, then we want both dates to show, thus the from date must also match before we take the PID
(PIDCountForOverlappingMatches.NumberOfPID = 1 OR a.date_from = PIDForMaxDateThatMatches.date_from)
where
a.date_from <= #output_date and
a.date_to >= #input_date
GROUP BY
a.ID,
PIDForMaxDateThatMatches.PID
ORDER BY
a.ID
EDIT: DB Fiddle http://dbfiddle.uk/?rdbms=sqlserver_2014&fiddle=d43cb4b9765da1bca035531e78a2c77d
Results:
ID PID cost
10005 TEST02 43944.4562
10006 X01 22458.3342
10006 X02 22480.3772
10007 AA05 19673.7375
Hello you can try the following query :
select a.resource_id ID, max(a.post_id) PID, SUM(a.cost) Cost
from #tmp a
where date_from <=#output_date and date_to >=#input_date
group by a.resource_id
order by a.resource_id;
I think this might work:
SELECT
t1.ID,
q1.PID,
SUM(t1.cost)
FROM
Table AS t1
JOIN
(
SELECT
q2.ID,
t2.PID
FROM
(
SELECT
ID,
MAX(date_to) AS maxdate
FROM
Table
GROUP BY
ID
) AS q2
JOIN
table AS t2
ON
q2.ID = t2.ID
AND
q2.maxdate = t2.date_to
) AS q1
ON
t1.ID = q1.ID
AND
t1.PID = q1.PID
GROUP BY
t1.ID,
q1.PID
Here is a query without CTE. Idea of query:
1) Find consecutive dates and make different groups within each id
2) Find min and max date, sum of costs for each group
3) Limit by input parametres
declare #date_from date = '20060101'
declare #date_to date = '20061231'
declare #myTable table(
date_from date
, date_to date
, id int
, pid varchar(30)
, cost decimal(10,2)
)
insert into #myTable values
('20050101', '20050201', 10001, 'x123', 1254.32)
, ('20001010', '20060821', 10005, 'test01', 21350.96)
, ('20060822', '20991231', 10005, 'test02', 22593.49)
, ('20060101', '20991231', 10006, 'x01', 22548.33)
, ('20060208', '20991231', 10006, 'x02', 22480.38)
, ('20060101', '20060207', 10007, 'abo1', 565.42)
, ('20060208', '20060731', 10007, 'abo2', 19108.32)
select
date_from = min(date_from), date_to = max(date_to)
, id, pid = max(case when date_to = max_date_to then pid end)
, cost = sum(cost)
from (
select
a.date_from, a.date_to, a.id, a.pid, a.cost, a.rn, grp = sum(b.ss)
, max_date_to = max(a.date_to) over (partition by a.id, sum(b.ss))
from
(
select
a.*, ss = case when datediff(dd, b.date_to, a.date_from) = 1 then 0 else 1 end
from
(
select
*, rn = row_number() over (partition by id order by date_from)
from
#myTable
) a
left join (
select
*, rn = row_number() over (partition by id order by date_from)
from
#myTable
) b on a.id = b.id and a.rn - 1 = b.rn
) a
left join (
select
a.*, ss = case when datediff(dd, b.date_to, a.date_from) = 1 then 0 else 1 end
from
(
select
*, rn = row_number() over (partition by id order by date_from)
from
#myTable
) a
left join (
select
*, rn = row_number() over (partition by id order by date_from)
from
#myTable
) b on a.id = b.id and a.rn - 1 = b.rn
) b on a.id = b.id and a.rn >= b.rn
group by a.date_from, a.date_to, a.id, a.pid, a.cost, a.rn
) t
group by id, grp, max_date_to
having min(date_from) <= #date_from and max(date_to) >= #date_to
order by id
Output
date_from date_to id pid cost
------------------------------------------------
2000-10-10 2099-12-31 10005 test02 43944.45
2006-01-01 2099-12-31 10006 x01 22548.33
Result is a bit different than your provided output. But:
1) For id = 10006 and pid = X02 date_from = 08/02/2006 while input is 01/01/2006
2) For id = 10007 date_to = 31/07/2006 while input is 31/12/2006
So, I think query works correctly
Rextester demo in more readable format with cte

SQL - Return previous record details as column by date

I am trying to get a list of records showing changes in location and dates to display as one row for each record showing previous location.
Basically a query to take data like:
And display it like:
I tried using lag, but it mixes up some of the records. Would anyone be able suggest a good way to do this?
Thanks!
DECLARE #TABLE TABLE
(ID INT ,NAME VARCHAR(20), LOCATIONDATE DATETIME, REASON VARCHAR(20))
INSERT INTO #TABLE
(ID,NAME, LOCATIONDATE, REASON)
VALUES
( 1,'abc',CAST('2016/01/01' AS SMALLDATETIME),'move'),
( 2,'def',CAST('2016/02/01' AS SMALLDATETIME),'move'),
( 1,'abc',CAST('2016/06/01' AS SMALLDATETIME),'move'),
( 2,'def',CAST('2016/07/01' AS SMALLDATETIME),'move'),
( 1,'abc',CAST('2016/08/01' AS SMALLDATETIME),'move'),
( 3,'ghi',CAST('2016/08/01' AS SMALLDATETIME),'move')
select s.*
,t1.*
from
(
select t.*,
row_number() over (partition by id order by locationdate desc) rn
from #table t
) s
left join
(
select t.*,
row_number() over (partition by id order by locationdate desc) rn
from #table t
) t1
on t1.id = s.id and t1.rn = s.rn + 1
You can try it:
SELECT a.id,a.name,a.location as currentLocation,a.locationdatedate as currrentLocate,b.location as preLocation,b.locationdatedate as prevLocate,a.changereason
FROM test as a
JOIN test as b ON a.name = b.name
where a.locationdatedate > b.locationdatedate
group by a.name
Pleas try this one. it works here
SELECT l.id,
,l.name
,l.location as currentLocation
,l.locationdatedate as currrentLocate
,r.location as preLocation
,r.locationdatedate as prevLocate
,r.changereason
FROM tableName AS l inner join
tableName AS r ON r.id=l.id
WHERE l.locationdatedate !=r.locationdatedate AND l.locationdatedate > r.locationdatedate

Get Second duplicate Record

I am getting after doing joins as ::
CompanyID EmpID Emp_no Location
-------------------- -------------------- ------------- -------------
1 24 100543 First.png
1 24 100543 Second.png
I want to select second Record i.e. Second.png by using CASE WHEN in select query.
Check this out.
declare #t table(CompanyID int, empid int, emp_no varchar(50), location varchar(100))
insert into #t values (1,24,100543,'First.png'),(1,24,100543,'Second.png'),(1,25,100544,'Second.png'),(1,25,100544,'First.png')
select * from
(
select
ROW_NUMBER() over(partition by companyid, empid order by companyid, empid ) rowno, *
from
#t
) a where rowno = 2 --and empid = 24 --here you can give empid to get particular employee detail
In case you want to get multiple empid's second entry in single select statement.
declare #t table(CompanyID int, empid int, emp_no varchar(50), location varchar(100))
insert into #t values (1,24,100543,'First.png'),(1,24,100543,'Second.png'),(1,25,100544,'Second.png'),(1,25,100544,'First.png')
,(1,26,100545,'First.png')
;with cte as
(
select
*
from
(
select
ROW_NUMBER() over(partition by empid order by empid ) rowno, *
from
#t
) a
),
cte1 as (
select
*,
ROW_NUMBER() OVER(PARTITION BY empid ORDER BY rowno DESC) as RN
from cte
)
select * from cte1 where rn = 1
You can write as:
;WITH CTE as
(
SELECT ROW_NUMBER() OVER ( PARTITION BY CompanyID,EmpID,Emp_no ORDER BY (SELECT 1))
AS rownum,CompanyID,EmpID,Emp_no,Location
FROM (SELECT * FROM #Test ) AS T
),CTE1 as
(
SELECT MAX(rownum) AS maxrownum,
CompanyID,
EmpID,
Emp_no
FROM CTE
GROUP BY CompanyID,EmpID,Emp_no
)
SELECT T.CompanyID,T.EmpID,T.Emp_no,T.Location
FROM CTE T
JOIN CTE1 T1 ON T.CompanyID = T1.CompanyID
AND T.EmpID = T1.EmpID
AND T.Emp_no = T1.Emp_no
AND T.rownum = T1.maxrownum
Explanation:
As there's no column like primary key through which we can identify
which row comes first you can write SELECT 1 in partition window.
Once you get rownumber for each combination of CompanyID,EmpID and Emp_no you can use second CTE to get the maxrow for each
combination
Just collect the data from the table for all rows with maxrownumbers
Hope this helps:)