Create Hierarchy from Ordered Table - sql-server

I have a table that has parent-child relationship in one column.
I can use a row number that has been added when the file was loaded to sort the table into the correct order.
I would like to get a full hierarchy from the column using the sort order
Sample Data
RowNumber Type Area Name
1 1 Europe Bob
2 2 Scotland Bill
3 3 Edinburgh Dave
4 2 England Sharron
5 3 London Tessa
6 2 Spain Steve
7 2 Portugal Carie
8 1 Asia Helen
9 2 Thailand John
1 2 Japan Frank
11 3 Tokyo Kate
12 3 Osaka Brian
13 1 North America Joe
I would like to be able to say exclude rows where level 1 = Asia.
Happy to use temporary tables or anything to get this working!
Help!

I'm going by a guess on here, but it looks like you're looking for the previous level with a value of type 1 lower. This isn't how traditional hierarchical data is stored, so we need to fix that.
First some consumable Sample Data:
CREATE TABLE dbo.SampleData (RowNumber int,
[Type] int,
Area varchar(30),
[name] varchar(30));
GO
INSERT INTO dbo.SampleData (RowNumber,
[Type],
Area,
[name])
VALUES(1,1,'Europe','Bob'),
(2,2,'Scotland','Bill'),
(3,3,'Edinburgh','Dave'),
(4,2,'England','Sharron'),
(5,3,'London','Tessa'),
(6,2,'Spain','Steve'),
(7,2,'Portugal','Carie'),
(8,1,'Asia','Helen'),
(9,2,'Thailand','John'),
(10,2,'Japan','Frank'),
(11,3,'Tokyo','Kate'),
(12,3,'Osaka','Brian'),
(13,1,'North America','Joe');
GO
Now we need to add a new column for the parent's ID (row number):
ALTER TABLE dbo.SampleData ADD ParentID int;
Then we need to populate that column:
UPDATE SD
SET ParentID = P.RowNumber
FROM dbo.SampleData SD
CROSS APPLY (SELECT TOP 1 ca.RowNumber
FROM dbo.SampleData ca
WHERE ca.[Type] = SD.[Type] - 1
AND ca.RowNumber < SD.RowNumber
ORDER BY ca.RowNumber DESC) P;
Now we can achieve what you're after, such as getting all rows that aren't related to 'Asia' by use of a rCTE (recursive Common Table Expression):
WITH rCTE AS(
SELECT RowNumber,
[Type],
Area,
[name]
FROM dbo.SampleData
WHERE [Type] = 1
AND Area != 'Asia'
UNION ALL
SELECT SD.RowNumber,
SD.Type,
SD.Area,
SD.name
FROM rCTE r
JOIN dbo.SampleData SD ON r.RowNumber = SD.ParentID)
SELECT r.RowNumber,
r.[Type],
r.Area,
r.[name]
FROM rCTE r;

If you have a known or maximum number of levels
Example
Select *
,ID = RowNumber
,Pt = choose( [Type]-1
,max(case when [Type]=1 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=2 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=3 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=4 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=5 then RowNumber end) over (Order By RowNumber)
)
From YourTable A
Returns

Related

How to get previous record if current record does not exist in table

