Grouping Data by Changing Status Over Time - sql-server

I am trying to assign a group number to distinct groups of rows in a dataset that has changing data over time. The changing fields are tran_seq, prog_id, deg-id, cur_id, and enroll_status in my example. When any of those fields are different from the previous row, I need a new grouping number. When the fields are the same as the prior row, then the grouping number should stay the same. When I try ROW_NUMBER(), RANK(), or DENSE_RANK(), I get increasing values for the same group (e.g. the first 2 rows in example). I feel I need to ORDER BY start_date as it is temporal data.
+----+----------+---------+--------+--------+---------------+------------+------------+---------+
| | tran_seq | prog_id | deg_id | cur_id | enroll_status | start_date | end_date | desired |
+----+----------+---------+--------+--------+---------------+------------+------------+---------+
| 1 | 1 | 6 | 9 | 3 | ENRL | 2004-08-22 | 2004-12-11 | 1 |
| 2 | 1 | 6 | 9 | 3 | ENRL | 2006-01-10 | 2006-05-06 | 1 |
| 3 | 1 | 6 | 9 | 59 | ENRL | 2006-08-29 | 2006-12-16 | 2 |
| 4 | 2 | 12 | 23 | 45 | ENRL | 2014-01-21 | 2014-05-16 | 3 |
| 5 | 2 | 12 | 23 | 45 | ENRL | 2014-08-18 | 2014-12-05 | 3 |
| 6 | 2 | 12 | 23 | 45 | LOAP | 2015-01-20 | 2015-05-15 | 4 |
| 7 | 2 | 12 | 23 | 45 | ENRL | 2015-08-25 | 2015-12-11 | 5 |
| 8 | 2 | 12 | 23 | 45 | LOAP | 2016-01-12 | 2016-05-06 | 6 |
| 9 | 2 | 12 | 23 | 45 | ENRL | 2016-05-16 | 2016-08-05 | 7 |
| 10 | 2 | 12 | 23 | 45 | LOAJ | 2016-08-23 | 2016-12-02 | 8 |
| 11 | 2 | 12 | 23 | 45 | ENRL | 2017-01-18 | 2017-05-05 | 9 |
| 12 | 2 | 12 | 23 | 45 | ENRL | 2018-01-17 | 2018-05-11 | 9 |
+----+----------+---------+--------+--------+---------------+------------+------------+---------+
Once I have grouping numbers, I think I can group by those to get what I'm ultimately after: a timeline of different statuses with start dates and end dates. For the example data above, that would be:
+---+----------+---------+--------+--------+---------------+------------+------------+
| | tran_seq | prog_id | deg_id | cur_id | enroll_status | start_date | end_date |
+---+----------+---------+--------+--------+---------------+------------+------------+
| 1 | 1 | 6 | 9 | 3 | ENRL | 2004-08-22 | 2006-05-06 |
| 2 | 1 | 6 | 9 | 59 | ENRL | 2004-08-29 | 2006-12-16 |
| 3 | 2 | 12 | 23 | 45 | ENRL | 2014-01-21 | 2014-12-05 |
| 4 | 2 | 12 | 23 | 45 | LOAP | 2015-01-20 | 2015-05-15 |
| 5 | 2 | 12 | 23 | 45 | ENRL | 2015-08-25 | 2015-12-11 |
| 6 | 2 | 12 | 23 | 45 | LOAP | 2016-01-12 | 2016-05-06 |
| 7 | 2 | 12 | 23 | 45 | ENRL | 2016-05-16 | 2016-08-05 |
| 8 | 2 | 12 | 23 | 45 | LOAJ | 2016-08-23 | 2016-12-02 |
| 9 | 2 | 12 | 23 | 45 | ENRL | 2017-01-17 | 2018-05-06 |
+---+----------+---------+--------+--------+---------------+------------+------------+

