TOP 1 or LIMIT 1 not working in a Correlated Subquery - SPS 11 - sql

I am trying to work on a query to fill the Price for a product where it is zero with non-zero value fro previous record. I tried to write a simple correlated subquery but its not working.
var_1 = select * from "XYZ"."PRD_TEST" where price <> 0 order by period desc;
var_out = select a.product,a.period, ( select price from :var_1 b where a.product = b.product and a.period > b.period and b.period <> 0 limit 1 ) as price from "XYZ"."PRD_TEST" a;
PRODUCT PERIOD PRICE
A 1 100
A 2 0 - to be filled with 100
A 3 0 - to be filled with 100
A 4 5
A 5 0 - to be filled with 5
I tried to replace the sub-query with scalar function but it does not take table as a Parameter.
I tried to achieve the output using Left outer join and Row_number but it's too expensive and runs for a long time.
I am looking for a best option to fetch only 1 record in the subquery just like TOP 1.

you could use a scalar subquery like this ( select ... limit 1 is not considered scalar in HANA, unfortunately):
Do begin
var_1 = select * from (( Select 'A' product, '1' period, '100' price from sys.dummy )
Union ( Select 'A' product, '2' period, '0' price from sys.dummy )
Union ( Select 'A' product, '3' period, '0' price from sys.dummy )
Union ( Select 'A' product, '4' period, '5' price from sys.dummy )
Union ( Select 'A' product, '5' period, '0' price from sys.dummy )) order by period desc;
var_out = ( select a.product,
a.period,
( select max(price)
from :var_1 b
where a.product = b.product
and a.period > b.period
and b.period <> 0
and b.period = ( select max(period) from :var_1 c
where a.product = c.product
AND a.period > c.period
and c.period <> 0
and c.price <> 0
)) as price
from :var_1 a where price = '0' )
union (select product, period, price from :var_1 where price <> '0' );
select * from :var_out order by product, period;
end
Tested on sps12
Added after comment.
Why don't you just try? It is very simple.
Because I was curious I tried it on my HCP trial instance, takes about 1 sec for on milion rows. I included a ifnull to avoid rows with null-values where there is no price in earlier periods.
Here is the coding:
drop table var_1;
create column table var_1 as
(
select
cast ( 'Prod' || prd.GENERATED_PERIOD_START as nvarchar(20) ) product,
cast ( per.GENERATED_PERIOD_START as decimal(2)) period,
cast ( case when rand() < '0.5' then rand() * '100' else '0' end
as decimal(5,2)) as price -- ~50% of price is 0
from series_generate_integer(1,0,1000000/13) as prd, --~1Mio records
series_generate_integer(1,0,13) as per --12 periods + period 0
);
merge delta of var_1;
select * from var_1
order by product, period
limit 100;
do begin sequential execution -- don't let parallel execution influence the runtime-measurement
declare start_timestamp timestamp;
start_timestamp = current_timestamp;
var_out = ( select a.product,
a.period,
ifnull ((select max(price)
from var_1 b
where a.product = b.product
and a.period > b.period
and b.period <> 0
and b.period = ( select max(period) from var_1 c
where a.product = c.product
AND a.period > c.period
and c.period <> 0
and c.price <> 0
)),'0.0') as price
from var_1 a where price = '0' )
union (select product, period, price from var_1 where price <> '0' );
select nano100_between(:start_timestamp, (select current_timestamp from dummy) )/10000 as runtime_millisec from dummy;
select * from :var_out
order by product, period
limit 100;
end

Related

Can `any` and `exists` apply to each group after `GROUP BY`?

