Convert colums to rows in SQL Server 2008 - sql-server

I have this table of data. Each row represents 6 months of data (Quantity).
EX: Year = 2016 and Term = 1 means Mon1 to Mon6 is Jan to June
If Term = 2 Means July to December.
Above data I have to show as below. Need 3 columns of data like
So each product must be 12 months data from current month.
Means, Current month June then 2016June to 2017May (Including current month)
Can you please suggest with some script to retrieve this data from above table?

try this, the part to get month number is tricky, I have used the column names, you need to change that part if column names are not the same i.e. mon1, mon2 and so on.
DECLARE #MyTable TABLE (ProductID INT, [Year] INT, Team INT, Mon1 INT, Mon2 INT, Mon3 INT, Mon4 INT, Mon5 INT, Mon6 INT)
INSERT INTO #MyTable
VALUES (1, 2016, 1, 10,20,30,40,50,60)
,(1, 2016, 2, 10,20,30,40,50,60)
,(1, 2017, 1, 10,20,30,40,50,60)
,(1, 2017, 2, 10,20,30,40,50,60)
,(2, 2016, 1, 10,20,30,40,50,60)
,(2, 2016, 2, 10,20,30,40,50,60)
,(2, 2017, 1, 10,20,30,40,50,60)
,(2, 2017, 2, 10,20,30,40,50,60)
,(3, 2016, 1, 10,20,30,40,50,60)
,(3, 2016, 2, 10,20,30,40,50,60)
,(3, 2017, 1, 10,20,30,40,50,60)
,(3, 2017, 2, 10,20,30,40,50,60)
,(4, 2016, 1, 10,20,30,40,50,60)
,(4, 2016, 2, 10,20,30,40,50,60)
,(4, 2017, 1, 10,20,30,40,50,60)
,(4, 2017, 2, 10,20,30,40,50,60)
DECLARE #FromYear INT, #FromYearMonth INT, #ToYear INT, #ToYearMonth INT
-- Get from - to yearmonth base on current date.
SELECT #FromYear = YEAR(GETDATE()),
#ToYear = YEAR( DATEADD(month,11,GETDATE())) -- add 11 months as it starts from current month
SELECT #FromYearMonth = #FromYear * 100 + MONTH(GETDATE()),
#ToYearMonth = #ToYear * 100 + MONTH( DATEADD(month,11,GETDATE()))
SELECT #FromYear, #ToYear, #FromYearMonth, #ToYearMonth -- check the dates.
;WITH CTE AS
(
SELECT
ProductID,
[Year],
Team,
Quantity,
CASE WHEN Team = 2 THEN RIGHT(Months,1) + 6 ELSE RIGHT(Months,1) END Mon, -- get month number
([Year] * 100) + CASE WHEN Team = 2 THEN RIGHT(Months,1) + 6 ELSE RIGHT(Months,1) END YearMonth -- get YearMonth value
FROM ( SELECT *
FROM #MyTable
WHERE [YEAR] BETWEEN #FromYear AND #ToYear -- filter data for year range
) x
UNPIVOT
( Quantity FOR Months IN (Mon1,Mon2,Mon3,Mon4,Mon5,Mon6) -- deflat row-cols
) p
)
SELECT ProductID, YearMonth, Quantity, Team, Mon
FROM CTE
WHERE YearMonth BETWEEN #FromYearMonth AND #ToYearMonth -- filer final data.

Related

Whether to perform an inner join or cross join to expand based on date range provided in another table?

