Pivot/Transpose rows into column and convert NULLs into 0's - sql-server

I have some data that looks like this:
Table1
Number TYPE Acct Total
------ --- --- ----
1X2 GGG 111 100
1X2 GGG 222 200
What I'm trying to do is PIVOT this data so that it looks like this:
Number Type 111 222
----- --- --- ---
1X2 GGG 100 200
Here's how I pivot:
Select * from Table1
PIVOT (MAX(Total)
FOR ACCT in ([111],[222],[333])
Now this works very well for Acct 111 and 222, but for 333 the Total = NULL. Thing is here, that I might sometimes have all three ACCTs, as in 111, 222, and 333. Other times as shown in the above example one of them might be missing.
Anywho, when I do my pivot the data looks like this:
Number Type 111 222 333
----- --- --- --- ---
1X2 GGG 100 200 NULL <-- I'm trying to set this to 0
As you can see, the Value for 333 is NULL - IS there anyway I can set this value to 0?

Two options. The first will use coalesce() to eliminate the null values. The second will create a a unique set of intersections via a CROSS JOIN and a UNION ALL (brute force)
Example
Declare #YourTable Table ([Number] varchar(50),[TYPE] varchar(50),[Acct] varchar(50),[Total] varchar(50))
Insert Into #YourTable Values
('1X2','GGG',111,100)
,('1X2','GGG',222,200)
Select Number
,Type
,[111] = coalesce( [111] ,0)
,[222] = coalesce( [222] ,0)
,[333] = coalesce( [333] ,0)
From #YourTable
PIVOT (MAX(Total)
FOR ACCT in ([111],[222],[333])) pvt
Second Option:
You may notice I supplied the Accts in the last CROSS JOIN because 333 was not in the scope of the sample data. If it will exist, you could use the Select Distinct Acct like we used in Cross Apply 1 and 2
Select *
From ( Select * from #YourTable
Union All
Select Number,Type,Acct,0
From ( Select distinct Number from #YourTable ) A
Cross Join (Select distinct Type from #YourTable) B
Cross Join (
Select * from (values ('111' )
,('222' )
,('333' )
)v(Acct)
) C
) src
PIVOT (MAX(Total)
FOR ACCT in ([111],[222],[333])) pvt
Both Results would be

Related

SQL Server find chains between two columns

I have a table like this:
from | to
-----+-----
23 | 24
24 | 25
25 | 27
27 | 30
45 | 46
46 | 47
50 | 52
53 | 60
I need a SQL Server query that detect chain's and return min (from) and max (to) in each chain (also chain's with one record):
from | to
-----+-----
23 | 30
45 | 47
50 | 52
53 | 60
Here's an approach using a recursive CTE.
CREATE TABLE #chainLinks(linkFrom INTEGER, linkTo INTEGER);
INSERT INTO #chainLinks VALUES (23,24);
INSERT INTO #chainLinks VALUES (24,25);
INSERT INTO #chainLinks VALUES (25,27);
INSERT INTO #chainLinks VALUES (27,30);
INSERT INTO #chainLinks VALUES (45,46);
INSERT INTO #chainLinks VALUES (46,47);
INSERT INTO #chainLinks VALUES (50,52);
INSERT INTO #chainLinks VALUES (53,60);
WITH reccte AS
(
/*Recursive Seed*/
SELECT linkFrom AS chainStart,
linkFrom,
linkTo,
0 as links
FROM #chainLinks as chainLinks
WHERE linkFrom NOT IN (SELECT DISTINCT linkTo FROM #chainLinks)
UNION ALL
/*Recursive Term*/
SELECT
reccte.chainStart,
chainLinks.linkFrom,
chainLinks.linkTo,
links + 1
FROM reccte
INNER JOIN #chainLinks as chainLinks ON reccte.linkTo = chainLinks.linkFrom
)
SELECT chainStart, linkTo AS chainEnd
FROM
(
SELECT chainStart, linkFrom, linkTo, links, ROW_NUMBER() OVER (PARTITION BY chainStart ORDER BY links DESC) AS rn
FROM reccte
)subrn
WHERE rn = 1;
A recursive CTE takes two parts
A recursive seed - This is the part above the UNION where we determine which records from our table begin the recursion. Here we want any linkFrom that isn't also a linkTo
A recusrive term - This is the part below the UNION where we join the cte called reccte back to the original table. This part of the CTE iterates over and over again until that join fails.
In here we are also tracking that links which is just a counter of the number of iterations we have gone through to get to that outputted record. We keep the highest number of links for each starting point chainStart.
Here is the working example: https://rextester.com/JWUW57837
If there are branches within the chains it become a little bit more tricky.
In the sample data below, there's a split on From=12.
So the result shows 2 chains starting from 14.
create table yourtable (
[From] int not null,
[To] int not null,
PRIMARY KEY ([From],[To])
)
GO
✓
insert into yourtable
([From],[To]) values
(2,3),(3,5),(5,4)
,(14,12),(12,15),(15,11),(11,10)
,(12,9)
,(21,23)
GO
9 rows affected
;WITH RCTE_CHAINS AS
(
-- seeding with the start of chains
SELECT [From] AS MinFrom, [From], [To], 0 AS Lvl
, CAST(IIF(EXISTS(
SELECT 1 FROM YourTable n
WHERE n.[From] = t.[To]
),1,0) AS BIT) AS hasNext
FROM YourTable t
WHERE NOT EXISTS
(
SELECT 1
FROM YourTable t2
WHERE t2.[To] = t.[From]
)
UNION ALL
-- looping through the childs
SELECT c.MinFrom, t.[From], t.[To], c.Lvl+1
, CAST(IIF(EXISTS(
SELECT 1 FROM YourTable n
WHERE n.[From] = t.[To]
),1,0) AS BIT) AS hasNext
FROM RCTE_CHAINS c
JOIN YourTable t ON t.[From] = c.[To]
)
SELECT MinFrom AS [From], [To]
FROM RCTE_CHAINS
WHERE hasNext = 0
GO
From | To
---: | -:
21 | 23
14 | 9
14 | 10
2 | 4
db<>fiddle here

How do you dynamically Assing Unique Code for every row iin hierarchical table?

My table.
Table1
Id ParentId Name Code
1 Null John
2 1 Harry
3 1 Mary
4 2 Emma
5 2 Kyle
6 4 Robert
7 Null Rohit
I want to assign each individual with the the following format unique hierarchy codes
Output Required
Id ParentId Name Code
1 Null John 1
2 1 Harry 1.1
3 1 Mary 1.2
4 2 Emma 1.1.1
5 2 Kyle 1.1.2
6 4 Robert 1.1.1.1
7 Null Rohit 2
and so on.
I hope I've got this correctly...
You can use a recursive CTE together with ROW_NUMBER() in order to create your codes.
DECLARE #dummy TABLE(Id INT,ParentId INT,[Name] VARCHAR(100));
INSERT INTO #dummy(Id,ParentId,[Name]) VALUES
(1,Null,'John')
,(2,1 ,'Harry')
,(3,1 ,'Mary')
,(4,2 ,'Emma')
,(5,2 ,'Kyle')
,(6,4 ,'Robert')
,(7,Null,'Rohit');
WITH recCTE AS
(
SELECT Id,ParentId,[Name]
,CONCAT(N'.',CAST(ROW_NUMBER() OVER(ORDER BY Id) AS NVARCHAR(MAX))) AS Code
FROM #dummy WHERE ParentId IS NULL
UNION ALL
SELECT d.Id,d.ParentId,d.[Name]
,CONCAT(r.Code,N'.', ROW_NUMBER() OVER(ORDER BY d.Id))
FROM #dummy d
INNER JOIN recCTE r ON d.ParentId=r.Id
)
SELECT Id,ParentId,[Name]
,STUFF(Code,1,1,'') AS Code
FROM RecCTE;
The idea in short:
We pick the rows with ParentId IS NULL and give them a running number.
Now we go iteratively through them (it's a hidden RBAR actually) and call their children, again with a running number.
This we do until there is nothing left.
the final SELECT needs a STUFF in order to to get rid of the first dot.
And with an extension like this, you can create an alphanumerically sortable code:
WITH recCTE AS
(
SELECT Id,ParentId,[Name]
,CONCAT(N'.',CAST(ROW_NUMBER() OVER(ORDER BY Id) AS NVARCHAR(MAX))) AS Code
,CONCAT(N'000',CAST(ROW_NUMBER() OVER(ORDER BY Id) AS NVARCHAR(MAX))) AS Code2
FROM #dummy WHERE ParentId IS NULL
UNION ALL
SELECT d.Id,d.ParentId,d.[Name]
,CONCAT(r.Code,N'.', ROW_NUMBER() OVER(ORDER BY d.Id))
,CONCAT(r.Code2,RIGHT(CONCAT('0000',ROW_NUMBER() OVER(ORDER BY d.Id)),4))
FROM #dummy d
INNER JOIN recCTE r ON d.ParentId=r.Id
)
SELECT Id,ParentId,[Name]
,STUFF(Code,1,1,'') AS Code
,Code2
FROM RecCTE
ORDER BY Code2;

Turning string into rows

I have an old vintage system with a table looking like this.
OptionsTable
id options
=== ========================
101 Apple,Banana
102 Audi,Mercedes,Volkswagen
In the application that consumes the data, a function will break down the options column into manageable lists and populate dropdowns etc.
The problem is that this kind of data isn't very SQL friendly, making it difficult to make ad-hoc queries and reports.
To that end, I'd like to transform the data into a friendlier view, looking like this:
OptionsView
id name value
=== ========== =====
101 Apple 1
101 Banana 2
102 Audi 1
102 Mercedes 2
102 Volkswagen 3
Now, there have been some topics on splitting string into rows in t-sql (Turning a Comma Separated string into individual rows comes to mind), but apart from splitting the strings into rows, I also need to generate values based on the position in the string.
The plan is to make a view that hides the uglines of the original table.
It will be used in a join with the table housing the answers in order to make ad-hoc statistical queries.
Is there a good way of doing this without having to use cursors etc?
Perhaps adding a udf is overkill for your needs, but I created a split function a long time ago that returns the value, the startposition within the string and the index. With it, the usage in this scenario would be:
select id, String as [Name], ItemIndex as value from OptionsTable
outer apply dbo.Split(options, ',')
Results:
id Name value
101 Apple 1
101 Banana 2
102 Audi 1
102 Mercedes 2
102 Volkswagen 3
And the split function (unrevised since then):
ALTER function [dbo].[Split] (
#StringToSplit varchar(2048),
#Separator varchar(128))
returns table as return
with indices as
(
select 0 S, 1 E, 0 I
union all
select E, charindex(#Separator, #StringToSplit, E) + len(#Separator) , I + 1
from indices
where E > S
)
select substring(#StringToSplit,S,
case when E > len(#Separator) then e-s-len(#Separator) else len(#StringToSplit) - s + 1 end) String
,S StartIndex, I ItemIndex
from indices where S >0
This should work for you:
DECLARE #OptionsTable TABLE
(
id INT
, options VARCHAR(100)
);
INSERT INTO #OptionsTable (id, options)
VALUES (101, 'Apple,Banana')
, (102, 'Audi,Mercedes,Volkswagen');
SELECT OT.id, T.name, t.value
FROM #OptionsTable AS OT
CROSS APPLY (
SELECT T.column1, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM dbo.GetTableFromList(OT.options, ',') AS T
) AS T(name, value);
Here dbo.GetTableFromList is a split string function.
CROSS APPLY executes this function for each row resulting in options split into names in seperate rows. And I used ROW_NUMBER() to add value row, If you want to order result set by name, please use ROW_NUMBER() OVER (ORDER BY t.column1), that should and probably will make results look consistent all the time.
Result:
id name value
-----------------
101 Apple 1
101 Banana 2
102 Audi 1
102 Mercedes 2
102 Volkswagen 3
You could convert your string to XML and then parse the string to transpose it to rows something like this:
SELECT A.[id]
,Split.a.value('.', 'VARCHAR(100)') AS Name
,ROW_NUMBER() OVER (PARTITION BY [id] ORDER BY (SELECT NULL)) as Value
FROM (
SELECT [id]
,CAST('<M>' + REPLACE([options], ',', '</M><M>') + '</M>' AS XML) AS Name
FROM optionstable
) AS A
CROSS APPLY Name.nodes('/M') AS Split(a);
Credits: #SRIRAM
SQL Fiddle Demo

How to pick multiple values from the table and add them dynamically for picking another column

SELECT
AgentID,Seat1,SeatUpdated_1,Seat2,SeatUpdated_2,
Seat3,SeatUpdated_3,nTimesSeatChanged,
DATEDIFF(MS,(F.SeatUpdated_1),(F.SeatUpdated_3)) AvgTime
FROM ##final F
Now I have to pick SeatUpdated_3 in the diff function based on column nTimesSeatChanges.
If it have value 2 for any agent, the selected column should be SeatUpdated_2
From the limited information would look at how you store the information first of all. It looks like you are storing related values across column. It would be simpler to work with if you looked at storing them as rows.
Current:
AgentID Seat1 SeatUpdated_1 Seat2 SeatUpdated_2 Seat3 SeatUpdated_3 nTimesSeatChanged
------- ----- ------------- ----- ------------- ----- ------------- -----------------
1 11 21 01/02/2015 31 01/03/2015 2
TO :
TableKey AgentID Seat SeatUpdated
-------- ------- ---- -----------
1 1 11 01/01/2015
2 1 12 01/02/2015
3 1 13 01/03/2015
4 2 22 02/02/2015
5 2 23 02/03/2015
Then work in simple queries to get the end result. I'm not an expert by any means but this is how I would approach it.
;
--Some sample data
WITH CTE_DATA as
(
SELECT '1' as TableKey, '1' as 'AgentID','11' as'Seat','01/01/2015' as 'SeatUpdated'
UNION
SELECT '2','1','12','01/02/2015'
UNION
SELECT '3','1','13','01/03/2015'
UNION
SELECT '4','2','22','02/02/2015'
UNION
SELECT '5','2','23','02/03/2015'
)
,
--Get Min Seat
CTE_Min AS (
SELECT AgentID
,MIN(Seat) AS min_seat
FROM CTE_DATA
GROUP BY AgentID
)
--Get max seat
,CTE_Max AS (
SELECT AgentID
,MAX(Seat) AS max_seat
FROM CTE_DATA
GROUP BY AgentID
)
--Stick them together
,CTE_Join AS (
SELECT Min.AgentID
,Min.min_seat
,max.max_seat
FROM CTE_Min min
JOIN CTE_Max max ON min.AgentID = max.AgentID
)
--Get the date
,CTE_JoinDate AS (
SELECT j.*
,d1.SeatUpdated AS min_date
,d2.SeatUpdated AS max_date
FROM CTE_Join j
LEFT JOIN CTE_DATA d1 ON j.AgentID = d1.AgentID
AND j.min_seat = d1.Seat
LEFT JOIN CTE_DATA d2 ON j.AgentID = d2.AgentID
AND j.max_seat = d2.Seat
)
--Work out nSeats
,CTE_nSeats AS (
SELECT AgentID
,COUNT(1) AS nSeats
FROM CTE_DATA
GROUP BY AgentID
)
--Final result set
SELECT j.*
,DATEDIFF(DAY, min_date, max_date) AS DIFF_Days
,n.nSeats
FROM CTE_JoinDate j
LEFT JOIN CTE_nSeats n ON j.AgentID = n.AgentID

Row data from a comma-delimited field used within a select query

I am trying to build a single select query that returns
a list of Product Type Description using Table1 that is not
in the ExcludedList of Table2.
I have the following tables and data in SQL Server 2008.
Table1(nProdType INT, sProdDesc VARCHAR)
nProdType SProdDesc
----------- --------------------
1 Pencils
2 Paper
3 Pens
4 Markers
5 Erasers
6 Crayons
7 HighLighters
8 Rulers
Table2(ClassID INT, ExcludeList VARCHAR)
ClassID ExcludedList
----------- --------------------
100 2,3,4,8
101 1,2,5,6,7,8
102 4,5,6,7
103 1,2,3,4,5,6,7,8
104 7
The query should return the following:
ClassID nProdType sProdDesc
-------- --------- --------------
100 1 Pencils
100 5 Erasers
100 6 Crayons
100 7 HighLighters
101 3 Pens
101 4 Markers
102 1 Pencils
102 2 Paper
102 3 Pens
102 8 Rulers
..and so on..
I know how to build (and of course there are plenty solutions in SO) a function to split the comma-delimited field but they return ALL the rows in the table and I want it to be per record (ClassID) so that I can query to see what's not in the ExcludedList. I am trying to not code this in C# or to use a RecordSet.
You can use a CSV Splitter for this. Here is the DelimitedSplit8K function by Jeff Moden.
;WITH CteDelimitted AS(
SELECT
t.ClassID,
nProdType = CAST(s.Item AS INT)
FROM Table2 t
CROSS APPLY dbo.DelimitedSplit8K(t.ExcludedList, ',') s
),
CteCross AS(
SELECT
t2.ClassID,
t1.nProdType,
t1.SprodDesc
FROM Table1 t1
CROSS JOIN(
SELECT DISTINCT ClassID FROM Table2
)t2
)
SELECT *
FROM CteCross c
WHERE NOT EXISTS(
SELECT 1
FROM CteDelimitted
WHERE
ClassID = c.ClassID
AND nProdType = c.nProdType
)
ORDER BY ClassID, nProdType
SQL Fiddle
Another approach using NOT IN:
WITH Cte AS(
SELECT
t2.ClassID,
t1.nProdType,
t1.SprodDesc
FROM Table1 t1
CROSS JOIN(
SELECT DISTINCT ClassID FROM Table2
)t2
)
SELECT *
FROM Cte c
WHERE c.nProdType NOT IN(
SELECT CAST(s.Item AS INT)
FROM Table2
CROSS APPLY dbo.DelimitedSplit8K(ExcludedList, ',') s
WHERE ClassID = c.ClassID
)
ORDER BY ClassID, nProdType
SQL Fiddle

Resources