How to get this output using SQL query - sql-server

Need help with this.
I have this table of data for illustration. (There are many other rows of data with different customer. Do consider this in the answer)
RowID Customer Category Date Figure1
1 Cust1 Week 1 Jun-11 10
2 Cust1 Week 2 Jun-11 20
3 Cust1 Week 3 Jun-11 30
4 Cust1 Week 4 Jun-11 40
5 Cust1 Actual Jun-11 200
6 Cust1 Forecast Jun-11 100
7 Cust2 Forecast Jun-11 100
I would like to have it display the Category Actual only (row 5) including the RowID on the pivoted Category as shown below
This should be the output.
RowID Customer Date Week1 Week2 Week3 Week4 Actual Forecast
5 Cust1 Jun-11 10 20 30 40 200 100
Any help would be appreciated.
Thanks in advance.
Tried Pivot but it gives me this which is not i want.
RowID Customer Date Week1 Week2 Week3 Week4 Actual Forecast
1 Cust1 Jun-11 10 null null null null null
2 Cust1 Jun-11 null 20 null null null null
3 Cust1 Jun-11 null null 30 null null null
4 Cust1 Jun-11 null null null 40 null null
5 Cust1 Jun-11 null null null null 200 null
6 Cust1 Jun-11 null null null null null 100

PIVOT is fine, but you need to GROUP BY and SUM afterwards.
Alternatively, you can self-JOIN on all the different code criteria, but it can be slightly less-maintainable than having the list of values in one place like you can with PIVOT.

You could do it this way:
SELECT
RowID,
Customer,
Date,
(SELECT Figure1 FROM sotest WHERE Customer = 'Cust1' AND Date = 'Jun-11' AND Category = 'Week 1') AS Week1,
(SELECT Figure1 FROM sotest WHERE Customer = 'Cust1' AND Date = 'Jun-11' AND Category = 'Week 2') AS Week2,
(SELECT Figure1 FROM sotest WHERE Customer = 'Cust1' AND Date = 'Jun-11' AND Category = 'Week 3') AS Week3,
(SELECT Figure1 FROM sotest WHERE Customer = 'Cust1' AND Date = 'Jun-11' AND Category = 'Week 4') AS Week4,
(SELECT Figure1 FROM sotest WHERE Customer = 'Cust1' AND Date = 'Jun-11' AND Category = 'Actual') AS Actual,
(SELECT Figure1 FROM sotest WHERE Customer = 'Cust1' AND Date = 'Jun-11' AND Category = 'Forecast') AS Forecast
FROM
sotest
WHERE
Customer = 'Cust1'
AND Date = 'Jun-11'
AND Category = 'Actual'
Should be fairly easy to wrap up in a stored procedure, where you can pass the CustomerID and Date params in.

