Cursor and update in Postgresql - sql

I've written a function in postgresql for updating a table using two cursors, the code executes fine without errors but doesn't do anything.
I have three tables
product master
sales data table
target table
So in the below function I'm getting product key from 1 and then in the sales table calculating no of stores/outlets the product was sold and then update the third table accordingly,
I need to calc the no of outlets for each product every week. but they should be unique, e.g If a product was sold in outlets A and B in first week the no of stores in first week should be 2, if it is sold in B, C, D next week the total should be 4 for 2nd week, If it is sold in A and D in third week the total should be still 4 for 3rd week. How do I do this.
Any help will be greatly appreciated.
create or replace function StCount() returns void as
$BODY$
Declare
ik1 integer ;
wk1 integer ;
Cur1 CURSOR FOR SELECT ik From table1 ;
Cur2 CURSOR FOR SELECT distinct(wk) FROM table2 order by wk asc;
begin
OPEN Cur1 ;
WHILE (Found) Loop
FETCH next from Cur1 INTO ik1 ;
OPEN Cur2 ;
WHILE (Found) Loop
FETCH next from Cur2 INTO wk1;
update table3 set skly =(select count(sk)from table2 a
where a.ik = ik1 and a.wk = wk1
and a.sk not in (select distinct (sk) from table2
where ik = ik1 and wk <= wk1 - 1 ))
where ik = ik1 and wk = w1
;
End loop ;
CLOSE Cur2;
end loop ;
CLOSE Cur1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

You didn't show us how your tables look like or what exactly the data is you are dealing with, so I am assuming a lot here.
Using this setup:
create table product (id integer primary key);
create table sales (id serial primary key, product_id integer, store text, wk integer);
insert into product values (1),(2),(3),(4);
insert into sales (product_id, store, wk)
values
(1, 'A', 1),
(1, 'B', 1),
(1, 'B', 2),
(1, 'C', 2),
(1, 'D', 2),
(1, 'A', 3),
(1, 'D', 3);
The cumulative sales per product, store and week can be calculated like this:
select product_id,
store,
wk,
count(store) over (partition by product_id order by wk) as sofar
from (
select product_id,
store,
wk,
row_number() over (partition by product_id, store order by wk) as rn
from sales
order by product_id, store
) t
where rn = 1;
Note that the product table isn't actually used in this, but it can easily be joined to the inner query.
For the above sample data, this returns:
product_id | store | wk | sofar
-----------+-------+----+------
1 | A | 1 | 2
1 | B | 1 | 2
1 | C | 2 | 4
1 | D | 2 | 4
Now this result can be used to update the target table:
update target_table
set slky = t.sofar
from (
select product_id,
store,
wk,
count(store) over (partition by product_id order by wk) as sofar
from (
select product_id,
store,
wk,
row_number() over (partition by product_id, store order by wk) as rn
from sales
order by product_id, store
) t
where rn = 1
) s
where s.wk = target_table.wk
and s.product_id = target_table.product_id
and s.store = target_table.store;
This assumes that the combination of (product_id, store, wk) is unique in target_table. As you have obfuscated the names of your tables and columns in your example it is hard to tell though.

Related

How to create random function using a stored procedure? PL/SQL