I have a table. Some of its columns are:
paymentID
payment_property
line
line_amount
where each payment consists of one or more lines, each payment has a payment_property, and each line has an amount.
I want to have three queries:
I want to find those payments, which has property 'S', and has no line with a negative amount.
And, complementary to that, those payments, with property 'S', and with at least one line with a negative amount.
I also want to find those payments, which has property 'S', and has all lines with negative amounts.
Can any and exists apply to each group after GROUP BY?
Thanks.
e.g.
paymentID payment_property line line_amount
'1' 'S' '1' 15
'1' 'S' '2' -15
'1' 'S' '3' 0
'2' 'S' '1' 15
'2' 'S' '2' 10
'2' 'S' '3' 0
'3' 'S' '1' -1
'3' 'S' '2' -2
'3' 'S' '3' -3
payment '1' has a line with a negative amount, and payment '2' doesn't. Payment '3' has all its lines with negative amounts.
You could use any or exists inside subqueries:
-- no negatives
select distinct paymentID from T t1
where not exists (
select 1 from T t2
where t2.paymentID = t1.paymentID and t2.line_amount < 0
);
-- one or more negative
select distinct paymentID from T t1
where exists (
select 1 from T t2
where t2.paymentID = t1.paymentID and t2.line_amount < 0
);
-- all negative
select distinct paymentID from T t1
where not exists (
select 1 from T t2
where t2.paymentID = t1.paymentID and line_amount >= 0
);
You could adapt this approach to find exactly one negative amount but then it becomes more difficult to identify specific counts:
-- exactly one negative
select distinct paymentID from T t1
where line_amount < 0 and not exists (
select 1 from T t2
where t2.paymentID = t1.paymentID and t2.line <> t1.line and line_amount < 0
);
-- more than one negative
select distinct paymentID from T t1
where line_amount < 0 and line_amount > any ( /* finally using any */
select t2.line_amount from T t2
where t2.paymentID = t1.paymentID
);
This is probably the better approach anyway, and it lets you count a specific number too:
select paymentID from T group by paymentID
having count(case when line_amount < 0 then 1 end) = 0;
select paymentID from T group by paymentID
having count(case when line_amount < 0 then 1 end) > 0;
select paymentID from T group by paymentID
having count(case when line_amount < 0 then 1 end) = count(*);

Sql optimization query with join on Presto

I have the following query which shows out of all the clicks on products how many no of products have >1 image. The queries separately work fine but when combined its not able to execute within 3m threshold time. Any inputs how can this be optimized best.
select DATE (DATE_TRUNC('week',dt)) AS wk_dt,COUNT(DISTINCT a.product_id) tot_prods,COUNT(DISTINCT b.product_id) multiimp_prods,count(a.product_id) AS total_clicks,count(case when b.product_id>0 then a.product_id END) AS total_mulimg_clicks
from (
select product_id,DATE(from_unixtime((time/1000)+19800)) as dt--,count(distinct_id) clicks
from silver.mixpanel_android__product_clicked
WHERE DATE(from_unixtime((time/1000)+19800)) BETWEEN date(date_trunc('week',cast(Current_date - interval '14' day AS date))) AND Current_date
GROUP BY 1,2) a --2,471,245 1,458,476
LEFT join (
SELECT product_id,tot_img
FROM (SELECT DISTINCT product_id, catalog_id,(a + b + c + d + e) AS tot_img
FROM (
SELECT product_id,catalog_id,
CASE WHEN images is NULL then 0 else 1 end as a,
CASE WHEN img_2 is NULL then 0 else 1 end as b,
CASE WHEN img_3 is NULL then 0 else 1 end as c,
CASE WHEN img_4 is NULL then 0 else 1 end as d,
CASE WHEN img_5 is NULL then 0 else 1 end as e
FROM (
SELECT DISTINCT id AS product_id,catalog_id,
TRIM(images) AS images,
TRIM(SPLIT_PART(images,',',2)) AS "img_2",
TRIM(SPLIT_PART(images,',',3)) AS "img_3",
TRIM(SPLIT_PART(images,',',4)) AS "img_4",
TRIM(SPLIT_PART(images,',',5)) AS "img_5"
FROM silver.supply__products --WHERE date(created) <= date(date_trunc('week',cast(Current_date - interval '14' day AS date)))
)
--GROUP BY 1,2
order by 6 asc
)
)
WHERE tot_img>1
) b
on a.product_id=b.product_id
GROUP BY 1

issues in Case and when Statement