my table like this
Id Date type quantity
1 29/04/2019 APPLE 2
2 29/04/2019 Banana 15
3 29/04/2019 Mango 100
4 29/04/2019 Grapes 50
5 29/04/2019 Fish 80
6 30/04/2019 APPLE 4
7 30/04/2019 Grapes 100
8 30/04/2019 Fish 90
9 01/05/2019 APPLE 6
10 01/05/2019 Banana 30
11 01/05/2019 Grapes 150
12 01/05/2019 Fish 100
13 02/05/2019 Mango 200
14 02/05/2019 Grapes 200
15 02/05/2019 Fish 110
16 03/05/2019 APPLE 8
17 03/05/2019 Banana 45
18 03/05/2019 Mango 300
19 04/05/2019 APPLE 10
20 04/05/2019 Grapes 300
21 04/05/2019 Fish 120
22 05/05/2019 APPLE 12
23 05/05/2019 Fish 130
i miss some inputs every day,But i need to fill the gaps with previous row of the same "Type" on 30/04/2019 i missed "Banana & Mango" bu i need like
Id Date type quantity
1 29/04/2019 APPLE 2
2 29/04/2019 Banana 15
3 29/04/2019 Mango 100
4 29/04/2019 Grapes 50
5 29/04/2019 Fish 80
6 30/04/2019 APPLE 4
7 30/04/2019 Grapes 100
8 30/04/2019 Fish 90
9 30/04/2019 Banana 15
10 30/04/2019 Mango 100
actually last two rows are null but it should updated same on 29/04/2019
I think the easiets way might be this:
DECLARE #PDate DATE = SELECT TOP 1 Date FROM YourTable ORDER BY Date ASC --Previous Date
DECLARE #NDate DATE = SELECT TOP 1 Date FROM YourTable WHERE DATE>#PDate --Next Date
WHILE (#NDate IS NOT NULL)
BEGIN
WITH X AS
(
SELECT T1.Date AS Date1, T1.Type AS Type1, T1.Quantity AS Q1
T2.Date AS Date2, T2.Type AS Type2, T2.Quantity AS Q2
FROM YourTable T1
LEFT JOIN YourTable T2 ON T1.Type = T2.Type
WHERE T1.Date = #PDate AND T2.Date = #NDate
)
INSERT INTO YourTable (Date,Type,Quantity)
SELECT #NDate,Type1,Q1
WHERE X.Type2 IS NULL
SET #PDate = #NDate
SET #NDate = NULL -- If next result wasnt found this stays null for while condition
SET #NDate = SELECT TOP 1 Date FROM YourTable WHERE Date>#PDate
END
I think this is the way that may work and I wish so
( if there is any syntax or ... mistakes its because I didnt have SSMS installed to test. Sorry)
try this :
declare #date date
and for initiate #date you can use select #date=max(date) from table1 or pass static value set #date='02/01/2019'
and then find input
select input,max(date) as MaxDate into #temp
from table1
where input not in (select input from table1 where date=#date )
group by input
then :
select t.* from Table1 t join #temp on Table1.input=#temp.Input and Table1.date=#temp.MaxDate
OK, after the goal posts are settled, this is one method. Note that this solution builds both a Types and Dates dataset. Really the Types dataset should already exist somewhere in your database, and you should create a Calendar Table if you're going to be doing this type of work often.
Any way, I've left comments in the code for you. I've assumed you're using SQL Server 2012+, as 2008 is literally about to run out of support.
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
[date] date,
[type] varchar(10),
Quantity int);
INSERT INTO dbo.MyTable
SELECT CONVERT(date,[date],103),
RTRIM([Type]),
Quantity
FROM (VALUES('29/04/2019','APPLE ',2),
('29/04/2019','Banana',15),
('29/04/2019','Mango ',100),
('29/04/2019','Grapes',50),
('29/04/2019','Fish ',80),
('30/04/2019','APPLE ',4),
('30/04/2019','Grapes',100),
('30/04/2019','Fish ',90),
('01/05/2019','APPLE ',6),
('01/05/2019','Banana',30),
('01/05/2019','Grapes',150),
('01/05/2019','Fish ',100),
('02/05/2019','Mango ',200),
('02/05/2019','Grapes',200),
('02/05/2019','Fish ',110),
('03/05/2019','APPLE ',8),
('03/05/2019','Banana',45),
('03/05/2019','Mango ',300),
('04/05/2019','APPLE ',10),
('04/05/2019','Grapes',300),
('04/05/2019','Fish ',120),
('05/05/2019','APPLE ',12),
('05/05/2019','Fish ',130)) V([date],[Type],Quantity);
GO
--SELECT *
--FROM dbo.MyTable;
GO
--Create a calendar table
WITH N AS (
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3), --1000 days shuld be enough
Dates AS(
SELECT DATEADD(DAY, T.I, MIN(MT.[date])) AS [Date]
FROM Tally T
CROSS JOIN dbo.MyTable MT
GROUP BY T.I
HAVING DATEADD(DAY, T.I, MIN(MT.[date])) <= MAX([Date])),
--Get Types
Types AS (
SELECT DISTINCT [Type]
FROM dbo.MyTable MT),
--Create islands
Grps AS(
SELECT MT.ID,
D.[Date],
T.[Type],
MT.Quantity,
COUNT(MT.Quantity) OVER (PARTITION BY T.[Type] ORDER BY D.[date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Grp
FROM Dates D
CROSS JOIN Types T
LEFT JOIN dbo.MyTable MT ON D.[Date] = MT.[date]
AND T.[type] = MT.[type])
SELECT G.ID AS ID,
ROW_NUMBER() OVER (ORDER BY G.[Date], G.[Type]) AS RN,
G.[Date],
G.[Type],
MAX(G.Quantity) OVER (PARTITION BY G.[Type], G.Grp) AS Quantity
FROM Grps G
ORDER BY G.[Date],
G.[Type];
GO
DROP TABLE dbo.MyTable;
db<>fiddle
I think using cursor is a good option to insert your missing entries in the table. By cursor you will be able to check date wise missing types and insert them with the previous quantity.
You can also use this following script to find the missing records in your table. To create the script I consider the table name = 'add_missing_records'
SELECT AA.date AS [Date],
AA.type AS [Type],
BB.quantity AS [Original Quantity] ,
CASE
WHEN BB.quantity IS NULL THEN
(
SELECT quantity
FROM add_missing_records C
WHERE C.date = (
SELECT MAX([date])
FROM add_missing_records B
WHERE B.date < AA.date
AND B.type = AA.type
)
AND C.type = AA.type
)
ELSE BB.quantity
END AS [New Quantuty]
FROM (
SELECT date,type
FROM (
SELECT DISTINCT 'A' AS common,date
FROM add_missing_records
)A
FULL JOIN (
SELECT DISTINCT 'A' as common, type
FROM add_missing_records
)B
ON a.common = b.common
) AA
LEFT JOIN add_missing_records BB
ON AA.date = BB.date
AND AA.type = BB.type
WHERE BB.quantity IS NULL
ORDER BY 1,2

Select Distinct on one column and eliminate nulls in Select Distinct?

follow this question
I have...
ID SKU PRODUCT
=======================
1 FOO-23 Orange
2 BAR-23 Orange
3 FOO-24 Apple
4 FOO-25 Orange
5 FOO-25 null
6 FOO-25 null
expected result:
1 FOO-23 Orange
3 FOO-24 Apple
5 FOO-25 null
6 FOO-25 null
This query isn't getting me there. How can I SELECT DISTINCT on just one column and eliminate null in SELECT DISTINCT?
SELECT *
FROM (SELECT ID, SKU, Product,
ROW_NUMBER() OVER (PARTITION BY PRODUCT ORDER BY ID) AS RowNumber
FROM MyTable
WHERE SKU LIKE 'FOO%') AS a
WHERE a.RowNumber = 1
Perhaps one approach is using the WITH TIES in concert with a conditional PARTITION
Example
Declare #YourTable Table ([ID] int,[SKU] varchar(50),[PRODUCT] varchar(50))
Insert Into #YourTable Values
(1,'FOO-23','Orange')
,(2,'BAR-23','Orange')
,(3,'FOO-24','Apple')
,(4,'FOO-25','Orange')
,(5,'FOO-25',NULL)
,(6,'FOO-25',NULL)
Select top 1 with ties *
From #YourTable
Where SKU Like 'FOO%'
Order By Row_Number() over (Partition By IsNull(Product,NewID()) Order By ID)
Returns
ID SKU PRODUCT
6 FOO-25 NULL
5 FOO-25 NULL
3 FOO-24 Apple
1 FOO-23 Orange
Using John Cappelletti's sample data here is another approach. All you really needed was to add the OR predicate to your where clause.
Declare #YourTable Table ([ID] int,[SKU] varchar(50),[PRODUCT] varchar(50))
Insert Into #YourTable Values
(1,'FOO-23','Orange')
,(2,'BAR-23','Orange')
,(3,'FOO-24','Apple')
,(4,'FOO-25','Orange')
,(5,'FOO-25',NULL)
,(6,'FOO-25',NULL)
SELECT *
FROM
(
SELECT ID
, SKU
, Product
, ROW_NUMBER() OVER (PARTITION BY PRODUCT ORDER BY ID) AS RowNumber
FROM #YourTable
WHERE SKU LIKE 'FOO%'
) AS a
WHERE a.RowNumber = 1
OR a.PRODUCT IS NULL --This was the only part you were missing
I changed your row_number to dense rank:
Declare #YourTable Table ([ID] int,[SKU] varchar(50),[PRODUCT] varchar(50))
Insert Into #YourTable Values
(1,'FOO-23','Orange')
,(2,'BAR-23','Orange')
,(3,'FOO-24','Apple')
,(4,'FOO-25','Orange')
,(5,'FOO-25',NULL)
,(6,'FOO-25',NULL)
SELECT *
FROM (SELECT ID, SKU, Product,
Dense_RANK() OVER (PARTITION BY SKU ORDER BY Product) AS RowNumber
FROM #YourTable
WHERE left(SKU,3) = 'FOO') AS a
WHERE a.RowNumber = 1
Results:
ID SKU Product RowNumber
1 FOO-23 Orange 1
3 FOO-24 Apple 1
5 FOO-25 NULL 1
6 FOO-25 NULL 1

SQL combine multiple rows into 1 row

I am trying to take rows with the same ID and return them on the same row. My data looks like the follow:
ID Fruit
1 Banana
1 Apple
1 Grapefruit
2 Cherry
2 Blueberry
3 Lime
3 Pear
And I would like it to look like this:
ID Fruit Fruit1 Fruit2
1 Banana Apple Grapefruit
2 Cherry Blueberry NULL
I have tried this as a query, but I don't seem to be having much luck:
SELECT a.[ID],a.[Fruit],b.[Fruit]
FROM [test].[dbo].[Fruit] a
JOIN [test].[dbo].[Fruit] b
ON a.ID = b.ID
WHERE a.FRUIT <> b.FRUIT
Can anybody help with this?
Thanks!
If the fruit count is not fixed, you can you use dynamic script:
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
CREATE TABLE #t(ID INT,Fruit VARCHAR(100))
INSERT INTO #t(ID,Fruit)
SELECT 1,'Banana' UNION
SELECT 1,'Apple' UNION
SELECT 1,'Grapefruit' UNION
SELECT 2,'Cherry' UNION
SELECT 2,'Blueberry' UNION
SELECT 3,'Lime' UNION
SELECT 3,'Pear'
DECLARE #sql NVARCHAR(max),#cols VARCHAR(max)
SELECT #cols=ISNULL(#cols+',','')+t.col FROM (
SELECT *,'Fruit'+LTRIM(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY(SELECT 1) )) AS col FROM #t AS t
) AS t GROUP BY t.col
SET #sql='
SELECT * FROM (
SELECT *,''Fruit''+LTRIM(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY(SELECT 1) )) AS col FROM #t AS t
) AS t PIVOT(MAX(Fruit) FOR col in ('+#cols+')) p
'
PRINT #sql
EXEC(#sql)
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
ID Fruit1 Fruit2 Fruit3
----------- ---------- ---------- ----------
1 Apple Banana Grapefruit
2 Blueberry Cherry NULL
3 Lime Pear NULL
You can use combination of a windowing function like row_number and then some conditional aggregation using a CASE expression with MAX() to get the result that you want:
select
Id,
Fruit = max(case when rn = 1 then Fruit end),
Fruit1 = max(case when rn = 2 then Fruit end),
Fruit2 = max(case when rn = 3 then Fruit end)
from
(
select
Id,
Fruit,
rn = row_number() over(partition by Id order by Id)
from [test].[dbo].[Fruit]
) d
group by Id;
See a Demo. The row_number() function creates a unique number for each id, then using this number along with CASE and MAX you will convert your rows of data into columns.
you can use pivot to do this as below:
Select Id, [0] as Fruit, [1] as [Fruit1], [2] as [Fruit2] from (
Select *, RowN = Row_Number() over (partition by Id order by Fruit) - 1 from yourtable )
pivot ( max(Fruit) for RowN in ([0], [1],[2]) ) p

T-SQL : how to select row with multiple conditions

I have below data set in SQL Server and I need to select the data with conditions in order below:
First, check to see if date_end is 1/1/2099, then select the row that has smallest days gap and skill_group is not SWAT for rows have same employee_id, in this case that is row 2.
Second, for rows that do not have 1/1/2099 date_end, select row that has most recent day date_end, in this case it's row 4.
ID employee_id last_name first_name date_start date_end skill_group
---------------------------------------------------------------------------
1 N05E0F Mike Pamela 12/19/2013 1/1/2099 SWAT
2 N05E0F Mike Pamela 9/16/2015 1/1/2099 Welcome Team
3 NSH8A David Smith 12/19/2013 9/16/2016 Unlicensed
4 NSH8A David Smith 8/16/2015 10/16/2016 CMT
There are many ways to do this. Here are some of them:
top with ties version:
select top 1 with ties
*
from tbl
where skill_group != 'SWAT'
order by
row_number() over (
partition by employee_id
order by date_end desc, datediff(day,date_start,date_end) asc
)
with common_table_expression as () using row_number() version:
with cte as (
select *
, rn = row_number() over (
partition by employee_id
order by date_end desc, datediff(day,date_start,date_end) asc
)
from tbl
where skill_group != 'SWAT'
)
select *
from cte
where rn = 1

how to to get first top 6 records indifferent columns T-sql?

I got a situation to display first top 6 records. first 3 records in FirstCol and next 3 in SecondCol. My query is like this:
select top 6 [EmpName]
from [Emp ]
order by [Salary] Desc
Result:
[EmpName]
----------------------
Sam
Pam
Oliver
Jam
Kim
Nixon
But I want the result to look like this:
FirstCol SecondCol
Sam Jam
Pam Kim
Oliver Nixon
; WITH TOP_3 AS
(
select TOP 3 [EmpName]
,ROW_NUMBER() OVER (ORDER BY [Salary] Desc) rn
from [Emp ]
order by [Salary] Desc
),
Other3 AS
(
SELECT [EmpName]
,ROW_NUMBER() OVER (ORDER BY [Salary] Desc) rn
FROM Employees
ORDER BY [Salary] DESC OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY
)
SELECT T3.[EmpName] , O3.[EmpName]
FROM TOP_3 T3 INNER JOIN Other3 O3
ON T3.RN = O3.RN
ORDER BY T3.RN ASC
You can do this using several windowing functions, this is kind of ugly but it will get you the result that you want:
;with data as
(
-- get your Top 6
select top 6 empname, salary
from emp
order by salary desc
),
buckets as
(
-- use NTILE to split the six rows into 2 buckets
select empname,
nt = ntile(2) over(order by salary desc),
salary
from data
)
select
FirstCol = max(case when nt = 1 then empname end),
SecondCol = max(case when nt = 2 then empname end)
from
(
-- create a row number for each item in the buckets to return multiple rows
select empname,
nt,
rn = row_number() over(partition by nt order by salary desc)
from buckets
) d
group by rn;
See SQL Fiddle with Demo. This uses the function NTILE, this takes your dataset of six rows and splits it into two buckets - 3 rows in bucket 1 and 3 rows in bucket 2. The (2) inside the NTILE is used to determine the number of buckets.
Next I used row_number() to create a unique value for each row within each bucket, this allows you to return multiple rows for each column.

Resources