This is a classic XY problem, in that you are asking for an intermediate step to a different solution, rather than asking about the solution itself.
As you included your overall end goal as a bit of an addendum however, here is how you can reach that without your intermediate step:
declare #t table(tran_seq int, prog_id int, deg_id int, cur_id int, enroll_status varchar(4), start_date date, end_date date, desired int)
insert into #t values
(1,6,9,3 ,'ENRL','2004-08-22','2004-12-11',1)
,(1,6,9,3 ,'ENRL','2006-01-10','2006-05-06',1)
,(1,6,9,59 ,'ENRL','2006-08-29','2006-12-16',2)
,(2,12,23,45,'ENRL','2014-01-21','2014-05-16',3)
,(2,12,23,45,'ENRL','2014-08-18','2014-12-05',3)
,(2,12,23,45,'LOAP','2015-01-20','2015-05-15',4)
,(2,12,23,45,'ENRL','2015-08-25','2015-12-11',5)
,(2,12,23,45,'LOAP','2016-01-12','2016-05-06',6)
,(2,12,23,45,'ENRL','2016-05-16','2016-08-05',7)
,(2,12,23,45,'LOAJ','2016-08-23','2016-12-02',8)
,(2,12,23,45,'ENRL','2017-01-18','2017-05-05',9)
,(2,12,23,45,'ENRL','2018-01-17','2018-05-11',9)
;
select tran_seq
,prog_id
,deg_id
,cur_id
,enroll_status
,min(start_date) as start_date
,max(end_date) as end_date
from(select *
,row_number() over (order by end_date) - row_number() over (partition by tran_seq,prog_id,deg_id,cur_id,enroll_status order by end_date) as grp
from #t
) AS g
group by tran_seq
,prog_id
,deg_id
,cur_id
,enroll_status
,grp
order by start_date;
Output
+----------+---------+--------+--------+---------------+------------+------------+
| tran_seq | prog_id | deg_id | cur_id | enroll_status | start_date | end_date |
+----------+---------+--------+--------+---------------+------------+------------+
| 1 | 6 | 9 | 3 | ENRL | 2004-08-22 | 2006-05-06 |
| 1 | 6 | 9 | 59 | ENRL | 2006-08-29 | 2006-12-16 |
| 2 | 12 | 23 | 45 | ENRL | 2014-01-21 | 2014-12-05 |
| 2 | 12 | 23 | 45 | LOAP | 2015-01-20 | 2015-05-15 |
| 2 | 12 | 23 | 45 | ENRL | 2015-08-25 | 2015-12-11 |
| 2 | 12 | 23 | 45 | LOAP | 2016-01-12 | 2016-05-06 |
| 2 | 12 | 23 | 45 | ENRL | 2016-05-16 | 2016-08-05 |
| 2 | 12 | 23 | 45 | LOAJ | 2016-08-23 | 2016-12-02 |
| 2 | 12 | 23 | 45 | ENRL | 2017-01-18 | 2018-05-11 |
+----------+---------+--------+--------+---------------+------------+------------+

Related

SQL I want to figure out how to get sales 10 days for each product and within each group