I have a procedure that adds data
add_price (cust_id customers.id%type,
items_id items.id%type,
price number);
and I want to create a function that for each combination of customers and items to create an additional one at random entry in the table price.
How can I do that?
UPD: Please note, I believe the idea from MT0 is better because you'll need only one insert statement. My solution is for the case when using add_price function is required
So, "each combination of customers and items" means you need a cartesian product:
select cust_id, item_id
from customers
cross join items;
For example if you had following data in "customers" and "items" table:
cust_id
cust_name
1
A
2
B
item_id
item_name
1
a
2
b
the query above would return:
cust_id
item_id
1
1
1
2
2
1
2
2
Thus, all is left is to get random value. Use dbms_random.value for that
begin
for q in (select cust_id, item_id from customers cross join items) loop
add_price(q.cust_id, q.item_id, round(dbms_random.value(10, 1000), 2));
end loop;
end;
The parameters for value are lowes_value and highest_value so the result will be between those numbers. You probably will need to set them somehow. And rounding will be needed too
Don't use a function, create a procedure and use INSERT ... SELECT with the CROSS JOIN of the customers and the items tables:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
INSERT INTO prices (customer_id, item_id, price)
SELECT c.customer_id,
i.item_id,
ROUND(DBMS_RANDOM.VALUE(0,100),2)
FROM customers c
CROSS JOIN items i;
END generate_random_prices;
/
Which, if you have the sample data:
CREATE TABLE customers (customer_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,5,42));
CREATE TABLE items (item_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,3,61));
CREATE TABLE prices (
customer_id REFERENCES customers(customer_id),
item_id REFERENCES items(item_id),
price NUMBER(4,2)
);
Then after:
BEGIN
generate_random_prices();
END;
/
The prices table may (randomly) contain:
CUSTOMER_ID
ITEM_ID
PRICE
1
1
38.91
1
3
39.74
1
61
67.28
5
1
13.92
5
3
48.17
5
61
70.21
42
1
90.33
42
3
5.7
42
61
40.37
If you want to call your ADD_PRICE procedure then just take the same CROSS JOIN query and use a cursor loop:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
FOR rw IN (SELECT c.customer_id,
i.item_id
FROM customers c
CROSS JOIN items i)
LOOP
ADD_PRICE(rw.customer_id, rw.item_id, ROUND(DBMS_RANDOM.VALUE(0,100),2));
END LOOP;
END generate_random_prices;
/
(But it will be more efficient to just use a single INSERT ... SELECT statement.)
db<.fiddle here

Sum Quantity and Filter Results

I have the following table with order id's and quantities. I need to be able to sum the quantity and retrieve the id's that that equal less than the provided number.
| id | quantity |
|------|----------|
| 100 | 1 |
| 200 | 25 |
| 300 | 15 |
For example, I need the id's where the sum of quantity equals less than 25.
When I try the following it only provides me the first id (100).
Select *
from (
select *,
SUM (Quantity) OVER (ORDER BY Id) AS SumQuantity
from dbo.Orders
) as A
where SumQuantity <= 25
Is it possible to adjust this query where it will provide me id 100 and 300, since the sum total of those orders is less than 25?
I know I can use a where clause on for quantity less than 25, but the important thing here is I need to be able to sum the quantity and pull id's that give me less than the provided number.
Thank you in advance!
Perhaps you want to order by the quantity instead of id?
Select o.*
from (select o.*, SUM (Quantity) OVER (ORDER BY quantity) AS SumQuantity
from dbo.Orders
) o
where SumQuantity <= 25;
This chooses the smallest values so you will get the most rows.
Group by Id and set the condition in the HAVING clause:
select Id, SUM(Quantity) AS SumQuantity
from Orders
group by Id
having SUM(Quantity) <= 25
See the demo.
Results:
Id | SumQuantity
100 | 1
200 | 25
300 | 15
If you want to include all the columns you can modify your query to not ORDER BY id but PARTITION BY id:
select *
from (
select *,
SUM (Quantity) OVER (PARTITION BY Id) AS SumQuantity
from Orders
) as A
where SumQuantity <= 25
For this dataset:
CREATE TABLE Orders([id] varchar(6), [quantity] int);
INSERT INTO Orders([id], [quantity])VALUES
('100', '1'), ('100', '2'),
('200', '25'), ('200', '3'),
('300', '15'), ('300', '5');
Results:
id | quantity | SumQuantity
100 | 1 | 3
100 | 2 | 3
300 | 15 | 20
300 | 5 | 20
See the demo.
Setup:
Your threshold can vary, so let's make it into a variable:
declare #threshold int = 25;
But I also imagine that your table values can vary, like if we add another row only having a quantity of 2:
declare #orders table (id int, quantity int)
insert #orders values (100,1), (200,25), (300,15), (400, 2);
Solution:
For this, we'll need a recursive kind of cross joining:
with
traverse as (
select ids = convert(nvarchar(255), id),
id,
quantity
from #orders
where quantity < #threshold
union all
select ids =
convert(nvarchar(255), tv.ids + ',' +
convert(nvarchar(255), o.id)),
o.id,
quantity = tv.quantity + o.quantity
from traverse tv
cross join #orders o
where tv.id < o.id
and tv.quantity + o.quantity < #threshold
)
select t.ids, t.quantity
from traverse t;
which will produce:
Explanation:
The above code is an algorithm that builds a tree. It starts with your base id's and quantities as nodes (the anchor part of the CTE). It trims anything not meeting the threshold.
It then adds edges by cross joining with orders table again (the recursive part of the CTE), but it only includes the following:
Id's that are greater than the last id considered in the current node (this is so that we avoid duplicate considerations, such as ids = '300,400' and ids = '400,300').
Ids where the sum of quantities is less than the threshold.
Warnings:
But beware, the type of problem you're considering will have computational complexity considerations. But because of the trimming conditions, it will be more efficient than doing all the cross joins first and then filtering the result set at the end.
Also, keep in mind that you may get rows in your table where there is no single set of numbers that will sum up to less than 25. Rather, you can get different paths to that sum. The way I produce the results here will help you identify such a situation.
cross join is perfect for this task, try:
declare #tbl table (id int, quantity int);
insert into #tbl values
(100, 1), (200, 25), (300, 15), (400, 10);
select distinct case when t1.id > t2.id then t1.id else t2.id end,
case when t1.id < t2.id then t1.id else t2.id end
from #tbl t1
cross join #tbl t2
where t1.id <> t2.id
and t1.quantity + t2.quantity < 25

