SQL result into 3 bucket by count - sql

=============================
Itemnumber| Check_ind| year
=============================
123 |Y | 2011
456 |Y | 2011
123 |Y | 2012
456 |Y | 2011
456 |Y | 2011
I want to result to be
=====================
1| 2-3| 4
=====================
123| 456 |
I want to count total time that each itemnumber appear in the table and where year=2011, then put it into bucket. my itnitial think was something like :
SELECT case when count(Itemnumber)>=0 and <=1 then '1'
case when count(Itemnumber)>=2 and <=3 then '2-3'
else '4' end
from table where year = '2011'

My guess is maybe there is a better solution using pivot.
While someone find that, here is my solution:
You could handle null with a case to show space if that is a problem.
Sql Fiddle Demo
I include a few more data in the sample, let me know if that is ok.
Have to use FULL JOIN because I don't know what group will have the most items.
.
with item_count AS (
SELECT itemnumber, count(*) as total
FROM item
WHERE year = '2011'
GROUP BY itemnumber
), t_01 AS (
SELECT itemnumber, ROW_NUMBER() OVER(ORDER BY itemnumber) AS row_id
FROM item_count
WHERE total between 0 and 1
), t_02 AS (
SELECT itemnumber, ROW_NUMBER() OVER(ORDER BY itemnumber) AS row_id
FROM item_count
WHERE total between 2 and 3
), t_03 AS (
SELECT itemnumber, ROW_NUMBER() OVER(ORDER BY itemnumber) AS row_id
FROM item_count
WHERE total = 4
)
SELECT t_01.itemnumber as '0-1', t_02.itemnumber as '2-3', t_03.itemnumber as '4'
from
t_01
full join t_02
on t_01.row_id = t_02.row_id
full join t_03
on t_01.row_id = t_03.row_id
I add item 789 and 999 to the data sample

I think this is what you need -
DECLARE #T TABLE ( ItemNumber VARCHAR(5)
,Check_Ind CHAR(1)
,YEAR varchar(4)
)
INSERT INTO #T VALUES ('123','Y','2011')
,('456','Y','2011')
,('123','Y','2012')
,('456','Y','2011')
,('456','Y','2011')
SELECT * FROM #t
SELECT CASE WHEN Count(ItemNumber) < 2 THEN ItemNumber ELSE '' END [0-1]
,CASE WHEN Count(ItemNumber) BETWEEN 2 AND 3 THEN ItemNumber ELSE '' END [2-3]
,CASE WHEN Count(ItemNumber) > 3 THEN ItemNumber ELSE '' END [4]
FROM #T
WHERE YEAR = '2011'
GROUP BY ItemNumber

This question is not well defined so I am somewhat making a guess here. Notice I also am posting ddl and sample data in a consumable format. This makes things a lot easier for the people trying to help.
create table #Something
(
Itemnumber int,
Check_ind char(1),
MyYear int
)
insert #Something
select 123, 'Y', 2011 union all
select 456, 'Y', 2011 union all
select 123, 'Y', 2012 union all
select 456, 'Y', 2011 union all
select 456, 'Y', 2011
--Now add another group for the "2-3" bucket
insert #Something
select 12, 'Y', 2011 union all
select 12, 'Y', 2011;
with GroupSubtotals as
(
select case when COUNT(ItemNumber) < 2 then 1 end as [0-1]
, case when COUNT(ItemNumber) > 1 and COUNT(ItemNumber) < 4 then 1 end as [2-3]
, case when COUNT(ItemNumber) > 3 then 1 end as [4]
from #Something s
where s.MyYear = 2011
group by ItemNumber
)
select SUM([0-1]) as [0-1]
, SUM([2-3]) as [2-3]
, SUM([4]) as [4]
from GroupSubtotals

Related

How to apply pivot to result of query