I have a SQL Server table T1 that has orders by product_id, brand, and size for each day.
T1:
+------------+-------+------+----------+--------+
| product_id | Brand | Size | Date | Orders |
+------------+-------+------+----------+--------+
| 1 | 1 | 11 | 10/18/18 | 1 |
| 1 | 1 | 6 | 10/18/18 | 2 |
| 1 | 1 | 10 | 10/18/18 | 1 |
| 1 | 1 | 7 | 10/18/18 | 3 |
| 1 | 1 | 8.5 | 10/18/18 | 5 |
| 1 | 1 | 9.5 | 10/18/18 | 2 |
| 2 | 1 | 8 | 10/19/18 | 3 |
| 2 | 1 | 7 | 10/19/18 | 6 |
| 2 | 1 | 9 | 10/19/18 | 2 |
| 3 | 2 | 5 | 10/19/18 | 23 |
| 3 | 2 | 6 | 10/19/18 | 6 |
| 3 | 2 | 10 | 10/19/18 | 7 |
+------------+-------+------+----------+--------+
I also have a table, T2, that has the launch date for each product_id. A product_id may have more than one launch dates, signifying it is "restocked".
T2:
+------------+-------------+
| product_id | launch_date |
+------------+-------------+
| 1 | 8/18/18 |
| 1 | 10/18/18 |
| 2 | 10/18/18 |
| 3 | 4/18/18 |
+------------+-------------+
My goal is to create a table that is just the first 10 days of orders in each launch date (for each product_id, brand, and size). So if launch date for product 1 is 8/18/18 and 10/18/18, then I want the daily orders from 8/18/18 to 8/28/18, and from 10/18/18 to 10/28/18.
How would I go about creating this table?
Example output:
+------------+-------+------+----------+--------+
| product_id | Brand | Size | Date | Orders |
+------------+-------+------+----------+--------+
| 1 | 1 | 11 | 10/18/18 | 1 |
| 1 | 1 | 6 | 10/18/18 | 2 |
| 1 | 1 | 10 | 10/18/18 | 1 |
| 1 | 1 | 7 | 10/18/18 | 3 |
| 1 | 1 | 8.5 | 10/18/18 | 5 |
| 1 | 1 | 9.5 | 10/18/18 | 2 |
| … | | | | |
| 1 | 1 | 11 | 10/22/18 | 4 |
| 1 | 1 | 6 | 10/22/18 | 6 |
| 1 | 1 | 10 | 10/22/18 | 2 |
| 1 | 1 | 7 | 10/22/18 | 2 |
| 1 | 1 | 8.5 | 10/22/18 | 2 |
| 1 | 1 | 9.5 | 10/22/18 | 5 |
| … | | | | |
| 1 | 1 | 11 | 10/28/18 | 7 |
| 1 | 1 | 6 | 10/28/18 | 4 |
| 1 | 1 | 10 | 10/28/18 | 2 |
| 1 | 1 | 7 | 10/28/18 | 2 |
| 1 | 1 | 8.5 | 10/28/18 | 8 |
| 1 | 1 | 9.5 | 10/28/18 | 7 |
| … | | | | |
| 2 | 1 | 8 | 10/19/18 | 3 |
| 2 | 1 | 7 | 10/19/18 | 6 |
| 2 | 1 | 9 | 10/19/18 | 2 |
| 3 | 2 | 5 | 10/19/18 | 23 |
| 3 | 2 | 6 | 10/19/18 | 6 |
| 3 | 2 | 10 | 10/19/18 | 7 |
+------------+-------+------+----------+--------+
Thank you!
EDIT: including what I have tried so far:
My thought process is to try to create to join the launch_date and then create a column that is the number of days between the launch date and the Date of order. Then I can just filter for WHERE that column is less than or equal to 10.
This is the query I am using:
with temp as (
select
t1.product_id, t1.brand, t1.size, t1.date, t1.orders, t2.launch_date
from t1
left join t2 on t1.product_id = t2.product_id and t1.order_date = t2.order_date
)
select product_id,
brand,
size,
size,
date,
orders,
launch_date
from temp
;
In order for my reasoning to work, I would need to forward-fill the launch_date wherever it is null. I am not sure how to accomplish this. Here is the output I have so far:
+------------+-------+------+----------+--------+-------------+
| product_id | Brand | Size | Date | Orders | launch_date |
+------------+-------+------+----------+--------+-------------+
| 1 | 1 | 11 | 10/18/18 | 1 | 10/18/18 |
| 1 | 1 | 6 | 10/18/18 | 2 | 10/18/18 |
| 1 | 1 | 10 | 10/18/18 | 1 | 10/18/18 |
| 1 | 1 | 7 | 10/18/18 | 3 | 10/18/18 |
| 1 | 1 | 8.5 | 10/18/18 | 5 | 10/18/18 |
| 1 | 1 | 9.5 | 10/18/18 | 2 | 10/18/18 |
| … | | | | | |
| 1 | 1 | 11 | 10/22/18 | 4 | NULL |
| 1 | 1 | 6 | 10/22/18 | 6 | NULL |
| 1 | 1 | 10 | 10/22/18 | 2 | NULL |
| 1 | 1 | 7 | 10/22/18 | 2 | NULL |
| 1 | 1 | 8.5 | 10/22/18 | 2 | NULL |
| 1 | 1 | 9.5 | 10/22/18 | 5 | NULL |
| … | | | | | |
| 1 | 1 | 11 | 10/28/18 | 7 | NULL |
| 1 | 1 | 6 | 10/28/18 | 4 | NULL |
| 1 | 1 | 10 | 10/28/18 | 2 | NULL |
| 1 | 1 | 7 | 10/28/18 | 2 | NULL |
| 1 | 1 | 8.5 | 10/28/18 | 8 | NULL |
| 1 | 1 | 9.5 | 10/28/18 | 7 | NULL |
| … | | | | | |
| 2 | 1 | 8 | 10/19/18 | 3 | 10/18/18 |
| 2 | 1 | 7 | 10/19/18 | 6 | 10/18/18 |
| 2 | 1 | 9 | 10/19/18 | 2 | 10/18/18 |
| 3 | 2 | 5 | 10/19/18 | 23 | 10/18/18 |
| 3 | 2 | 6 | 10/19/18 | 6 | 10/18/18 |
| 3 | 2 | 10 | 10/19/18 | 7 | 10/18/18 |
+------------+-------+------+----------+--------+-------------+
If I can forward-fill the launch_date wherever it is NULL to be the most recent launch_date of that product_id, then I would be able to create a column to subtract the dates.