Retrieve specific value if set, or general if not

I have to write a query which will import some data from table. Table structure is like:
Item_ID
Plant
Price
I have second table with
Item_ID
Plant
Second table is a key, and I have to match rows from first table to get valid price. Seems to be easy, however:
In first table column plant might determinate specific plant, or have value 'ALL'. What I want to do is retrieve price for given plant, if it is set, or get price for value 'ALL' if there is no row for given plant. In other words:
If first.Plant = second.Plant
return price
Else If first.Plant = 'ALL'
return price
Else
return NULL
I can't use simple ON first.Plant = second.Plant OR first.Plant = 'ALL', because there might be two rows: one for given plant and second for rest with value 'ALL'. I need to return only first price in that case. E.g.
Item_ID | Plant | Price
2 | M1 | 10,0
2 | All | 12,0
1 | All | 9,0
In that case for Item_ID = 2 and Plant = M1 the only valid price = 10, but for Item_ID = 2 and Plant = M2 price = 12, and for any Item_ID = 1 price = 9
I hope You understood something after my explanation ;)
Using ROW_Number and a Common Table Expression you can ensure that ROW_NUMBER is partitioned by Item_ID and Plant and ordered such that if there are two rows then 'All' is second. You then simple select the rows with a row number = 1:
Setup
CREATE TABLE #Price
(
Item_ID int,
Plant Varchar(20),
Price Decimal(6,2)
)
CREATE TABLE #Plant
(
Item_ID int,
Plant VARCHAR(20)
)
INSERT INTO #Price
VALUES (2, 'M1', 10.0),
(2, 'All', 12.0),
(1, 'All', 9.0)
INSERT INTO #Plant
VALUES (2, 'M1'),
(2, 'M2'),
(1, 'M1'),
(1, 'M2')
Here's the query for SQL Server >= 2012
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY IIF(PR.Plant = 'All', 1, 0)) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1
And here's a version using CASE which will work for all SQL Server >= 2008 R2
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY CASE WHEN PR.Plant = 'All' Then 1 Else 0 End) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1

Create Historical Table from Dates with Ranked Contracts (Gaps and Islands?)