I have 2 tables.
DateTable1 (client-wise month start and end dates, an appropriate month number):
Client|MonthNumber|MonthStartDate|MonthEndDate
DateTable2 (1 row for each day, for each client, and the row holds an appropriare QuarterNumber):
Client|Date|QuarterNumber
I want to create 1 date table such that it has 1 row for each day, for each client showing the QuarterNumber (thus far it is the same as DateTable2), and additionally I want the MonthNumber from DateTable1.
I am thinking about 2 solutions:
Perform an inner join on client name, and apply the where criteria to filter such that DateTable2's date is between the start and end values of DateTable1.
Perform cross join (so without an on clause), and apply the same where criteria as above.
Please can I have guidance on how to choose from the above solutions?
The correct answer here is likely to utilize a calendar table.
If you can't or don't want to fully implement one, you can fake one with a little recursion:
DECLARE #DateTable1 TABLE (Client INT, MonthNumber INT, MonthStartDate DATE, MonthEndDate DATE);
INSERT INTO #DateTable1 (Client, MonthNumber, MonthStartDate, MonthEndDate) VALUES
(1, 1, '2022-01-01', '2022-01-31'),(1, 2, '2022-02-01', '2022-02-28'),(1, 3, '2022-03-01', '2022-03-31'),(1, 4, '2022-04-01', '2022-04-30'),(1, 5, '2022-05-01', '2022-05-31'),
(2, 1, '2022-01-01', '2022-01-31'),(2, 2, '2022-02-01', '2022-02-28'),(2, 3, '2022-03-01', '2022-03-31'),(2, 4, '2022-04-01', '2022-04-30'),
(3, 1, '2022-01-01', '2022-01-31'),(3, 2, '2022-02-01', '2022-02-28'),(3, 3, '2022-03-01', '2022-03-31'),(3, 4, '2022-04-01', '2022-04-30'),(3, 5, '2022-05-01', '2022-05-31'),(3, 6, '2022-06-01', '2022-06-30');
;WITH cte AS (
SELECT Client, MIN(MonthNumber) AS mnS, MAX(MonthNumber) AS mnE, MIN(MonthStartDate) AS Date, DATEPART(QUARTER,MIN(MonthStartDate)) AS Quarter
FROM #DateTable1
GROUP BY Client
UNION ALL
SELECT Client, mnS, mnE, DATEADD(DAY,1,Date) AS Date, DATEPART(QUARTER,DATEADD(DAY,1,Date)) AS Quarter
FROM cte
WHERE DATEPART(MONTH,DATEADD(DAY,1,Date)) <= mnE
)
SELECT Client, Date, Quarter
FROM cte
ORDER BY Client, Date
OPTION (MAXRECURSION 0)
Using just one of you tables we can iterate over the dates that should be available for your clients, and fill in the gaps.
Client Date Quarter
---------------------------
1 2022-01-01 1
1 2022-01-02 1
1 2022-01-03 1
...
1 2022-05-30 2
1 2022-05-31 2
2 2022-01-01 1
2 2022-01-02 1
...
2 2022-04-29 2
2 2022-04-30 2
3 2022-01-01 1
3 2022-01-02 1
...
3 2022-06-29 2
3 2022-06-30 2
Each client now has a row for each day (where they have data in the first table).
You can join to this as necessary.
Edit:
if the other table is as described and contains a non-standard quarter number you could just join to it:
DECLARE #DateTable1 TABLE (Client INT, MonthNumber INT, MonthStartDate DATE, MonthEndDate DATE);
INSERT INTO #DateTable1 (Client, MonthNumber, MonthStartDate, MonthEndDate) VALUES
(1, 1, '2022-01-01', '2022-01-31'),(1, 2, '2022-02-01', '2022-02-28'),(1, 3, '2022-03-01', '2022-03-31'),(1, 4, '2022-04-01', '2022-04-30'),(1, 5, '2022-05-01', '2022-05-31'),
(2, 1, '2022-01-01', '2022-01-31'),(2, 2, '2022-02-01', '2022-02-28'),(2, 3, '2022-03-01', '2022-03-31'),(2, 4, '2022-04-01', '2022-04-30'),
(3, 1, '2022-01-01', '2022-01-31'),(3, 2, '2022-02-01', '2022-02-28'),(3, 3, '2022-03-01', '2022-03-31'),(3, 4, '2022-04-01', '2022-04-30'),(3, 5, '2022-05-01', '2022-05-31'),(3, 6, '2022-06-01', '2022-06-30');
DECLARE #DataTable2 TABLE (Client INT, Date DATE, QuarterNumber INT)
INSERT INTO #DataTable2 (Client, Date, QuarterNumber) VALUES
(1, '2022-01-01', 1),(1, '2022-01-02', 1),(1, '2022-01-03', 1),(1, '2022-01-04', 1),
(1, '2022-05-30', 2),(1, '2022-05-31', 2),(2, '2022-01-01', 1),(2, '2022-01-02', 1),
(2, '2022-01-03', 1),(2, '2022-04-29', 2),(2, '2022-04-30', 2),(3, '2022-01-01', 3),
(3, '2022-01-02', 3),(3, '2022-01-03', 3),(3, '2022-06-28', 4),(3, '2022-06-29', 4),
(3, '2022-06-30', 4)
;WITH cte AS (
SELECT Client, MIN(MonthNumber) AS mnS, MAX(MonthNumber) AS mnE, MIN(MonthStartDate) AS Date, DATEPART(QUARTER,MIN(MonthStartDate)) AS Quarter
FROM #DateTable1
GROUP BY Client
UNION ALL
SELECT Client, mnS, mnE, DATEADD(DAY,1,Date) AS Date, DATEPART(QUARTER,DATEADD(DAY,1,Date)) AS Quarter
FROM cte
WHERE DATEPART(MONTH,DATEADD(DAY,1,Date)) <= mnE
)
SELECT c.Client, c.Date, d.QuarterNumber
FROM cte c
LEFT OUTER JOIN #DataTable2 d
ON c.Client = d.Client
AND c.Date = d.Date
ORDER BY Client, Date
OPTION (MAXRECURSION 0)
I did not include every date from the other table.