Try this solution based on two parameters (#MyCustomer & #MyDate):
DECLARE #MyCustomer VARCHAR(10) = 'Cust1'
,#MyDate VARCHAR(10) = 'Jun-11';
SELECT pvt.*
FROM
(
SELECT t.Customer, t.Date, t.Category, t.Figure1
FROM MyTable t
WHERE t.Customer = #MyCustomer AND t.[Date] = #MyDaye
) src
PIVOT ( SUM(src.Figure1) FOR src.Category IN ([Week 1], [Week 2], [Week 3], [Week 4], [Actual], [Forecast]) ) pvt
The basic idea is to filter in src derived table only those rows and columns you need for pivot, nothing more or less.

Related

Find each employee Project Start Date and End Date (i.e., Start Date and End Date should be continuous without any break in Days/Months/Year)

ID
EmployeeId
ProjectId
StartDate
EndDate
1
1
100
01-04-2019
30-04-2019
2
1
100
01-05-2019
31-05-2019
3
1
100
01-12-2019
31-12-2019
4
1
100
01-01-2020
31-01-2020
5
2
200
01-01-2019
31-01-2019
6
2
200
01-02-2019
28-02-2019
7
2
200
01-04-2019
28-04-2019
8
2
200
01-05-2019
31-05-2019
9
2
200
01-06-2019
30-06-2019
10
3
100
01-08-2019
31-08-2019
11
3
100
01-09-2019
30-09-2019
12
3
200
01-10-2019
31-10-2019
13
3
200
01-11-2019
30-11-2019
14
3
300
01-12-2019
31-12-2019
15
3
300
01-01-2020
31-01-2020
16
3
300
01-02-2020
29-02-2020
expected Output
EmployeeId
ProjectId
StartDate
EndDate
1
100
01-04-2019
31-05-2019
1
100
01-12-2019
31-01-2020
2
200
01-01-2019
28-02-2019
2
200
01-04-2019
28-04-2019
2
200
01-05-2019
30-06-2019
3
100
01-08-2019
30-09-2019
3
200
01-10-2019
30-11-2019
3
300
01-12-2019
29-02-2020
I have tried to find the enddate of the currentrow is enddate+1 is startdate of the next row,if it is continious without any gaps then need to select startdate of the previous row and enddate of current row.
;with MyCTE as
(
select mt.EmployeeId, mt.StartDate, mt.EndDate, ROW_NUMBER() over (order by ID) as RowNum
from #Employees mt
)
select c1.employeeId, case when c2.employeeId is null then c1.StartDate else dateadd(dd,1, c2.EndDate) end as StartDate,
c1.EndDate
from MyCTE c1
left join MyCTE c2
on C1.employeeId=c2.employeeId and
--and dateadd(dd,1,c1.startdate)
c1.RowNum = c2.RowNum +1
This is a classic gaps-and-islands problem.
There are many solutions. A typical simple (if not very efficient) solution, is as follows:
Use LAG to identify rows which start a group/island (partitioning as necessary)
Use a windowed COUNT to assign a group ID to each of those
Group by that ID, and take the MIN/MAX of the values
WITH PrevValues AS (
SELECT *,
IsStart = CASE WHEN DATEADD(day, -1, StartDate) <=
LAG(EndDate) OVER (PARTITION BY EmployeeId, ProjectId ORDER BY StartDate)
THEN NULL ELSE 1 END
FROM Employees e
),
Groups AS (
SELECT *,
GroupId = COUNT(IsStart) OVER (PARTITION BY EmployeeId, ProjectId ORDER BY StartDate ROWS UNBOUNDED PRECEDING)
FROM PrevValues pv
)
SELECT
g.EmployeeId,
g.ProjectId,
StartDate = MIN(StartDate),
EndDate = MAX(EndDate)
FROM Groups g
GROUP BY
g.EmployeeId,
g.ProjectId,
g.GroupId;
db<>fiddle

create date range report based on history table

We have been keeping track of some changes in a History Table like this:
ChangeID EmployeeID PropertyName OldValue NewValue ModifiedDate
100 10 EmploymentStart Not Set 1 2013-01-01
101 10 SalaryValue Not Set 55000 2013-01-01
102 10 SalaryValue 55000 61500 2013-03-20
103 10 SalaryEffectiveDate 2013-01-01 2013-04-01 2013-03-20
104 11 EmploymentStart Not Set 1 2013-01-21
105 11 SalaryValue Not Set 43000 2013-01-21
106 10 SalaryValue 61500 72500 2013-09-20
107 10 SalaryEffectiveDate 2013-04-01 2013-10-01 2013-09-20
Basically if an Employee's Salary changes, we log two rows in the history table. One row for the Salary value itself and the other row for the salary effective date. So these two have identical Modification Date/Time and are kind safe to assume that are always after each other in the database. We can also assume that Salary Value is always logged first (so it is one record before the corresponding effective date
Now we are looking into creating reports based on a given date range into a table like this:
Annual Salary Change Report (2013)
EmployeeID Date1 Date2 Salary
10 2013-01-01 2013-04-01 55000
10 2013-04-01 2013-10-01 61500
10 2013-10-01 2013-12-31 72500
11 2013-03-21 2013-12-31 43000
I have done something similar in the past by joining the table to itself but in those cases the effective date and the new value where in the same row. Now I have to create each row of the output table by looking into a few rows of the existing history table. Is there an straightforward way of doing this whitout using cursors?
Edit #1:
Im reading on this and apparently its doable using PIVOTs
Thank you very much in advance.
You can use self join to get the result you want. The trick is to create a cte and add two rows for each EmployeeID as follows (I call the history table ht):
with cte1 as
(
select EmployeeID, PropertyName, OldValue, NewValue, ModifiedDate
from ht
union all
select t1.EmployeeID,
(case when t1.PropertyName = "EmploymentStart" then "SalaryEffectiveDate" else t1.PropertyName end),
(case when t1.PropertyName = "EmploymentStart" then t1.ModifiedDate else t1.NewValue end),
(case when t1.PropertyName = "SalaryValue" then t1.NewValue
when t1.PropertyName = "SalaryEffectiveDate" then "2013-12-31"
when t1.PropertyName = "EmploymentStart" then "2013-12-31" end),
"2013-12-31"
from ht t1
where t1.ModifiedDate = (select max(t2.ModifiedDate) from ht t2 where t1.EmployeeID = t2.EmployeeID)
)
select t3.EmployeeID, t4.OldValue Date1, t4.NewValue Date2, t3.OldValue Salary
from cte1 t3
inner join cte1 t4 on t3.EmployeeID = t4.EmployeeID
and t3.ModifiedDate = t4.ModifiedDate
where t3.PropertyName = "SalaryValue"
and t4.PropertyName = "SalaryEffectiveDate"
order by t3.EmployeeID, Date1
I hope this helps.
It is a little over kill to use pivot since you only need two properties. Use GROUP BY can also achieve this:
;WITH cte_salary_history(EmployeeID,SalaryEffectiveDate,SalaryValue)
AS
(
SELECT EmployeeID,
MAX(CASE WHEN PropertyName='SalaryEffectiveDate' THEN NewValue ELSE NULL END) AS SalaryEffectiveDate,
MAX(CASE WHEN PropertyName='SalaryValue' THEN NewValue ELSE NULL END) AS SalaryValue
FROM yourtable
GROUP BY EmployeeID,ModifiedDate
)
SELECT EmployeeID,SalaryEffectiveDate,
LEAD(SalaryEffectiveDate,1,'9999-12-31') OVER(PARTITION BY EmployeeID ORDER BY SalaryEffectiveDate) AS SalaryEndDate,
SalaryValue
FROM cte_salary_history

Select rowset with null value in first row of group by result set

I am stuck with a problem.
I have some data likes these :
Id Creation date Creation date hour range Id vehicule Id variable Value
1 2017-03-01 9:10 2017-03-01 9:00 1 6 0.18
2 2017-03-01 9:50 2017-03-01 9:00 1 3 0.50
3 2017-03-01 9:27 2017-03-01 9:00 1 3 null
4 2017-03-01 10:05 2017-03-01 10:00 1 3 0.35
5 2017-03-01 10:17 2017-03-01 10:00 1 3 0.12
6 2017-03-01 9:05 2017-03-01 9:00 1 5 0.04
7 2017-03-01 9:57 2017-03-01 9:00 1 5 null
I need to select rowset group by Id vehicule, Id variable, Creation date hour range and order by group by Id vehicule, Id variable, Creation date where the first Value is null but second value, third value, ... is not null. So, in the sample above, the following rowset :
Id Creation date Creation date hour range Id vehicule Id variable Value
3 2017-03-01 9:27 2017-03-01 9:00 1 3 null
2 2017-03-01 9:50 2017-03-01 9:00 1 3 0.50
Could you help me please ?
Thank you
You will have no luck with a group by in this case. I would give 2 "if exists" into the where clause to filter all IDs that fit your criteria:
(for example/not tested/probably takes forever)
select *
from yourTable y1
where id in
--the id must be in all IDs, where the first value of the set is null
--same ID instead of group by
(select 1 from yourTable y2 where y1.IDs = y2.IDs and
--the first in the set
y2.createdate = (select min(createdate) from yourtable y3 with sameid) and
y2.value is null)
AND
--the id must also be in the IDs, where there are values besides the first that are not null
id in (same select but with "not min" and "not null" obviously
hope that helped :)
Include the Value field in the ORDER BY clause and it will be sorted to the top because NULL has a lower practical value than a non-NULL value.
Assuming (because your middle paragraph is hard to understand) you want all the fields output but you want the 4th and 5th columns to produce some grouping of the output, with Value = NULL at the top of each group:
SELECT Id, CreatedDate, CreatedDateHourRange, IdVehicule, IdVariable, Value
ORDER BY IdVehicule, IdVariable, Value
I don't see any need for an actual GROUP BY clause.
I think it is unclear as to whether you want to limit the NULL Value rows in each block to just one row of NULL, but if you do you would need to state the order for which the datetime columns are sorted.
indeed group by was no use here. Also I wasn't sure where your 10:00 records were going to. Does this help?
;WITH CTE_ADD_SOME_LOGIC
AS
(
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value
, CASE WHEN Value IS NULL THEN 1 ELSE 0 END AS VALUE_IS_NULL FROM tbl
),
CTE_MORE_LOGIC
AS
(
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value,VALUE_IS_NULL
, RANK() OVER (ORDER BY CreationDateHourRange,VALUE_IS_NULL) AS RN FROM CTE_ADD_SOME_LOGIC),
CTE_ORDER
AS
(
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value,VALUE_IS_NULL, RN
, ROW_NUMBER() OVER(PARTITION BY RN ORDER BY RN,IdVehicle,IdVariable,CreationDate, VALUE_IS_NULL DESC) AS HIERARCHY FROM CTE_MORE_LOGIC
)
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value FROM CTE_ORDER WHERE HIERARCHY = 1
ORDER BY Id
Try this Query
DECLARE #Nulloccurrence INT=1 -- Give like 1,2,3 value to get first null occurrence 2 for 2nd null occurrence
SELECT TOP 2 *
FROM cte
WHERE Id <= (
SELECT ID FROM
(
SELECT Id, ROW_NUMBER()OVER( Order by id) AS Seq
FROM cte
WHERE (
CASE
WHEN CAST(variableValue AS VARCHAR) IS NULL
THEN 'P'
ELSE CAST(variableValue AS VARCHAR)
END
) = 'P'
)Dt
WHERE Dt.Seq=#Nulloccurrence
)
ORDER BY 1 DESC
Expected Result
Id Creationdate Creationdatehourrange Ids vehicleId variableValue
------------------------------------------------------------------------
3 2017-03-01 9:27 2017-03-01 9:00 1 3 NULL
2 2017-03-01 9:50 2017-03-01 9:00 1 3 0.50
For 'where the first Value is null but second value, third value, ... is not null' i suppose you want to filter cases where there is a null and a not null value at [Value] within the set you group by, to decide to filter or not that grouped row. This cannot be filtered on standard WHERE clause because at WHERE clause each row is filtered with conditions relevant to that row scope only. Simply put, each row filtered cannot 'see' other rows unless you use sub-query. You need to use HAVING clause (the comment out is for 2+ null records)
This will work:
> DECLARE #mytbl TABLE(Id INT, [Creation date] DATETIME, [Creation date
> hour range] DATETIME, [Id veh] INT, [Id var] INT, Value INT )
>
> INSERT INTO #mytbl VALUES (1,'2017-03-01 9:10 ','2017-03-01 9:00 ',1,
> 6, 0.18) INSERT INTO #mytbl VALUES (2,'2017-03-01 9:50 ','2017-03-01
> 9:00 ',1, 3, 0.50) INSERT INTO #mytbl VALUES (3,'2017-03-01 9:27
> ','2017-03-01 9:00 ',1, 3, NULL) INSERT INTO #mytbl VALUES
> (4,'2017-03-01 10:05','2017-03-01 10:00',1, 3, 0.35) INSERT INTO
> #mytbl VALUES (5,'2017-03-01 10:17','2017-03-01 10:00',1, 3, 0.12)
> INSERT INTO #mytbl VALUES (6,'2017-03-01 9:05 ','2017-03-01 9:00 ',1,
> 5, 0.04) INSERT INTO #mytbl VALUES (7,'2017-03-01 9:57 ','2017-03-01
> 9:00 ',1, 5, NULL)
>
> SELECT [Id veh], [Id var],[Creation date hour range] FROM #mytbl GROUP
> BY [Id veh], [Id var],[Creation date hour range] HAVING COUNT([Id
> veh]) - COUNT(Value) = 1
> --HAVING COUNT([Id veh]) - COUNT(Value) >= 1 ORDER BY [Id veh], [Id var],[Creation date hour range]