I have an issue in Teradata where I am trying to build a historical contract table that lists a system, it's corresponding contracts and the start and end dates of each contract. This table would then be queried for reporting as a point in time table. Here is some code to better explain.
CREATE TABLE TMP_WORK_DB.SOLD_SYSTEMS
(
SYSTEM_ID varchar(5),
CONTRACT_TYPE varchar(10),
CONTRACT_RANK int,
CONTRACT_STRT_DT date,
CONTRACT_END_DT date
);
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('AAA', 'BEST', 10, '2012-01-01', '2012-06-30');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('AAA', 'BEST', 9, '2012-01-01', '2012-06-30');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('AAA', 'OK', 1, '2012-08-01', '2012-12-30');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('BBB', 'BEST', 10, '2013-12-01', '2014-03-02');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('BBB', 'BETTER', 7, '2013-12-01', '2017-03-02');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('BBB', 'GOOD', 4, '2016-12-02', '2017-12-02');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('CCC', 'BEST', 10, '2009-10-13', '2014-10-14');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('CCC', 'BETTER', 7, '2009-10-13', '2016-10-14');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS VALUES ('CCC', 'OK', 2, '2008-10-13', '2017-10-14');
The required output would be:
SYSTEM_ID CONTRACT_TYPE CONTRACT_STRT_DT CONTARCT_END_DT CONTRACT_RANK
AAA BEST 01/01/2012 06/30/2012 10
AAA OK 08/01/2012 12/30/2012 1
BBB BEST 12/01/2013 03/02/2014 10
BBB BETTER 03/03/2014 03/02/2017 7
BBB GOOD 03/03/2017 12/02/2017 4
CCC OK 10/13/2008 10/12/2009 2
CCC BEST 10/13/2009 10/14/2014 10
CCC BETTER 10/15/2014 10/14/2016 7
CCC OK 10/15/2016 10/14/2017 2
I'm not necessarily looking to reduce rows but am looking to get the correct state of the system_id at any given point in time. Note that when a higher ranked contract ends and a lower ranked contract is still active the lower ranked picks up where the higher one left off.
We are using TD 14 and I have been able to get the easy records where the dates flow sequentially and are of higher rank but am having trouble with the overlaps where two different ranked contracts cover multiple date spans.
I found this blog post (Sharpening Stones) and got it working for the most part but am still having trouble setting the new start dates for the overlapping contracts.
Any help would be appreciated. Thanks.
*UPDATE 04/04/2014 *
I came up with the following code which gives me exactly what I want but I'm not sure of the performance. It works on smaller data sets of a few hundred rows but I havent tested it on several million:
*UPDATE 04/07/2014 *
Updated the date subquery due to spool issues. This query explodes all days where the contract is possibly active and then uses the ROW_NUMBER function to get the highest ranked CONTRACT_TYPE per day. The MIN/MAX functions are then partitioned over the system and contract type to pick up when the highest ranked contract type changes.
*UPDATE - 2 - 04/07/2014 *
I cleaned up the query and it seems to be perform a little better.
SELECT
SYSTEM_ID
, CONTRACT_TYPE
, MIN(CALENDAR_DATE) NEW_START_DATE
, MAX(CALENDAR_DATE) NEW_END_DATE
, CONTRACT_RANK
FROM (
SELECT
CALENDAR_DATE
, SYSTEM_ID
, CONTRACT_TYPE
, CONTRACT_RANK
, ROW_NUMBER() OVER (PARTITION BY SYSTEM_ID, CALENDAR_DATE ORDER BY CONTRACT_RANK DESC, CONTRACT_STRT_DT DESC, CONTRACT_END_DT DESC) AS RNK
FROM SOLD_SYSTEMS t1
JOIN (
SELECT CALENDAR_DATE
FROM FULL_CALENDAR_TABLE ia
WHERE CALENDAR_DATE > DATE'2013-01-01'
)dt
ON CALENDAR_DATE BETWEEN CONTRACT_STRT_DT AND CONTRACT_END_DT
QUALIFY RNK = 1
)z1
GROUP BY 1,2,5
Following approach uses the new PERIOD functions in TD13.10.
-- 1. TD_SEQUENCED_COUNT can't be used in joins, so create a Volatile Table
-- 2. TD_SEQUENCED_COUNT can't use additional columns (e.g. CONTRACT_RANK),
-- so simply create a new row whenever a period starts or ends without
-- considering CONTRACT_RANK
CREATE VOLATILE TABLE vt AS
(
WITH cte
(
SYSTEM_ID
,pd
)
AS
(
SELECT
SYSTEM_ID
-- PERIODs can easily be constructed on-the-fly, but the end date is not inclusive,
-- so I had to adjust to your implementation, CONTRACT_END_DT +/- 1:
,PERIOD(CONTRACT_STRT_DT, CONTRACT_END_DT + 1) AS pd
FROM SOLD_SYSTEMS
)
SELECT
SYSTEM_ID
,BEGIN(pd) AS CONTRACT_STRT_DT
,END(pd) - 1 AS CONTRACT_END_DT
FROM
TABLE (TD_SEQUENCED_COUNT
(NEW VARIANT_TYPE(cte.SYSTEM_ID)
,cte.pd)
RETURNS (SYSTEM_ID VARCHAR(5)
,Policy_Count INTEGER
,pd PERIOD(DATE))
HASH BY SYSTEM_ID
LOCAL ORDER BY SYSTEM_ID ,pd) AS dt
)
WITH DATA
PRIMARY INDEX (SYSTEM_ID)
ON COMMIT PRESERVE ROWS
;
-- Find the matching CONTRACT_RANK
SELECT
vt.SYSTEM_ID
,t.CONTRACT_TYPE
,vt.CONTRACT_STRT_DT
,vt.CONTRACT_END_DT
,t.CONTRACT_RANK
FROM vt
-- If both vt and SOLD_SYSTEMS have a NUPI on SYSTEM_ID this join should be
-- quite efficient
JOIN SOLD_SYSTEMS AS t
ON vt.SYSTEM_ID = t.SYSTEM_ID
AND ( t.CONTRACT_STRT_DT, t.CONTRACT_END_DT)
OVERLAPS (vt.CONTRACT_STRT_DT, vt.CONTRACT_END_DT)
QUALIFY
-- As multiple contracts for the same period are possible:
-- find the row with the highest rank
ROW_NUMBER()
OVER (PARTITION BY vt.SYSTEM_ID,vt.CONTRACT_STRT_DT
ORDER BY t.CONTRACT_RANK DESC, vt.CONTRACT_END_DT DESC) = 1
ORDER BY 1,3
;
-- Previous query might return consecutive rows with the same CONTRACT_RANK, e.g.
-- BBB BETTER 2014-03-03 2016-12-01 7
-- BBB BETTER 2016-12-02 2017-03-02 7
-- If you don't want that you have to normalize the data:
WITH cte
(
SYSTEM_ID
,CONTRACT_STRT_DT
,CONTRACT_END_DT
,CONTRACT_RANK
,CONTRACT_TYPE
,pd
)
AS
(
SELECT
vt.SYSTEM_ID
,vt.CONTRACT_STRT_DT
,vt.CONTRACT_END_DT
,t.CONTRACT_RANK
,t.CONTRACT_TYPE
,PERIOD(vt.CONTRACT_STRT_DT, vt.CONTRACT_END_DT + 1) AS pd
FROM vt
JOIN SOLD_SYSTEMS AS t
ON vt.SYSTEM_ID = t.SYSTEM_ID
AND ( t.CONTRACT_STRT_DT, t.CONTRACT_END_DT)
OVERLAPS (vt.CONTRACT_STRT_DT, vt.CONTRACT_END_DT)
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY vt.SYSTEM_ID,vt.CONTRACT_STRT_DT
ORDER BY t.CONTRACT_RANK DESC, vt.CONTRACT_END_DT DESC) = 1
)
SELECT
SYSTEM_ID
,CONTRACT_TYPE
,BEGIN(pd) AS CONTRACT_STRT_DT
,END(pd) - 1 AS CONTRACT_END_DT
,CONTRACT_RANK
FROM
TABLE (TD_NORMALIZE_MEET
(NEW VARIANT_TYPE(cte.SYSTEM_ID
,cte.CONTRACT_RANK
,cte.CONTRACT_TYPE)
,cte.pd)
RETURNS (SYSTEM_ID VARCHAR(5)
,CONTRACT_RANK INT
,CONTRACT_TYPE VARCHAR(10)
,pd PERIOD(DATE))
HASH BY SYSTEM_ID
LOCAL ORDER BY SYSTEM_ID, CONTRACT_RANK, CONTRACT_TYPE, pd ) A
ORDER BY 1, 3;
Edit: This is another way to get the result of the 2nd query without Volatile Table and TD_SEQUENCED_COUNT:
SELECT
t.SYSTEM_ID
,t.CONTRACT_TYPE
,BEGIN(CONTRACT_PERIOD) AS CONTRACT_STRT_DT
,END(CONTRACT_PERIOD)- 1 AS CONTRACT_END_DT
,t.CONTRACT_RANK
,dt.p P_INTERSECT PERIOD(t.CONTRACT_STRT_DT,t.CONTRACT_END_DT + 1) AS CONTRACT_PERIOD
FROM
(
SELECT
dt.SYSTEM_ID
,PERIOD(d, MIN(d)
OVER (PARTITION BY dt.SYSTEM_ID
ORDER BY d
ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)) AS p
FROM
(
SELECT
SYSTEM_ID
,CONTRACT_STRT_DT AS d
FROM SOLD_SYSTEMS
UNION
SELECT
SYSTEM_ID
,CONTRACT_END_DT + 1 AS d
FROM SOLD_SYSTEMS
) AS dt
QUALIFY p IS NOT NULL
) AS dt
JOIN SOLD_SYSTEMS AS t
ON dt.SYSTEM_ID = t.SYSTEM_ID
WHERE CONTRACT_PERIOD IS NOT NULL
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY dt.SYSTEM_ID,p
ORDER BY t.CONTRACT_RANK DESC, t.CONTRACT_END_DT DESC) = 1
ORDER BY 1,3
And based on that you can also include the normalization in a single query:
WITH cte
(
SYSTEM_ID
,CONTRACT_TYPE
,CONTRACT_STRT_DT
,CONTRACT_END_DT
,CONTRACT_RANK
,pd
)
AS
(
SELECT
t.SYSTEM_ID
,t.CONTRACT_TYPE
,BEGIN(CONTRACT_PERIOD) AS CONTRACT_STRT_DT
,END(CONTRACT_PERIOD)- 1 AS CONTRACT_END_DT
,t.CONTRACT_RANK
,dt.p P_INTERSECT PERIOD(t.CONTRACT_STRT_DT,t.CONTRACT_END_DT + 1) AS CONTRACT_PERIOD
FROM
(
SELECT
dt.SYSTEM_ID
,PERIOD(d, MIN(d)
OVER (PARTITION BY dt.SYSTEM_ID
ORDER BY d
ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)) AS p
FROM
(
SELECT
SYSTEM_ID
,CONTRACT_STRT_DT AS d
FROM SOLD_SYSTEMS
UNION
SELECT
SYSTEM_ID
,CONTRACT_END_DT + 1 AS d
FROM SOLD_SYSTEMS
) AS dt
QUALIFY p IS NOT NULL
) AS dt
JOIN SOLD_SYSTEMS AS t
ON dt.SYSTEM_ID = t.SYSTEM_ID
WHERE CONTRACT_PERIOD IS NOT NULL
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY dt.SYSTEM_ID,p
ORDER BY t.CONTRACT_RANK DESC, t.CONTRACT_END_DT DESC) = 1
)
SELECT
SYSTEM_ID
,CONTRACT_TYPE
,BEGIN(pd) AS CONTRACT_STRT_DT
,END(pd) - 1 AS CONTRACT_END_DT
,CONTRACT_RANK
FROM
TABLE (TD_NORMALIZE_MEET
(NEW VARIANT_TYPE(cte.SYSTEM_ID
,cte.CONTRACT_RANK
,cte.CONTRACT_TYPE)
,cte.pd)
RETURNS (SYSTEM_ID VARCHAR(5)
,CONTRACT_RANK INT
,CONTRACT_TYPE VARCHAR(10)
,pd PERIOD(DATE))
HASH BY SYSTEM_ID
LOCAL ORDER BY SYSTEM_ID, CONTRACT_RANK, CONTRACT_TYPE, pd ) A
ORDER BY 1, 3;
SEL system_id,contract_type,MAX(contract_rank),
CASE WHEN contract_strt_dt<prev_end_dt THEN prev_end_dt+1
ELSE contract_strt_dt
END AS new_start ,contract_strt_dt,contract_end_dt,
MIN(contract_end_dt) OVER (PARTITION BY system_id
ORDER BY contract_strt_dt,contract_end_dt ROWS BETWEEN 1 PRECEDING
AND 1 PRECEDING) prev_end_dt
FROM sold_systems
GROUP BY system_id,contract_type,contract_strt_dt,contract_end_dt
ORDER BY contract_strt_dt,contract_end_dt,prev_end_dt
I think I'v got it....
try this
select SYSTEM_ID, CONTRACT_TYPE,CONTRACT_RANK,
case
when CONTRACT_STRT_DT<NEW_START_DATE then NEW_START_DATE /*if new_star_date overlap startdate then get new_Start_date */
else CONTRACT_STRT_DT
end as new_contract_str_dt,
CONTRACT_END_DT
from
(select t1.SYSTEM_ID,t1.CONTRACT_TYPE,t1.CONTRACT_RANK,t1.CONTRACT_STRT_DT,t1.CONTRACT_END_DT,
coalesce(max(t1.CONTRACT_END_DT) over (partition by t1.SYSTEM_ID order by t1.CONTRACT_RANK desc rows between UNBOUNDED PRECEDING and 1 preceding ), t1.CONTRACT_STRT_DT) NEW_START_DATE
from SOLD_SYSTEMS t1
) as temp1
/*you may remove fully overlapped contracts*/
where NEW_START_DATE<=CONTRACT_END_DT
It's simpler and have a good execution plan... You can work with large tables (don't forget to collect statistics )