I have a question about Case and when statements. I have a list of two transtypeid like 10 and 12.
I tried to take sale1 amount like if the transtypeid 11 has a sum amount !=0 means, I need to minus the amount with sum amount of transtypeid 10
I tried a lot but nothing worked.
I have these queries I tried
select
CT.CustomerCode, C.CustomerName,
sale1 = case
when (ct.TransTypeID = 11) and (sum(ct.OVAmount - ct.OVDiscount) != 0)
then sum(ct.OVAmount - ct.OVDiscount) - sum(ct.OVAmount - ct.OVDiscount)
else 0
end,
C.CountryCode, C.CityCode
from
CustomerTransactions CT
inner join
Customers C ON CT.CustomerCode = C.CustomerCode
where
ct.TransDate >= '2015-01-01'
and ct.TransDate <= '2015-12-31'
and ct.TransTypeID in (10, 11)
group by
ct.CustomerCode, c.CustomerName, c.CountryCode, c.CityCode
Try calculate sale1 with this SQL code:
CASE WHEN
SUM(CASE WHEN ct.TransTypeID = 11
THEN ct.OVAmount - ct.OVDiscount
ELSE 0 END) != 0
THEN
SUM(CASE WHEN ct.TransTypeID = 11
THEN ct.OVAmount - ct.OVDiscount
ELSE O END)
- SUM(CASE WHEN ct.TransTypeID = 10
THEN ct.OVAmount - ct.OVDiscount
ELSE 0 END)
ELSE 0 END
I'm not sure that I understand what you need. But I give it a try since you are in hurry.
Something like this, maybe?
select
CT1.CustomerCode, C.CustomerName,
sale1 =
case
when ( sum(ct1.OVAmount - ct1.OVDiscount) != 0 )
then sum( ct1.OVAmount - ct1.OVDiscount ) - sum( ct2.OVAmount - ct2.OVDiscount )
else
0
end,
C.CountryCode, C.CityCode
from
Customers c
Inner join CustomerTransactions CT1 ON ( CT1.CustomerCode = C.CustomerCode ) And ( ct1.TransTypeID = 11 )
Inner join CustomerTransactions CT2 ON ( CT2.CustomerCode = C.CustomerCode ) And ( ct2.TransTypeID = 10 )
where
( ct1.TransDate >= '2015-01-01' )
and ( ct1.TransDate < '2016-01-01' )
and ( ct2.TransDate >= '2015-01-01' )
and ( ct2.TransDate < '2016-01-01' )
group by
ct1.CustomerCode,c.CustomerName,c.CountryCode,c.CityCode
Using CTEs:
with
cte10 ( CustomerId, amount ) as (
select
customerId, sum( amount ) as amount
from
CustomerTransaction
where
( Type = 1 )
group by CustomerId
),
cte11 ( CustomerId, amount ) as (
select
customerId, sum( amount ) as amount
from
CustomerTransaction
where
( Type = 2 )
group by CustomerId
)
select
c.Id, c.Description,
sale1 =
case
when ( cte10.amount <> 0 )
then cte10.amount - cte11.amount
else
0
end
from
Customer c
Inner join cte10 on ( cte10.CustomerId = C.id )
inner join cte11 on ( cte11.Customerid = C.id )

SQL: Trying to understand IF/ELSE