SQL Server : I am trying to add a conditional select of column values based on another column

Here is my example table:
CREATE TABLE demo1
(
ProviderID int NOT NULL,
ProviderClientID int NOT NULL,
ClientIdentifier nvarchar(9) NOT NULL,
ClientIdentifierTypeID int NOT NULL
);
INSERT INTO demo1 (ProviderID, ProviderClientID, ClientIdentifier, ClientIdentifierTypeID)
VALUES
(5, 7, 123444567, 1),
(5, 7, 000111234, 2),
(5, 11, 145342332, 1),
(5, 12, 234212345, 1),
(5, 13, 324564332, 3),
(5, 14, 123222467, 3),
(5, 19, 234444879, 1),
(5, 19, 000111643, 2),
(5, 19, 999234252, 3),
(5, 20, 456333245, 1)
The column ClientIdentifierTypeID values mean:
1 - Drivers License #,
2 - SchoolID,
3 - StateID
So clients can be in the table multiple times with different identifier types.
I want to select each client & just their drivers license #. If they don't have drivers license, then get their state id. I don't want their school id at all.
This is going to be part of a client count I am going to do next.
Here is what I tried in my query so far (I filter out school id already):
WITH A AS
(
SELECT
*
FROM
demo1
WHERE
ClientIdentifierTypeID IN ('1', '3')
), B AS
(
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY [ProviderClientID] ORDER BY [ClientIdentifierTypeID]) AS rn
FROM
A
)
SELECT
*
FROM
B
WHERE
rn = 1
EDIT: I am re-reading through my query, it seems do what I want.
When there is a license# rn will be 1. If no license, the state id will have rn=1.
If counts clients including school id then disable where clause otherwise enable it.
-- sql server
SELECT ProviderClientID
, COUNT(1) count_client
, MIN(CASE WHEN ClientIdentifierTypeID = 1
THEN ClientIdentifier
WHEN ClientIdentifierTypeID = 3
THEN ClientIdentifier
END)
FROM demo1
-- WHERE ClientIdentifierTypeID IN ('1', '3')
GROUP BY ProviderClientID;
I use two Selects to filter. One is to get the uses who has driver License . Second block is to get the users who has Sate but not driver license.
then combine both lists with UNION ALL.
This is one possibility.
--client has driver License
SELECT * FROM demo1
WHERE ClientIdentifierTypeID in (1)
AND ClientIdentifierTypeID not in (3,2)
UNION ALL
-- Clients who has State and not driver License
SELECT * FROM demo1
WHERE ClientIdentifierTypeID in (3)
and ProviderClientID Not in (SELECT ProviderClientID FROM demo1
WHERE ClientIdentifierTypeID in (1)
)