Select without - values from table SQL

I'm taking items [I1] from this SQL Server table and returning to this table as - QTY and - TOTAL ..
http://i.stack.imgur.com/bdnPF.png
After that I have another row with the same Item ID and - QTY AND -Total
http://i.stack.imgur.com/4vUNP.png
I need to filter every time without returned product,
In this case I already returned the I1, when I'm going to return sale invoice .. I need to select the none returned products,
SELECT
DEL_PurchasesLines.ItemIdentityCode,
SUM(DEL_PurchasesLines.Qty) As Qty,
SUM(DEL_PurchasesLines.Total) As Total
FROM DEL_PurchasesLines
WHERE InvoiceNo = '1' AND DealerCode = 'S0002M'
GROUP BY
DEL_PurchasesLines.ItemIdentityCode
HAVING SUM(DEL_PurchasesLines.Total) > 0 AND SUM(DEL_PurchasesLines.Qty) > 0
I always like to create some test data in tempdb.
--
-- Create sample data
--
use tempdb;
go
if object_id('items') > 0
drop table items
go
create table items
(
id varchar(8),
qty int,
total int
);
go
insert into items values
('I1', 2, 4),
('I2', 3, 6),
('I3', 5, 10),
('I1', -2, -4);
go
select * from items;
go
One way to solve this problem is to group by the id, summing both the qty and total columns. Display only rows that have > 0.
--
-- Show lines in which qty * total > 0
--
select id, sum(qty) as sum_qty, sum(total) as sum_total
from items
group by id
having sum(qty) > 0 and sum(total) > 0;
Another way to think of this is to show all orders that do not have returns.
--
-- Show rows that do not have any returns
--
select *
from items i left join
(
select id
from items
where qty < 0 or total < 0
) r on i.id = r.id
where r.id is null