SELECT CASE r.SourceId
WHEN '1' THEN 'ITUNES'
WHEN '2' THEN 'SFR'
WHEN '3' THEN 'ORANGE'
ELSE 'Others'
END as source
, CAST(SUM (r.SalesVolume) AS DECIMAL(14, 4) ) AS Volume
, CAST(SUM (r.SalesVolume * r.CustomerPrice) AS DECIMAL(14, 4) ) AS Value
from Rawdata r
INNER JOIN Product p
ON p.ProductId = r.ProductId
INNER JOIN Calendar c
ON r.DayId = c.DayId
WHERE c.WeekId BETWEEN (20145227) AND (20155230)
AND p.ContentFlavor IN ('SD', 'HD')
AND p.VODEST IN ('VOD','EST')
AND p.Distributor IN ('M6SND')
GROUP BY CASE r.SourceId
WHEN '1' THEN 'ITUNES'
WHEN '2' THEN 'SFR'
WHEN '3' THEN 'ORANGE'
ELSE 'Others'
END
The result of the above query is:
source Volume Value
ITUNES 48316.0000 506067.2600
This result is perfectly OK since my source table RawData doesnt contain any values for SourceId 2 or 3.
But what I basically want is the result to look like is:
source Volume Value
ITUNES 48316.0000 506067.2600
SFR 0 0
ORANGE 0 0
Others 0 0
If there is no value corresponding to any column parameter then I need it to be 0
I assume this could be done using IF/ELSE but not sure how?
with the help of a CTE this is a way to do it. (replace the first query with something more dynamic if you want)
with myChoices (choices)
as (
select
choices
from (
values
('ITUNES'),
('SFR'),
('ORANGE'),
('Others')
) [ ] (choices)
),
myQuery ([source],[Volume],[Value])
as (
SELECT CASE r.SourceId
WHEN '1' THEN 'ITUNES'
WHEN '2' THEN 'SFR'
WHEN '3' THEN 'ORANGE'
ELSE 'Others'
END as source
, CAST(SUM (r.SalesVolume) AS DECIMAL(14, 4) ) AS Volume
, CAST(SUM (r.SalesVolume * r.CustomerPrice) AS DECIMAL(14, 4) ) AS Value
from Rawdata r
INNER JOIN Product p
ON p.ProductId = r.ProductId
INNER JOIN Calendar c
ON r.DayId = c.DayId
WHERE c.WeekId BETWEEN (20145227) AND (20155230)
AND p.ContentFlavor IN ('SD', 'HD')
AND p.VODEST IN ('VOD','EST')
AND p.Distributor IN ('M6SND')
GROUP BY CASE r.SourceId
WHEN '1' THEN 'ITUNES'
WHEN '2' THEN 'SFR'
WHEN '3' THEN 'ORANGE'
ELSE 'Others'
END
)
select
c.choices,
ISNULL(q.Volume,0)Volume,
ISNULL(q.Value,0)Value
from myChoices c
left join myQuery q on
c.choices = q.[source]
Create an inline view called "Product_Inline_View", which is like
(select 1 as SourceId, 'ITUNES' as source_name
union all
select 2 as SourceId, 'SFR' as source_name
union all
select 3 as SourceId, 'ORANGE' as source_name
)
Right Join the Product_Inline_view with the Query you have, but without the CASE.
And then do the group by.

Update Value without cursor