Find the date when a milestone was achieved

I have people that do many multi-day assignments (date x to date Y). I would like to find the date that they completed a milestone e.g. 50 days work completed.
Data is stored as a single row per Assignment
AssignmentId
StartDate
EndDate
I can sum up the total days they have completed up to a date, but am struggling to see how I would find out the date that a milestone was hit. e.g. How many people completed 50 days in October 2020 showing the date within the month that this occurred?
Thanks in advance
PS. Our database is SQL Server.
As mentioned by prwvious comments, it would be much easier to help you if you could provide example data and table structure in order help you answer this question.
However, guessing a simple DB structure with a table for your peolple, your tasks and the work each user completed, you can get the required sum of days by use of a date table (or cte) which contains a entry for each day and the window function SUM with UNBOUNDED PRECEDING. Following an example:
DECLARE #people TABLE(
id int
,name nvarchar(50)
)
DECLARE #tasks TABLE(
id int
,name nvarchar(50)
)
DECLARE #work TABLE(
people_id int
,task_id int
,task_StartDate date
,task_EndDate date
)
INSERT INTO #people VALUES (1, 'Peter'), (2, 'Paul'), (3, 'Mary');
INSERT INTO #tasks VALUES (1, 'Devleopment'), (2, 'QA'), (3, 'Sales');
INSERT INTO #work VALUES
(1, 1, '2019-04-05', '2019-04-08')
,(1, 1, '2019-05-05', '2019-06-08')
,(1, 1, '2019-07-05', '2019-09-08')
,(2, 2, '2019-04-08', '2019-06-08')
,(2, 2, '2019-09-08', '2019-10-03')
,(3, 1, '2019-11-01', '2019-12-01')
;WITH cte AS(
SELECT CAST('2019-01-01' AS DATE) AS dateday
UNION ALL
SELECT DATEADD(d, 1, dateday)
FROM cte
WHERE DATEADD(d, 1, dateday) < '2020-01-01'
),
cteWorkDays AS(
SELECT people_id, task_id, dateday, 1 AS cnt
FROM #work w
INNER JOIN cte c ON c.dateday BETWEEN w.task_StartDate AND w.task_EndDate
),
ctePeopleWorkdays AS(
SELECT *, SUM(cnt) OVER (PARTITION BY people_id ORDER BY dateday ROWS UNBOUNDED PRECEDING) dayCnt
FROM cteWorkDays
)
SELECT *
FROM ctePeopleWorkdays
WHERE dayCnt = 50
OPTION (MAXRECURSION 0)
The solution depends on how you store your data. The solution below assumes that each worked day exists as a single row in your data model.
The approach below uses a common table expression (cte) to generate a running total (Total) for each person (PersonId) and then filters on the milestone target (I set it to 5 to reduce the sample data size) and target month.
Sample data
create table WorkedDays
(
PersonId int,
TaskDate date
);
insert into WorkedDays (PersonId, TaskDate) values
(100, '2020-09-01'),
(100, '2020-09-02'),
(100, '2020-09-03'),
(100, '2020-09-04'),
(100, '2020-09-05'), -- person 100 worked 5 days by 2020-09-05 = milestone (in september)
(200, '2020-09-29'),
(200, '2020-09-30'),
(200, '2020-10-01'),
(200, '2020-10-02'),
(200, '2020-10-03'), -- person 200 worked 5 days by 2020-10-03 = milestone (in october)
(200, '2020-10-04'),
(200, '2020-10-05'),
(200, '2020-10-06'),
(300, '2020-10-10'),
(300, '2020-10-11'),
(300, '2020-10-12'),
(300, '2020-10-13'),
(300, '2020-10-14'), -- person 300 worked 5 days by 2020-10-14 = milestone (in october)
(300, '2020-10-15'),
(400, '2020-10-20'),
(400, '2020-10-21'); -- person 400 did not reach the milestone yet
Solution
with cte as
(
select wd.PersonId,
wd.TaskDate,
count(1) over(partition by wd.PersonId
order by wd.TaskDate
rows between unbounded preceding and current row) as Total
from WorkedDays wd
)
select cte.PersonId,
cte.TaskDate as MileStoneDate
from cte
where cte.Total = 5 -- milestone reached
and year(cte.TaskDate) = 2020
and month(cte.TaskDate) = 10; -- in october
Result
PersonId MilestoneDate
-------- -------------
200 2020-10-03
300 2020-10-14
Fiddle (also shows the common table expression output).