There is my current query:
SELECT Name, Code, Today
, Account || Currency as Accounts
FROM (
SELECT
b.description AS Name
, b.contragentidentifycode AS Code
, c.systemday AS Today
, b.accountno AS Account
, b.currencysname AS Currency
FROM vAACCOUNT b, currentdaysetting c
WHERE b.contragentid = 412
AND b.accountno LIKE '26%'
)
it gives me such result:
Name | Code | Today | Accounts
---------------------------------------
name1 | code1 | 07.09.2016 | acc1+curr1
name1 | code1 | 07.09.2016 | acc2+curr1
name1 | code1 | 07.09.2016 | acc1+curr2
name1 | code1 | 07.09.2016 | acc2+curr2
name1 | code1 | 07.09.2016 | acc1+curr3
name1 | code1 | 07.09.2016 | acc2+curr3
name1 | code1 | 07.09.2016 | acc1+curr4
name1 | code1 | 07.09.2016 | acc2+curr4
I need convert this view to:
Name | Code | Today | someName1 | someName2 | someName3 | someName4 | someName5 | someName6 | someName7 | someName8
-------------------------------------------------------------------------------------------------------------------------------------------
name1 | code1 | 07.09.2016 | acc1+curr1 | acc2+curr1 | acc1+curr2 | acc2+curr2 | acc1+curr3 | acc2+curr3 | acc1+curr4 | acc2+curr4
I guess that most probably for this I have to use the keyword "Pivot". But all my attempts to do so - have failed. I can not to project what I see in the examples, to my table. Please help.
For number of columns I can add such "id" column:
SELECT id, Name, Code, Today
, Account || Currency as Accounts
FROM (
SELECT
row_number() over (ORDER BY b.id) AS id
, b.description AS Name
...
In my scenario:
numbers of accounts may be different;
name, code and data - one per query;
combination of accaunt+currency are unique;
result should be in one line;
total number of lines in result of query, cannot be more then 10 (in my example 8)
Per my comment above, I don't think PIVOT works for you. The answer from #RoundFour works, but requires that you know, and code for, all possible values for Account || Currency. This suggests there will never be new values for these items - I find that unlikely.
The following will allow you to switch the shape of your data. It makes no assumptions about the values in your data, but it does assume a limit on the number of possible combinations - I have coded for eight.
WITH account_data (name,code,today,account)
AS
(
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr1' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr1' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr2' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr2' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr3' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr3' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr4' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr4' FROM dual UNION ALL
SELECT 'name2','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr1' FROM dual UNION ALL
SELECT 'name2','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr1' FROM dual UNION ALL
SELECT 'name2','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr2' FROM dual UNION ALL
SELECT 'name3','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr2' FROM dual
)
SELECT
name
,code
,today
,MAX(account1)
,MAX(account2)
,MAX(account3)
,MAX(account4)
,MAX(account5)
,MAX(account6)
,MAX(account7)
,MAX(account8)
FROM
(SELECT
name
,code
,today
,CASE
WHEN rn = 1 THEN account
END account1
,CASE
WHEN rn = 2 THEN account
END account2
,CASE
WHEN rn = 3 THEN account
END account3
,CASE
WHEN rn = 4 THEN account
END account4
,CASE
WHEN rn = 5 THEN account
END account5
,CASE
WHEN rn = 6 THEN account
END account6
,CASE
WHEN rn = 7 THEN account
END account7
,CASE
WHEN rn = 8 THEN account
END account8
FROM
(SELECT
name
,code
,today
,account
,ROW_NUMBER() OVER (PARTITION BY name ORDER BY account) rn
FROM
account_data
)
)
GROUP BY
name
,code
,today
;
UPDATE >>>>>>>>>
The WITH... clause above is just because I don't have your tables and data in my system. I've rewritten my answer using your query as a guide - please note I have not been able to test this ...
SELECT
name
,code
,today
,MAX(account1)
,MAX(account2)
,MAX(account3)
,MAX(account4)
,MAX(account5)
,MAX(account6)
,MAX(account7)
,MAX(account8)
FROM
(SELECT
name
,code
,today
,CASE
WHEN rn = 1 THEN account
END account1
,CASE
WHEN rn = 2 THEN account
END account2
,CASE
WHEN rn = 3 THEN account
END account3
,CASE
WHEN rn = 4 THEN account
END account4
,CASE
WHEN rn = 5 THEN account
END account5
,CASE
WHEN rn = 6 THEN account
END account6
,CASE
WHEN rn = 7 THEN account
END account7
,CASE
WHEN rn = 8 THEN account
END account8
FROM
(SELECT
b.description AS Name
,b.contragentidentifycode AS Code
,c.systemday AS Today
,b.accountno AS Account
,b.currencysname AS Currency
,b.accountno || b.currencysname AS Accounts
,ROW_NUMBER() OVER (PARTITION BY b.description ORDER BY b.accountno) rn
FROM vAACCOUNT b, currentdaysetting c
WHERE b.contragentid = 412
AND b.accountno LIKE '26%'
)
)
GROUP BY
name
,code
,today
;
If you know all the account+currency combinations you can use this pivot (I only implemented 3 of them here):
select *
from (
<your-query> )
pivot (
min(accounts) as accounts FOR (accounts) in ('acc1+curr1' as a, 'acc2+curr1' as b, 'acc1+curr2' c)
);
There is my pivot solution:
SELECT *
FROM (
SELECT id, Name, Code, Today, Account || Currency as Accounts
FROM (
SELECT
row_number() over (ORDER BY b.id) AS id
, b.description AS Name
, b.contragentidentifycode AS Code
, c.systemday AS Today
, b.accountno AS Account
, b.currencysname AS Currency
FROM vAACCOUNT b, currentdaysetting c
WHERE b.contragentid = 412
AND b.accountno LIKE '26%'
)
)
pivot (
MIN(Accounts)
FOR ID IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
) pvt

SQL query create cross column

I have this table
customer | product | quantity
-------------------------------
CLI01 | A | 10
CLI01 | B | 20
CLI02 | A | 31
CLI03 | A | 10
CLI03 | C | 12
and I want to create in SQL Server this output:
customer | crossProduct | quantity
-----------------------------------
CLI01 | A+B | 30
CLI02 | Only A | 31
CLI03 | B+C | 22
Thanks in advance
Niko
If you only care about two products, then this is simple aggregation:
select customer,
(case when count(distinct product) > 2 then 'Lots of Products'
when min(product) = max(product) then 'Only ' + min(product)
else min(product) + '+' + max(product)
end) as crossproduct,
sum(quantity)
from t
group by customer;
If you care about more than two products, then you'll need to do aggregation string concatenation. That is a bit painful in SQL Server. Start by Googling "sql server aggregate string concatenation".
This is s sample:
----- Test Data ----------
DECLARE #TestData TABLE (customer VARCHAR(10),product VARCHAR(10),quantity INT)
INSERT INTO #TestData
SELECT 'CLI01','A',10 UNION ALL
SELECT 'CLI01','B',20 UNION ALL
SELECT 'CLI02','A',31 UNION ALL
SELECT 'CLI03','A',10 UNION ALL
SELECT 'CLI03 ','C',12
----- Query -------------
SELECT customer,CASE WHEN COUNT( DISTINCT t.product)=1 THEN 'Only ' ELSE '' END + LEFT(c.product,LEN(c.product)-1) AS Product,SUM(quantity) AS quantity
FROM #TestData AS t
CROSS APPLY(SELECT a.product+'+' FROM #TestData AS a WHERE a.customer=t.customer FOR XML PATH('')) c(product)
GROUP BY customer,c.product
ORDER BY t.customer
customer Product quantity
CLI01 A+B 30
CLI02 Only A 31
CLI03 A+C 22
Have you tried using stuff? This will give you what you need. Works with as many products as necessary, from sql 2008 onwards.
CREATE TABLE x (customer VARCHAR (20), product CHAR(1), quantity INT )
INSERT INTO x
VALUES( 'CLI01', 'A', 10),
( 'CLI01', 'B', 20),
( 'CLI02', 'A', 31),
( 'CLI03', 'A', 10),
( 'CLI03', 'C', 12)
SELECT x1.customer, x3.Products, SUM(x1.quantity)
FROM x x1
CROSS APPLY ( SELECT Products = STUFF( (select '+' + product AS [text()]
FROM x x2
WHERE x2.customer = x1.customer
FOR XML PATH ('') ), 1, 1,'') ) x3
GROUP BY x1.customer, x3.Products

Count Calculation using PIVOT IN SQL

I am trying to calculate the number of stock and no-stock using pivot. Is this possible with SQL Server 2008?
Table:
DECLARE #MYTABLE TABLE
(
ID INT,
PRODUCT VARCHAR (35),
SKU INT,
NEWPRICE DECIMAL(10,5),
OLDPRICEEX DECIMAL(10,5),
REMARKS VARCHAR (35)
)
INSERT #MYTABLE
SELECT 438, 'RESISTOR', 43822, 12.66, 11.13, 'STOCK' UNION ALL
SELECT 438, 'RESISTOR', 43870, 11.99, 12.30, 'OUTS-STOCK' UNION ALL
SELECT 719, 'INDUCTOR', 71911, 666.66, 764.16, 'OUTS-STOCK' UNION ALL
SELECT 101, 'CAPACITOR',10159, 22.66, 19.12, 'STOCK' UNION ALL
SELECT 101, 'CAPACITOR',10159, 19.32, 18.19, 'STOCK'
Expected output
ID | PRODUCT | STOCK | OUT-STOCK
----+-----------+-------+----------
438 | RESISTOR | 1 | 1
719 | INDUCTOR | 0 | 1
101 | CAPACITOR | 2 | 0
Thanks
No need of Pivot. USe below query.
SELECT ID,Product, SUM(case when REMARKS ='STOCK' then 1 else 0 end) as STOCK,
SUM(case when REMARKS ='OUTS-STOCK' then 1 else 0 end) as [OUT-STOCK]
FROM #MYTABLE
GROUP BY ID,Product
Using PIVOT:
Select * from
(Select ID As ForCnt, ID, PRODUCT, REMARKS from #MYTABLE) a
PIVOT
(
COUNT(ForCnt) FOR REMARKS IN ([STOCK], [OUTS-STOCK])
) x

How can I generate a previous value table by combining fields from SQL table?

Let's say I have some data in a SQL Server database.
Location PayID Year
------------------------
Loc1 100 2010
Loc1 100 2011
Loc1 101 2012
Loc2 200 2010
Loc2 201 2011
Loc2 202 2012
And I am trying to write a query in SQL Server that will give me a table with two columns that I can search on to find out what the previous PayID for a particular Location. So the output would be.
PayID PrevID
-----------------
101 100
202 201
201 200
It only needs an entry when the previous year ID is different from the current year and I will query it recursively if I don't have the right match when a user goes back more than one year so It will pull each previous ID based on the one that was just pulled until it finds a PayID and Year matching the first table.
Any help on this would be much appreciated. I'll be attentively searching and will post a solution if I can find it.
This can be done pretty easily with a recursive CTE:
with cte as (
select Location, PayID, PayID as PrevID, Year from payhistory
union all
select p.Location, p.PayID, cte.PayID as PrevID, p.Year
from payhistory p
join cte on cte.Location = p.Location and cte.Year + 1 = p.Year
)
select distinct Location, PayID, PrevID
from cte
where PayID <> PrevID;
Here's the results I get:
| LOCATION | PAYID | PREVID |
|----------|-------|--------|
| Loc1 | 101 | 100 |
| Loc2 | 201 | 200 |
| Loc2 | 202 | 201 |
Demo: http://www.sqlfiddle.com/#!3/e0ac0/4
I didn't see a version specified, so I'd use LAG in 2012. You can filter the results if you want less info. You could replace LAG(PayID,1,NULL) with LAG(PayID,1,PayID) to alter the behavior of the first payid.
DECLARE #tbl TABLE (Location VARCHAR(4), PayID INT, Year INT)
INSERT INTO #tbl VALUES
('Loc1',100,2010)
,('Loc1',100,2011)
,('Loc1',101,2012)
,('Loc2',200,2010)
,('Loc2',201,2011)
,('Loc2',202,2012)
SELECT Location
,PayID
,LAG(PayID,1,NULL) OVER (PARTITION BY Location ORDER BY Year ASC) PrevID
FROM #tbl
http://www.sqlfiddle.com/#!6/e0ac0/2
Key assumption - This query will work only if the PayIds for each location are consecutive numbers.
select distinct l1.payId as PayId,
l2.payId as PrevId
from locs as l1
inner join locs as l2
on l1.location = l2.location
and l1.payid = (l2.payid + 1)
Following solution is non-recursive and it might offer a better performance:
DECLARE #Payment TABLE (
ID INT IDENTITY(1,1) PRIMARY KEY,
Location VARCHAR(50) NOT NULL,
PayID INT NOT NULL,
[Year] SMALLINT NOT NULL
);
INSERT #Payment
SELECT 'Loc1', 100, 2010
UNION ALL SELECT 'Loc1', 100, 2011
UNION ALL SELECT 'Loc1', 101, 2012
UNION ALL SELECT 'Loc2', 200, 2010
UNION ALL SELECT 'Loc2', 201, 2011
UNION ALL SELECT 'Loc2', 202, 2012
SELECT z.Location, z.GroupID,
MAX(CASE WHEN z.RowType = 1 THEN z.[Year] END) AS CurrentYear,
MAX(CASE WHEN z.RowType = 0 THEN z.[Year] END) AS PreviousYear,
MAX(CASE WHEN z.RowType = 1 THEN z.[PayID] END) AS CurrentPayID,
MAX(CASE WHEN z.RowType = 0 THEN z.[PayID] END) AS PreviousPayID
FROM
(
SELECT y.PayID, y.[Location], y.[Year],
-- It "groups" rows two by two: current row and previous row will have the same GroupID
(ROW_NUMBER() OVER(PARTITION BY y.Location ORDER BY y.RowNum + n.Num ASC) + 1) / 2 AS GroupID,
-- RowType: 1=Current row, 0=Previous row
ROW_NUMBER() OVER(PARTITION BY y.Location ORDER BY y.RowNum + n.Num ASC) % 2 AS RowType
FROM
(
SELECT x.Location, x.[Year], x.PayID, ROW_NUMBER() OVER(PARTITION BY x.Location ORDER BY x.[Year] DESC) RowNum
FROM #Payment x
) y
-- For every location, it duplicates every row except the last one
INNER JOIN (VALUES (1), (2)) n(Num) ON y.RowNum = 1 AND n.Num = 1 OR y.RowNum > 1
) z
GROUP BY z.Location, z.GroupID
HAVING MAX(CASE WHEN z.RowType = 1 THEN z.[Year] END) = MAX(CASE WHEN z.RowType = 0 THEN z.[Year] END) + 1
AND MAX(CASE WHEN z.RowType = 1 THEN z.[PayID] END) <> MAX(CASE WHEN z.RowType = 0 THEN z.[PayID] END)
ORDER BY z.Location;
Output:
Location GroupID CurrentYear PreviousYear CurrentPayID PreviousPayID
--------- ------- ----------- ------------ ------------ -------------
Loc1 1 2012 2011 101 100
Loc2 1 2012 2011 202 201
Loc2 2 2011 2010 201 200

Group data by the change of grouping column value in order

With the following data
create table #ph (product int, [date] date, price int)
insert into #ph select 1, '20120101', 1
insert into #ph select 1, '20120102', 1
insert into #ph select 1, '20120103', 1
insert into #ph select 1, '20120104', 1
insert into #ph select 1, '20120105', 2
insert into #ph select 1, '20120106', 2
insert into #ph select 1, '20120107', 2
insert into #ph select 1, '20120108', 2
insert into #ph select 1, '20120109', 1
insert into #ph select 1, '20120110', 1
insert into #ph select 1, '20120111', 1
insert into #ph select 1, '20120112', 1
I would like to produce the following output:
product | date_from | date_to | price
1 | 20120101 | 20120105 | 1
1 | 20120105 | 20120109 | 2
1 | 20120109 | 20120112 | 1
If I group by price and show the max and min date then I will get the following which is not what I want (see the over lapping of dates).
product | date_from | date_to | price
1 | 20120101 | 20120112 | 1
1 | 20120105 | 20120108 | 2
So essentially what I'm looking to do is group by the step change in data based on group columns product and price.
What is the cleanest way to achieve this?
There's a (more or less) known technique of solving this kind of problem, involving two ROW_NUMBER() calls, like this:
WITH marked AS (
SELECT
*,
grp = ROW_NUMBER() OVER (PARTITION BY product ORDER BY date)
- ROW_NUMBER() OVER (PARTITION BY product, price ORDER BY date)
FROM #ph
)
SELECT
product,
date_from = MIN(date),
date_to = MAX(date),
price
FROM marked
GROUP BY
product,
price,
grp
ORDER BY
product,
MIN(date)
Output:
product date_from date_to price
------- ---------- ------------- -----
1 2012-01-01 2012-01-04 1
1 2012-01-05 2012-01-08 2
1 2012-01-09 2012-01-12 1
I'm new to this forum so hope my contribution is helpful.
If you really don't want to use a CTE (although I think thats probably the best approach) you can get a solution using set based code. You will need to test the performance of this code!.
I have added in an extra temp table so that I can use a unique identifier for each record but I suspect you will already have this column in you source table. So heres the temp table.
If Exists (SELECT Name FROM tempdb.sys.tables WHERE name LIKE '#phwithId%')
DROP TABLE #phwithId
CREATE TABLE #phwithId
(
SaleId INT
, ProductID INT
, Price Money
, SaleDate Date
)
INSERT INTO #phwithId SELECT row_number() over(partition by product order by [date] asc) as SalesId, Product, Price, Date FROM ph
Now the main body of the Select statement
SELECT
productId
, date_from
, date_to
, Price
FROM
(
SELECT
dfr.ProductId
, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ChangeDate) AS rowno1
, ChangeDate AS date_from
, dfr.Price
FROM
(
SELECT
sl1.ProductId AS ProductId
, sl1.SaleDate AS ChangeDate
, sl1.price
FROM
#phwithId sl1
LEFT JOIN
#phwithId sl2
ON sl1.SaleId = sl2.SaleId + 1
WHERE
sl1.Price <> sl2.Price OR sl2.Price IS NULL
) dfr
) da1
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ChangeDate) AS rowno2
, ChangeDate AS date_to
FROM
(
SELECT
sl1.ProductId
, sl1.SaleDate AS ChangeDate
FROM
#phwithId sl1
LEFT JOIN
#phwithId sl3
ON sl1.SaleId = sl3.SaleId - 1
WHERE
sl1.Price <> sl3.Price OR sl3.Price IS NULL
) dto
) da2
ON da1.rowno1 = da2.rowno2
By binding the data source offset by 1 record (+or-) we can identify when the price buckets change and then its just a matter of getting the start and end dates for the buckets back into a single record.
All a bit fiddly and I'm not sure its going to give better performance but I enjoyed the challenge.
WITH marked AS (
SELECT
*,
case
when (lag(price,1,'') over (partition by product order by date_from)) = price
then 0 else 1
end is_price_change
FROM #ph
),
marked_as_group AS
( SELECT m.*,
SUM(is_price_change) over (PARTITION BY product order by date_from ROWS
BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS price_change_group
FROM marked m
),
SELECT
product,
date_from = MIN(date_from),
date_to = MAX(date_to),
price = MIN(price)
FROM marked_as_group
GROUP BY
product,
price_change_group
ORDER BY
product,
date_to
One solution I have come up with which is relatively "clean" is:
;with cte_sort (product, [date], price, [row])
as
(select product, [date], price, row_number() over(partition by product order by [date] asc) as row
from #ph)
select a.product, a.[date] as date_from, c.[date] as date_to, a.price
from cte_sort a
left outer join cte_sort b on a.product = b.product and (a.row+1) = b.row and a.price = b.price
outer apply (select top 1 [date] from cte_sort z where z.product = a.product and z.row > a.row order by z.row) c
where b.row is null
order by a.[date]
I used a CTE with row_number because you then don't need to worry about whether any dates are missing if you use functions like dateadd. You obviously only need the outer apply if you want to have the date_to column (which I do).
This solution does solve my problem, I am however having a slight issue getting it to perform as quickly as I'd like on my table of 5 million rows.
Create function [dbo].[AF_TableColumns](#table_name nvarchar(55))
returns nvarchar(4000) as
begin
declare #str nvarchar(4000)
select #str = cast(rtrim(ltrim(column_name)) as nvarchar(500)) + coalesce(' ' + #str , ' ')
from information_schema.columns
where table_name = #table_name
group by table_name, column_name, ordinal_position
order by ordinal_position DESC
return #str
end
--select dbo.AF_TableColumns('YourTable') Select * from YourTable