I have a table in the database.
Bill
ID Total Paid Status
1 1000 1000 Paid
2 500 400 Part Paid
3 700 0 Unpaid
4 200 0 Unpaid
Now the User pays PAID_AMT -> $900, which i want to distribute such that my table looks:
ID Total Paid Status
1 1000 1000 Paid
2 500 500 Paid
3 700 700 Paid
4 200 100 Part Paid
It can be easily done using cursor, but i want to avoid cursors.
Is it possible to achieve this using simple update queries with Output parameters.
Something like this
Update Bill
Set Paid = Total,
Status = 'Paid',
Output PAID_AMT = PAID_AMT - (Total-Paid )
where Total-Paid > PAID_AMT
The following query shows the amount owed, assuming SQL Server 2012:
select b.*,
sum(total - paid) over (order by id) as cumNotPaid
from bill b
You can now distribute the amount:
select b.*,
(case when cumNotPaid >= #AMOUNT then 0
when cumNotPaid - toBePaid <= #AMOUNT then toBePaid
else #AMOUNT - cumnotPaid
end) as PaidAmount
from (select b.*,
sum(total - paid) over (order by id) as cumNotPaid,
total - paid as ToBePaid
from bill b
) b
Now, this is an updatable CTE, so we can use this in an update statement:
with toupdate as (
(select b.*,
(case when cumNotPaid >= #AMOUNT then 0
when cumNotPaid - toBePaid <= #AMOUNT then toBePaid
else #AMOUNT - cumnotPaid
end) as PaidAmount
from (select b.*,
sum(total - paid) over (order by id) as cumNotPaid,
total - paid as ToBePaid
from bill b
) b
)
update toupdate
set paid = PaidAmount,
status = (case when total = paid then 'Paid' when total = 0 then 'UnPaid'
else 'PartPaid'
end);
I don't know what version of SQL server do you, if it 2008 then you could not use rolling sum with window function. You can try this recursive query:
declare #paid_amount int = 900
;with cte1 as (
select
b.id,
b.total - b.paid as diff,
row_number() over(order by id) as row_num
from Bill as b
where b.total <> b.paid
), cte2 as (
select
c1.id, c1.row_num, #paid_amount - c1.diff as paid_amount
from cte1 as c1
where c1.row_num = 1
union all
select
c1.id, c1.row_num, c2.paid_amount - c1.diff as paid_amount
from cte1 as c1
inner join cte2 as c2 on c2.row_num + 1 = c1.row_num
where c2.paid_amount > 0
)
update Bill set
Paid =
case
when c.paid_amount >= 0 then b.Total
else b.Total - b.Paid + c.paid_amount
end,
Status = case when c.paid_amount >= 0 then 'Paid' else 'Part Paid' end
from Bill as b
inner join cte2 as c on c.id = b.id
sql fiddle demo
for SQL server 2012 it's a bit easier:
declare #paid_amount int = 900
;with cte1 as (
select
b.id,
b.total - b.paid as amount_to_pay,
sum(b.total - b.paid) over(order by b.id) as amount
from Bill as b
where b.total <> b.paid
), cte2 as (
select
c.id,
#paid_amount - c.amount as remain_amount
from cte1 as c
where #paid_amount - c.amount + c.amount_to_pay >= 0
)
update Bill set
Paid =
case
when c.remain_amount >= 0 then b.Total
else b.Total - b.Paid + c.remain_amount
end,
Status = case when c.remain_amount >= 0 then 'Paid' else 'Part Paid' end
from Bill as b
inner join cte2 as c on c.id = b.id;
sql fiddle demo
If you are using SQL 2012 you can use the following:
DECLARE #PayAmount INT = 900;
WITH Dues AS
(
SELECT *, Total-Paid AS Due
FROM Bill
)
, Cumulative AS
(
SELECT *, SUM(Due) OVER (ORDER BY Id) AS CumulativeTotalDue
FROM Dues
)
, Payable AS
(
SELECT *, #PayAmount - CumulativeTotalDue AS AmountLeftAfterPaying
FROM Cumulative
)
, BillWithAmountToApplyRaw AS
(
SELECT *
, CASE
WHEN AmountLeftAfterPaying >= 0 THEN Due
ELSE Due + AmountLeftAfterPaying
END AS RawAmountToApply
FROM Payable
)
, BillWithAmountToApply AS
(
SELECT *, CASE WHEN RawAmountToApply < 0 THEN 0 ELSE RawAmountToApply END AS AmountToApply
FROM BillWithAmountToApplyRaw
)
That will give you the amount to apply in the AmountToApply column.
So you can use the above as
UPDATE BillWithAmountToApply
SET Paid = Paid + AmountToApply
FROM BillWithAmountToApply
(Use
SELECT *
FROM BillWithAmountToApply
to check it out first if you want)
SQL 2008 Version (less efficient because of the repeated joins, which aren't necessary in 2012):
WITH Dues AS
(
SELECT *, Total-Paid AS Due
FROM Bill
)
, CumulativeDue AS
(
SELECT base.Id, SUM(cumulative.Due) AS CumulativeTotalDue
FROM Dues base
JOIN Dues cumulative ON cumulative.Id <= base.Id
GROUP BY base.Id
)
, Cumulative AS
(
SELECT Dues.*, CumulativeDue.CumulativeTotalDue
FROM Dues
JOIN CumulativeDue ON CumulativeDue.Id = Dues.Id
)
... as above
Schema objects:
--BEGIN TRAN;
--CREATE TABLE Bill
--(
-- ID Int PRIMARY KEY IDENTITY,
-- Total INT NOT NULL,
-- Paid INT NOT NULL DEFAULT(0),
-- Status AS
-- CASE
-- WHEN Paid = Total THEN 'Paid'
-- WHEN Paid = 0 THEN 'Unpaid'
-- ELSE 'Part Paid'
-- END
--);
--WITH KnownValues(Total, Paid) AS
--(
-- SELECT 1000, 1000
-- UNION ALL SELECT 500, 400
-- UNION ALL SELECT 700, 0
-- UNION ALL SELECT 200, 0
--)
--INSERT INTO Bill(Total, Paid)
--SELECT *
--FROM KnownValues;
--COMMIT