How to make a pivot table in DB2?

I have a table be like:
| Date | Week | Name | No | Count |
|-----------|------|--------|----|-------|
| 2019/4/1 | 14 | John | 1 | 1 |
| 2019/4/1 | 14 | Mary | 2 | 1 |
| 2019/4/9 | 15 | Kevin | 3 | 2 |
| 2019/4/9 | 15 | John | 4 | 1 |
| 2019/4/9 | 15 | Jessie | 5 | 1 |
| 2019/4/18 | 16 | Kevin | 6 | 1 |
| 2019/4/18 | 16 | John | 7 | 1 |
| 2019/4/18 | 16 | Jessie | 8 | 2 |
| 2019/4/18 | 16 | Mary | 9 | 3 |
| 2019/4/18 | 16 | Mary | 10 | 1 |
| 2019/4/18 | 16 | Jessie | 11 | 1 |
| 2019/4/24 | 17 | Mary | 12 | 1 |
| 2019/4/24 | 17 | Jessie | 13 | 1 |
What I want to do is to calculate people's total count per Week.
And sort by their total count.
I know GROUP BY can make this happen, I've tried, but just can't figure it out.
This is what I expect:
| Name | 14 | 15 | 16 | 17 | Total |
|--------|----|----|----|----|-------|
| Mary | 1 | 0 | 4 | 1 | 6 |
| Jessie | 0 | 1 | 3 | 1 | 5 |
| John | 1 | 1 | 1 | 0 | 3 |
| Kevin | 0 | 2 | 1 | 0 | 3 |
| Total | 2 | 4 | 9 | 2 | 17 |
How can I do?
Select [Name]
,sum(case when [Week] = 14 then [Count] else 0 end) as Week14
,sum(case when [Week] = 15 then [Count] else 0 end) as Week15
,sum(case when [Week] = 16 then [Count] else 0 end) as Week16
,sum(case when [Week] = 17 then [Count] else 0 end) as Week17
,sum([Count]) as Total
from [table]
group by [Name]
order by Total
I'm not sure which version of DB2 you're using (LUW/zOS/i) so this is a general answer. The week number can be made to be more flexible but a certain amount of hard coding will need to be done for the number of weeks.

Sum, Group by and Null