How do you validate that range doesn't overlap in a list of data?

I have a list of data :
Id StartAge EndAge Amount
1 0 2 50
2 2 5 100
3 5 10 150
4 6 9 160
I have to set Amount for various age group.
The age group >0 and <=2 need to pay 50
The age group >2 and <=5 need to pay 100
The age group >5 and <=10 need to pay 150
But
The age group >6 and <=9 need to pay 160 is an invalid input because >6 and <=9 already exist on 150 amount range.
I have to validate such kind of invalid input before inserting my data as a bulk.Once 5-10 range gets inserted anything that is within this range should not be accepted by system. For example: In above list, user should be allowed to insert 10-15 age group but any of the following should be checked as invalid.
6-9
6-11
3-5
5-7
If Invalid Input exists on my list I don't need to insert the list.
You could try to insert your data to the temporary table first.
DECLARE #TempData TABLE
(
[Id] TINYINT
,[StartAge] TINYINT
,[EndAge] TINYINT
,[Amount] TINYINT
);
INSERT INTO #TempData ([Id], [StartAge], [EndAge], [Amount])
VALUES (1, 0, 2, 50)
,(2, 2, 5, 100)
,(3, 5, 10, 150)
,(4, 6, 9, 160);
Then, this data will be transferred to your target table using INSERT INTO... SELECT... statement.
INSERT INTO <your target table>
SELECT * FROM #TempData s
WHERE
NOT EXISTS (
SELECT 1
FROM #TempData t
WHERE
t.[Id] < s.[Id]
AND s.[StartAge] < t.[EndAge]
AND s.[EndAge] > t.[StartAge]
);
I've created a demo here
We can use recursive CTE to find how records are chained by end age and start age pairs:
DECLARE #DataSource TABLE
(
[Id] TINYINT
,[StartAge] TINYINT
,[EndAge] TINYINT
,[Amount] TINYINT
);
INSERT INTO #DataSource ([Id], [StartAge], [EndAge], [Amount])
VALUES (1, 0, 2, 50)
,(2, 2, 5, 100)
,(3, 5, 10, 150)
,(4, 6, 9, 160)
,(5, 6, 11, 160)
,(6, 3, 5, 160)
,(7, 5, 7, 160)
,(9, 10, 15, 20)
,(8, 7, 15, 20);
WITH PreDataSource AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY [StartAge] ORDER BY [id]) as [pos]
FROM #DataSource
), DataSource AS
(
SELECT [Id], [StartAge], [EndAge], [Amount], [pos]
FROM PreDataSource
WHERE [id] = 1
UNION ALL
SELECT R.[Id], R.[StartAge], R.[EndAge], R.[Amount], R.[pos]
FROM DataSource A
INNER JOIN PreDataSource R
ON A.[Id] < R.[Id]
AND A.[EndAge] = R.[StartAge]
AND R.[pos] =1
)
SELECT [Id], [StartAge], [EndAge], [Amount]
FROM DataSource;
This is giving us, the following output:
Note, that before this, we are using the following statement to prepare the data:
SELECT *, ROW_NUMBER() OVER (PARTITION BY [StartAge] ORDER BY [id]) as [pos]
FROM #DataSource;
The idea is to find records with same start age and to calculated which one is inserted first. Then, in the CTE we are getting only the first.
Assuming you are bulk inserting the mentioned data into a temp table(#tmp) or table variable (#tmp).
If you are working on sql server 2012 try the below.
select *
from(select *,lag(endage,1,0)over(order by endage) as [col1]
from #tmp)tmp
where startage>=col1 and endage>col1
The result of this query should be inserted into your main table.

How do I populate my Date Dimension Table in SQL Server so as to get a column with this specific value?

I have the following query that runs in SQL Server 2014 to populate a table called DateDimension.
DECLARE #startdate datetime
DECLARE #enddate datetime
SET #startdate = '01/01/2000'
SET #enddate = '12/31/2018'
DECLARE #loopdate datetime
SET #loopdate = #startdate
WHILE #loopdate <= #enddate
BEGIN
INSERT INTO DateDimension VALUES (
Day(#loopdate),
Month(#loopdate),
Year(#loopdate),
CASE WHEN Month(#loopdate) IN (1, 2, 3) THEN 3 -- Since Financial Year starts in July, January to March is considered as Quarter 3
WHEN Month(#loopdate) IN (4, 5, 6) THEN 4
WHEN Month(#loopdate) IN (7, 8, 9) THEN 1
WHEN Month(#loopdate) IN (10, 11, 12) THEN 2
END,
#loopdate,
CONVERT(VARCHAR(10),#loopdate,111)
)
SET #loopdate = DateAdd(d, 1, #loopdate)
END
Extract of current table, after running the above query is as follows:
id Day Month Year quarter date datevarchar
1 1 1 2000 3 2000-01-01 1/1/2000
2 2 1 2000 3 2000-01-02 1/2/2000
3 3 1 2000 3 2000-01-03 1/3/2000
4 4 1 2000 3 2000-01-04 1/4/2000
5 5 1 2000 3 2000-01-05 1/5/2000
I need the "quarter" column to show its values as below:
quarter
Qr 3 2000
It would be even nicer if the "quarter" column be left as-is and a new column added to show what I'm after. How can I do this?
You will need to add the column on to the DateDimension table first before running the query. It will be a varchar(9) column.
DECLARE #startdate datetime
DECLARE #enddate datetime
SET #startdate = '01/01/2000'
SET #enddate = '12/31/2018'
DECLARE #loopdate datetime
SET #loopdate = #startdate
WHILE #loopdate <= #enddate
BEGIN
INSERT INTO DateDimension VALUES (
Day(#loopdate),
Month(#loopdate),
Year(#loopdate),
CASE WHEN Month(#loopdate) IN (1, 2, 3) THEN 3 -- Since Financial Year starts in July, January to March is considered as Quarter 3
WHEN Month(#loopdate) IN (4, 5, 6) THEN 4
WHEN Month(#loopdate) IN (7, 8, 9) THEN 1
WHEN Month(#loopdate) IN (10, 11, 12) THEN 2
END,
#loopdate,
CONVERT(VARCHAR(10),#loopdate,111),
CASE WHEN Month(#loopdate) IN (1, 2, 3) THEN 'Qr 3 ' + CONVERT(VARCHAR(4),Year(#loopdate))
WHEN Month(#loopdate) IN (4, 5, 6) THEN 'Qr 4 ' + CONVERT(VARCHAR(4),Year(#loopdate))
WHEN Month(#loopdate) IN (7, 8, 9) THEN 'Qr 1 ' + CONVERT(VARCHAR(4),Year(#loopdate))
WHEN Month(#loopdate) IN (10, 11, 12) THEN 'Qr 2 ' + CONVERT(VARCHAR(4),Year(#loopdate))
END
)
SET #loopdate = DateAdd(d, 1, #loopdate)
END

Resources