check the first rows date is in between the next rows date

Each row has a DateEff and a DateExp. Lets say I return 5 rows. I need to check the DateEff from the first row to see if it is in between the DateEff and DateExp of the second, third, fourth and fifth row and so on. I need to check every DateEff to make sure it is not between any rows DateEff and DateExp.
Here is a sample of what the data looks like. As you can see, row 3 DateEff is (2013-03-30) and it is in between row 4 DateEff and DateExp and row 5DateEff and `DateExp.
Table
rowid DateEff DateExp
1 1969-01-01 2012-09-30
2 2012-10-01 2012-12-31
3 2013-03-30 2014-12-31
4 2013-01-01 2015-02-10
5 2013-01-01 2999-01-01
Results would look like this
Prob Id Problem Date Affected Id Aff Date Range
3 2013-03-30 4 2013-01-01 - 2015-02-10
3 2013-03-30 5 2013-01-01 - 2999-01-01
I think this should work for you:
select
[Prob Id] = t.rowid,
[Problem Date] = t.DateEff,
[Affected Id] = a.rowid,
[Aff Date Range] = concat(a.DateEff,' - ',a.DateExp)
from tbl t -- your table is called tbl
outer apply
(
select *
from tbl -- your table is called tbl
where t.DateEff between dateeff and DateExp and rowid > t.rowid
) a
where a.DateEff is not null
order by t.rowid, t.DateEff;
With your sample data this is the result:
Prob Id Problem Date Affected Id Aff Date Range
3 2013-03-30 4 2013-01-01 - 2015-02-10
3 2013-03-30 5 2013-01-01 - 2999-01-01
4 2013-01-01 5 2013-01-01 - 2999-01-01
To get the exact output from your example (excluding row 4) change the condition in the apply to t.DateEff > dateeff and t.DateEff < DateExp and rowid > t.rowid. The output would then be:
Prob Id Problem Date Affected Id Aff Date Range
3 2013-03-30 4 2013-01-01 - 2015-02-10
3 2013-03-30 5 2013-01-01 - 2999-01-01
Join the table to itself to return overlapping pairs of rows:
Select a.RowID, a.DateEff as ProblemDate
, b.RowID as OverlapID, b.DateEff as OverlapStart, b.DateExp as OverlapEnd
from MyTable a
left join MyTable b
on a.RowID <> b.RowID
and a.DateEff <= b.DateExp
and a.DateEff >= b.DateEff

Loop through month and year till date while using a merge statement

I have 2 tables with the following datas in them:-
Company
CompanyId CompanyName
1 Company1
2 Company2
3 Company3
Employees
EmployeeId EmployeeName CompanyId StartDate
1 Employee1 1 12/21/2011
2 Employee2 1 01/20/2012
3 Employee3 2 03/23/2012
4 Employee4 2 07/15/2012
5 Employee5 2 01/20/2013
6 Employee6 3 12/17/2013
Now i want to check, How many people were recruited in the team in the specified month and year? I have the storage table as follows:-
RecruiterIndicator
CompanyId Year Month EmployeeRecruited
1 2011 12 1
1 2012 1 1
2 2012 3 1
2 2012 7 1
2 2013 1 1
3 2013 12 1
This should be a merge stored procedure that should update the data if it is present for the same month year and company and insert if that is not present? The loop would start from a particular date that can be an parameter and it would loop through the current month.
Please help me with this
Thanks
Vishal
SELECT YEAR(StartDate) AS [Year], MONTH(StartDate) AS [Month], COUNT(*) EmpTotal
FROM Employees
GROUP BY YEAR(StartDate), MONTH(StartDate)
If you want to see the Total Employees by company as well you can do something like this
SELECT YEAR(StartDate) AS [Year], MONTH(StartDate) AS [Month]
,C.CompanyName , COUNT(E.EmployeeId) EmpTotal
FROM Employees E INNER JOIN Company C
ON E.CompanyId = C.CompanyId
GROUP BY YEAR(StartDate), MONTH(StartDate) ,C.CompanyName

Resources