I'm dipping my toes into SQL. I have the following table
+------+----+------+------+-------+
| Type | ID | QTY | Rate | Name |
+------+----+------+------+-------+
| B | 1 | 1000 | 21 | Jack |
| B | 2 | 2000 | 12 | Kevin |
| B | 1 | 3000 | 24 | Jack |
| B | 1 | 1000 | 23 | Jack |
| B | 3 | 200 | 13 | Mary |
| B | 2 | 3000 | 12 | Kevin |
| B | 4 | 4000 | 44 | Chris |
| B | 4 | 5000 | 43 | Chris |
| B | 3 | 1000 | 26 | Mary |
+------+----+------+------+-------+
I don't know how I would leverage Sum and Group by to achieve the following result.
+------+----+------+------+-------+------------+
| Type | ID | QTY | Rate | Name | Sum of QTY |
+------+----+------+------+-------+------------+
| B | 1 | 1000 | 21 | Jack | 5000 |
| B | 1 | 3000 | 24 | Jack | Null |
| B | 1 | 1000 | 23 | Jack | Null |
| B | 2 | 3000 | 12 | Kevin | 5000 |
| B | 2 | 3000 | 12 | Kevin | Null |
| B | 3 | 200 | 13 | Mary | 1200 |
| B | 3 | 1000 | 26 | Mary | Null |
| B | 4 | 4000 | 44 | Chris | 9000 |
| B | 4 | 5000 | 43 | Chris | Null |
+------+----+------+------+-------+------------+
Any help is appreciated!
You can use window function :
select t.*,
(case when row_number() over (partition by type, id order by name) = 1
then sum(qty) over (partition by type, id order by name)
end) as Sum_of_QTY
from table t;

Trying to create a parent-child hierarchy in sql server

I have a data set that is sorted by account key. when coming to certain rows which have NULL as group key, it should add current account key as a parent key for all above rows which have NULL as parentkey. So when coming to the next row with null as group key, you would set the current account key as parent key for all rows above that have parent key like null.
I have tried to copy a dataset below as mark down table but as you see I can't say I succeeded very well, but I hope some of you can help with the t-sql syntax to create a parent-child hierarchy of this
| AccountKey | ParentKey | GroupKey | AccountNumber | Cat | LineName | LineId |
|------------|-----------|----------|---------------|----------|------------------------------|--------------------------------------|
| 1 | NULL | 7 | 3040 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 2 | NULL | 7 | 3041 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 3 | NULL | 7 | 3081 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 4 | NULL | 7 | 3082 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 5 | NULL | 7 | 3083 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 6 | NULL | 7 | 3085 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 7 | NULL | 7 | 3086 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 8 | NULL | 7 | 3087 | Account | Salg fisk | C6BCDFB2-1AAC-4D05-94F1-879CDC615D76 |
| 9 | NULL | 2 | 3000 | Account | Salg annet | 26AC86B2-0667-463E-B994-11A5C6D519A6 |
| 10 | NULL | 2 | 3010 | Account | Salg annet | 26AC86B2-0667-463E-B994-11A5C6D519A6 |
| 11 | NULL | 2 | 3020 | Account | Salg annet | 26AC86B2-0667-463E-B994-11A5C6D519A6 |
| 12 | NULL | 2 | 3030 | Account | Salg annet | 26AC86B2-0667-463E-B994-11A5C6D519A6 |
| 41 | NULL | 11 | 3050 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 42 | NULL | 11 | 3600 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 43 | NULL | 11 | 3601 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 44 | NULL | 11 | 3610 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 45 | NULL | 11 | 3615 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 46 | NULL | 11 | 3690 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 47 | NULL | 11 | 3691 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 48 | NULL | 11 | 3701 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 49 | NULL | 11 | 3705 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 50 | NULL | 11 | 3720 | Account | Andre driftsinntekter | 65FFB620-AE42-4BE5-A6E7-BF3339AA04DF |
| 67 | NULL | NULL | NULL | SubTotal | Sum inntekter | NULL |
| 68 | NULL | 13 | 4120 | Account | Innkjøp smolt/settefisk/rogn | F9EE1CE4-22C7-400B-BC9D-E2D3214A5113 |
| 69 | NULL | 10 | 4010 | Account | Vareforbruk fôr | 04E63B6D-CA54-423D-8A44-A4ED99861975 |
| 70 | NULL | 10 | 4901 | Account | Vareforbruk fôr | 04E63B6D-CA54-423D-8A44-A4ED99861975 |
| 71 | NULL | 3 | 4000 | Account | Andre varekostnader | DB7FABAB-7ABA-4B9A-9720-1B538D99B3C8 |
| 72 | NULL | 3 | 4020 | Account | Andre varekostnader | DB7FABAB-7ABA-4B9A-9720-1B538D99B3C8 |
| 73 | NULL | 3 | 4030 | Account | Andre varekostnader | DB7FABAB-7ABA-4B9A-9720-1B538D99B3C8 |
| 133 | NULL | 8 | 4925 | Account | Beholdningsendring fisk | A8BA6F19-A792-44A1-AA21-8F79DB24D224 |
| 134 | NULL | NULL | NULL | SubTotal | Sum varekostnader | NULL |
| 135 | NULL | 12 | 5000 | Account | Lønn og sosiale kostnader | 5C475EDE-3731-4D39-B11A-C8EE72213FF6 |
| 136 | NULL | 12 | 5001 | Account | Lønn og sosiale kostnader | 5C475EDE-3731-4D39-B11A-C8EE72213FF6 |
| 137 | NULL | 12 | 5005 | Account | Lønn og sosiale kostnader | 5C475EDE-3731-4D39-B11A-C8EE72213FF6 |
| 138 | NULL | 12 | 5009 | Account | Lønn og sosiale kostnader | 5C475EDE-3731-4D39-B11A-C8EE72213FF6 |
| 263 | NULL | NULL | NULL | SubTotal | Sum lønnskostnadern | NULL |
| 462 | NULL | NULL | NULL | SubTotal | RESULTAT ETTER SKATT | NULL
If I understood your question you could use:
SELECT Accountkey, ParentKey,GroupKey,AccountNumber, NEW_PARID
FROM (
SELECT Accountkey, ParentKey,GroupKey,AccountNumber, AccountKey AS NEW_PARID, LAG(ACCOUNTKEY) OVER (ORDER BY Accountkey) AS PREC
FROM MYT
WHERE GroupKey IS NULL
UNION ALL
SELECT A.Accountkey, A.ParentKey,A.GroupKey,A.AccountNumber, B.Accountkey AS NEW_PARID, B.PREC
FROM MYT A
INNER JOIN ( SELECT Accountkey, ParentKey,GroupKey,AccountNumber, AccountKey AS NEW_PARID, LAG(ACCOUNTKEY) OVER (ORDER BY Accountkey) AS PREC
FROM MYT
WHERE GroupKey IS NULL) B ON A.Accountkey < B.Accountkey AND (B.PREC IS NULL OR B.PREC<A.accountKey)
WHERE A.GroupKey IS NOT NULL
AND B.GroupKey IS NULL
) X ORDER BY ACCOUNTKEY
You can write it in this way too (it's the same query):
WITH X AS (SELECT Accountkey, ParentKey,GroupKey,AccountNumber, AccountKey AS NEW_PARID, LAG(ACCOUNTKEY) OVER (ORDER BY Accountkey) AS PREC
FROM MYT
WHERE GroupKey IS NULL)
SELECT X.*
FROM X
UNION ALL
SELECT A.Accountkey, A.ParentKey,A.GroupKey,A.AccountNumber, X.Accountkey AS NEW_PARID, X.PREC
FROM MYT A
INNER JOIN X ON A.Accountkey < X.Accountkey AND (X.PREC IS NULL OR X.PREC<A.accountKey)
WHERE A.GroupKey IS NOT NULL
Output (MYT is the name of the table, the new parentid column is NEW_PARID):
+------------+-----------+----------+---------------+-----------+
| Accountkey | ParentKey | GroupKey | AccountNumber | NEW_PARID |
+------------+-----------+----------+---------------+-----------+
| 1 | NULL | 7 | 3040 | 67 |
| 2 | NULL | 7 | 3041 | 67 |
| 3 | NULL | 7 | 3081 | 67 |
| 4 | NULL | 7 | 3082 | 67 |
| 5 | NULL | 7 | 3083 | 67 |
| 6 | NULL | 7 | 3085 | 67 |
| 7 | NULL | 7 | 3086 | 67 |
| 8 | NULL | 7 | 3087 | 67 |
| 9 | NULL | 2 | 3000 | 67 |
| 10 | NULL | 2 | 3010 | 67 |
| 11 | NULL | 2 | 3020 | 67 |
| 12 | NULL | 2 | 3030 | 67 |
| 41 | NULL | 11 | 3050 | 67 |
| 42 | NULL | 11 | 3600 | 67 |
| 43 | NULL | 11 | 3601 | 67 |
| 44 | NULL | 11 | 3610 | 67 |
| 45 | NULL | 11 | 3615 | 67 |
| 46 | NULL | 11 | 3690 | 67 |
| 47 | NULL | 11 | 3691 | 67 |
| 48 | NULL | 11 | 3701 | 67 |
| 49 | NULL | 11 | 3705 | 67 |
| 50 | NULL | 11 | 3720 | 67 |
| 67 | NULL | NULL | NULL | 67 |
| 68 | NULL | 13 | 4120 | 134 |
| 69 | NULL | 10 | 4010 | 134 |
| 70 | NULL | 10 | 4901 | 134 |
| 71 | NULL | 3 | 4000 | 134 |
| 72 | NULL | 3 | 4020 | 134 |
| 73 | NULL | 3 | 4030 | 134 |
| 133 | NULL | 8 | 4925 | 134 |
| 134 | NULL | NULL | NULL | 134 |
| 135 | NULL | 12 | 5000 | 263 |
| 136 | NULL | 12 | 5001 | 263 |
| 137 | NULL | 12 | 5005 | 263 |
| 138 | NULL | 12 | 5009 | 263 |
| 263 | NULL | NULL | NULL | 263 |
| 462 | NULL | NULL | NULL | 462 |
+------------+-----------+----------+---------------+-----------+
Updated 20171221 - for MSSQL 2008
You can try this (but pay attention for performance if you have a large dataset):
SELECT A.ACCOUNTKEY
, A.PARENTKEY
, (SELECT MIN(B.ACCOUNTKEY) FROM MYT B WHERE B.GROUPKEY IS NULL AND A.ACCOUNTKEY<=B.ACCOUNTKEY) AS NEW_PARID
FROM MYT A
/* WHERE A.GROUPKEY IS NOT NULL*/
This is the code I need to rewrite to work on SQL Server 2008
SELECT AccountKey,
LineName,
AccountName,
GroupKey,
AccountNumber,
ParentAccountKey
INTO tempAccount
FROM
(
SELECT AccountKey,
LineName,
AccountName,
GroupKey,
AccountNumber,
AccountKey AS ParentAccountKey,
LAG(AccountKey) OVER(ORDER BY AccountKey) AS PREC
FROM tempTable2
WHERE GroupKey IS NULL
UNION ALL
SELECT A.AccountKey,
A.LineName,
A.AccountName,
A.GroupKey,
A.AccountNumber,
B.AccountKey AS ParentAccountKey,
B.PREC
FROM tempTable2 A
INNER JOIN
(
SELECT AccountKey,
LineName,
AccountName,
GroupKey,
AccountNumber,
AccountKey AS ParentAccountKey,
LAG(AccountKey) OVER(ORDER BY AccountKey) AS PREC
FROM tempTable2
WHERE GroupKey IS NULL
) B ON A.AccountKey < B.AccountKey
AND (B.PREC IS NULL
OR B.PREC < A.AccountKey)
WHERE A.GroupKey IS NOT NULL
AND B.GroupKey IS NULL
) X
ORDER BY AccountKey;

How do I generate lft and rght values for a cakephp 2.0 tree?

I've read that if you have a model that $actsAs = array('Tree'), that you can simply set the parent_id for your seed data, and a call to $this->Model->recover() should generate the proper lft and rght values for you, but when I do this, Cake seems to generate random values every time. The values vary from very large (in the thousands) to negative values. I've checked for circular references and found none as well. What could be wrong here?
SQL:
create table menus (
id int auto_increment not null,
parent_id int null ,
lft int null ,
rght int null ,
title varchar(1024) not null,
path varchar(1024) not null,
constraint pk_menus primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
This is the seeded data: After one Model->recover()
+----+-----------+------+------+ +----+-----------+------+------+
| id | parent_id | lft | rght | | id | parent_id | lft | rght |
+----+-----------+------+------+ +----+-----------+------+------+
| 1 | NULL | NULL | NULL | | 1 | NULL | 595 | 619 |
| 7 | 1 | NULL | NULL | | 7 | 1 | 619 | 567 |
| 6 | 1 | NULL | NULL | | 6 | 1 | 627 | 595 |
| 5 | 1 | NULL | NULL | | 5 | 1 | 600 | 621 |
| 4 | 1 | NULL | NULL | | 4 | 1 | 603 | 621 |
| 3 | 1 | NULL | NULL | | 3 | 1 | 619 | 529 |
| 2 | 1 | NULL | NULL | | 2 | 1 | 595 | 529 |
| 8 | 2 | NULL | NULL | | 8 | 2 | 627 | 628 |
| 13 | 3 | NULL | NULL | | 13 | 3 | 595 | 567 |
| 12 | 3 | NULL | NULL | | 12 | 3 | 627 | 621 |
| 11 | 3 | NULL | NULL | | 11 | 3 | 595 | 631 |
| 10 | 3 | NULL | NULL | | 10 | 3 | 604 | 529 |
| 9 | 3 | NULL | NULL | | 9 | 3 | 595 | 567 |
| 14 | 5 | NULL | NULL | | 14 | 5 | 628 | 629 |
| 15 | 5 | NULL | NULL | | 15 | 5 | 567 | 529 |
| 16 | 5 | NULL | NULL | | 16 | 5 | 619 | 600 |
| 17 | 5 | NULL | NULL | | 17 | 5 | 627 | 605 |
| 21 | 6 | NULL | NULL | | 21 | 6 | 567 | 619 |
| 20 | 6 | NULL | NULL | | 20 | 6 | 595 | 567 |
| 19 | 6 | NULL | NULL | | 19 | 6 | 619 | 600 |
| 18 | 6 | NULL | NULL | | 18 | 6 | 567 | 529 |
| 22 | 10 | NULL | NULL | | 22 | 10 | 567 | 619 |
| 23 | 11 | NULL | NULL | | 23 | 11 | 64 | 621 |
| 24 | 12 | NULL | NULL | | 24 | 12 | 627 | 621 |
| 25 | 13 | NULL | NULL | | 25 | 13 | 605 | 595 |
| 32 | 16 | NULL | NULL | | 32 | 16 | 628 | 627 |
| 31 | 16 | NULL | NULL | | 31 | 16 | 567 | 619 |
| 30 | 16 | NULL | NULL | | 30 | 16 | 64 | 621 |
| 29 | 16 | NULL | NULL | | 29 | 16 | 567 | 619 |
| 28 | 16 | NULL | NULL | | 28 | 16 | 595 | 567 |
| 27 | 16 | NULL | NULL | | 27 | 16 | 627 | 621 |
| 26 | 16 | NULL | NULL | | 26 | 16 | 605 | 595 |
| 33 | 17 | NULL | NULL | | 33 | 17 | 567 | 619 |
| 37 | 19 | NULL | NULL | | 37 | 19 | 52 | 619 |
| 36 | 19 | NULL | NULL | | 36 | 19 | 52 | 619 |
| 35 | 19 | NULL | NULL | | 35 | 19 | 595 | 619 |
| 34 | 19 | NULL | NULL | | 34 | 19 | 63 | 621 |
| 38 | 20 | NULL | NULL | | 38 | 20 | 63 | 621 |
| 39 | 20 | NULL | NULL | | 39 | 20 | 63 | 621 |
| 40 | 21 | NULL | NULL | | 40 | 21 | 605 | 595 |
+----+-----------+------+------+ +----+-----------+------+------+
I found that if I set the lft and rght values of the top level item to 1 and 2 respectively, recover() now generates the rest of the values properly. I find this to be a bug, but not a terrible one.

Resources