issues in Case and when Statement - sql

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 )

Related

Why is a value not returned if another value is 0 in SQL query

I have a query that does not return the expense value if the drvalue(Debtor Value) is equal to 0 or NULL,
If i change the drvalue to any value greater than 0, query returns the expenses value.
Drvalue is a sum of all the values for a specific period
Below is the query
SELECT f.vehiclenumber,f.fleettype,f.IsCreditor,
ISNULL(SUM(l.DrValue),0) AS drvalue,
CASE WHEN iscreditor=1 then Sum (DrValue) - Sum(CrValue) else null end AS Profit,
sum(l.CrValue) AS CrValue ,
sum(l.distance) AS LoadDist,
sum(l.DrValue)/DDist AS DRVal ,
d.Liters AS Liters,d.ddist AS DDist, d.DDiesel AS DDiesel, d.DieselCost AS dieslCost,
(MAX(isnull(l.Closingkm,0))-MIN(isnull(l.OpeningKM,0))) AS [CO],ISNULL(SUM(l.DrValue),0) / CASE WHEN (isnull(MAX(l.Closingkm),0)-MIN(isnull(l.OpeningKM,0)))=0 THEN 1 ELSE (isnull(MAX(l.Closingkm),0)-MIN(isnull(l.OpeningKM,0))) END AS TotalCPK,
(d.DieselCost/sum(l.DrValue)) * 100 AS DieselPerc,
SUM(jobdetails.total) AS Expenses,
count(l.vehicleNo) AS LoadCount
FROM tblVehicle AS f
LEFT JOIN (SELECT Vehicleno, loaddate, DrValue, CrValue,Distance, OpeningKM, closingkm FROM tblloads WHERE DrValue IS NOT NULL) AS l ON f.VehicleNumber = l.VehicleNo AND l.loaddate >= '2020-06-01' and l.loaddate <= '2020-06-30'
LEFT JOIN (SELECT fleet , NULLIF(SUM(Liters),0) AS Liters,NULLIF(sum(distance),0) AS DDist, NULLIF(sum(distance)/ CASE WHEN sum(Liters)=0 THEN 1 ELSE sum(Liters) END,0) AS DDiesel, NULLIF(SUM (Manual_Amount),0) AS DieselCost FROM tblinput
WHERE [Date] >= '2020-06-01' And [Date] <= '2020-06-30'
GROUP BY Fleet) AS d ON l.VehicleNo = d.Fleet
LEFT JOIN (SELECT jd.fleet, SUM(Total) AS total From tblJobDetails jd, tbljobcards WHERE tbljobcards.JobID = jd.jobid AND jobdate >= '2020-06-01' AND jobdate <= '2020-06-30' GROUP BY jd.fleet) AS jobdetails ON jobdetails.fleet = l.vehicleno
WHERE VehicleCategory <> 'T'
GROUP BY f.vehiclenumber,f.fleettype,f.IsCreditor,d.Liters,d.ddist, d.DDiesel,d.DieselCost
ORDER BY fleettype
RESULTS

SQL server pivot count function works incorrectly if used with inner query

I think there is a bug in my SQL-server pivot function because I can't find any other explanation.
I am running a simple pivot and using the exact syntax shown in MSDN.
but the pivot answer shows the exact same number for all columns with the value equivalence of the count of all weeks!!!
(left is query result and right is what I want)
here is my query:
SELECT
*
FROM (SELECT
r.cutomer_id
,c.[Week]
,r.id
FROM r
JOIN c
ON r.Create_date = c.Date
WHERE Is_ride = 1
AND ((Create_date_int BETWEEN 20190302 AND 20190319)
OR (Create_date_int BETWEEN 20190406 AND 20190426))) p
PIVOT
(
COUNT(id)
FOR [Week] IN
([9], [10], [11], [12], [14], [15], [16], [17])
) AS pvt
here are some test data representing the "p" output (i just changed id numbers, weeks are the same number you get from the query)
DROP TABLE IF EXISTS #t
CREATE TABLE #t (
customer_id INT
,WEEK INT
,id INT
)
INSERT #t (customer_id, WEEK, id)
VALUES (12032, 10, 8607)
, (43551, 10, 8721051)
, (55025, 10, 81200)
, (198874, 10, 861362)
, (99675, 10, 867081)
, (19387, 10, 863656)
, (12526, 10, 8603706)
, (19503, 10, 860924)
, (37597, 10, 860909)
, (136019, 10, 8610674);
so I thought there is something wrong with my query but then I changed the query to this:
SELECT
r.cutomer_id
,c.[Week]
,r.id INTO #t
FROM r
JOIN c
ON r.Create_date = c.Date
WHERE Is_ride = 1
AND ((Create_date_int BETWEEN 20190302 AND 20190319)
OR (Create_date_int BETWEEN 20190406 AND 20190426))
SELECT
*
FROM #t
PIVOT
(
COUNT(id)
FOR [Week] IN
([9], [10], [11], [12], [14], [15], [16], [17])
) AS pvt
and it works just fine!
also if I remove the r.id column from select and change it to count(week) it works fine!!!
also if I only change my where to
WHERE Is_ride = 1
AND ((Create_date_int BETWEEN 20190302 AND 20190319)
OR (Create_date_int BETWEEN 20190406 AND 20190426))
and passenger_id in (43551,12032,136019)
) p
it works fine!!!
can someone give me an explanation?
I have no idea why you're getting the result shown on your images. There's possibly some information missing.
Here's an MVCE with code commented to create a larger set of test data.
CREATE TABLE r(
id int identity,
customer_id int,
create_date date,
Create_date_int AS CONVERT( int, CONVERT( char(8), create_date, 112)),
is_ride bit)
INSERT INTO r(customer_id, create_date, is_ride)
SELECT customer_id, '20190307', 1
FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(customer_id)
--SELECT TOP 100000
-- ABS(CHECKSUM(NEWID())) % 10,
-- DATEADD( dd, ABS(CHECKSUM(NEWID())) % 120, '2019'),
-- 1
--FROM sys.all_columns a, sys.all_columns b;
CREATE TABLE c(
[Date] date,
[Week] AS DATEPART( wk, [Date])
)
INSERT INTO c([Date])
SELECT DATEADD( dd, ROW_NUMBER() OVER( ORDER BY (SELECT NULL))-1, '2019')
FROM sys.all_columns;
From here, I could run your original query that gives the expected results, just 1's in week 10.
I can also run an alternative query using cross tabs that is my preferred method to pivot data.
SELECT
r.customer_id
,COUNT( CASE WHEN c.[Week] = 9 THEN r.id END) AS wk9
,COUNT( CASE WHEN c.[Week] = 10 THEN r.id END) AS wk10
,COUNT( CASE WHEN c.[Week] = 11 THEN r.id END) AS wk11
,COUNT( CASE WHEN c.[Week] = 12 THEN r.id END) AS wk12
,COUNT( CASE WHEN c.[Week] = 14 THEN r.id END) AS wk14
,COUNT( CASE WHEN c.[Week] = 15 THEN r.id END) AS wk15
,COUNT( CASE WHEN c.[Week] = 16 THEN r.id END) AS wk16
,COUNT( CASE WHEN c.[Week] = 17 THEN r.id END) AS wk17
FROM r
JOIN c ON r.Create_date = c.Date
WHERE Is_ride = 1
AND (Create_date_int BETWEEN 20190302 AND 20190319
OR Create_date_int BETWEEN 20190406 AND 20190426)
GROUP BY r.customer_id
ORDER BY r.customer_id;
That also gives the correct information.
Both of these methods might benefit from preaggregation to improve their performance.
WITH ctePreAggregate AS(
SELECT
r.customer_id
,c.[Week]
,COUNT(*) AS weeklycount
FROM r
JOIN c
ON r.Create_date = c.Date
WHERE Is_ride = 1
AND (Create_date_int BETWEEN 20190302 AND 20190319
OR Create_date_int BETWEEN 20190406 AND 20190426)
GROUP BY r.customer_id
,c.[Week]
)
SELECT
*
FROM ctePreAggregate
PIVOT
(
SUM(weeklycount)
FOR [Week] IN
([9], [10], [11], [12], [14], [15], [16], [17])
) AS pvt
ORDER BY customer_id;
WITH ctePreAggregate AS(
SELECT
r.customer_id
,c.[Week]
,COUNT(*) AS weeklycount
FROM r
JOIN c
ON r.Create_date = c.Date
WHERE Is_ride = 1
AND (Create_date_int BETWEEN 20190302 AND 20190319
OR Create_date_int BETWEEN 20190406 AND 20190426)
GROUP BY r.customer_id
,c.[Week]
)
SELECT
customer_id
,SUM( CASE WHEN [Week] = 9 THEN weeklycount ELSE 0 END) AS wk9
,SUM( CASE WHEN [Week] = 10 THEN weeklycount ELSE 0 END) AS wk10
,SUM( CASE WHEN [Week] = 11 THEN weeklycount ELSE 0 END) AS wk11
,SUM( CASE WHEN [Week] = 12 THEN weeklycount ELSE 0 END) AS wk12
,SUM( CASE WHEN [Week] = 14 THEN weeklycount ELSE 0 END) AS wk14
,SUM( CASE WHEN [Week] = 15 THEN weeklycount ELSE 0 END) AS wk15
,SUM( CASE WHEN [Week] = 16 THEN weeklycount ELSE 0 END) AS wk16
,SUM( CASE WHEN [Week] = 17 THEN weeklycount ELSE 0 END) AS wk17
FROM ctePreAggregate
GROUP BY customer_id
ORDER BY customer_id;
The only issue is that you would need to add a series of ISNULL() in the column list to show NULLs instead of zeros for the PIVOT query.

How to get Sql Temp Table Join with column values that are not in table, replaced by 0

I have 5 temp tables that I want to combine into a new temp table via the month_year column and the prod_id. Where the columns are not prod_id or month_year and values are not available, I want that column's value(e.g. job_amt or received_qty) to be 0.
#tmp_spoilt_good_job_amt
month_year job_amt spoil good prod_id
07-2017 40 10 20 2
08-2017 827 0 210 3
09-2017 27 1 27 2
09-2017 732 22 345 3
10-2017 50 0 6 2
10-2017 1130 55 50 3
11-2017 300 0 0 4
#tmp_received_qty
month_year received_qty prod_id
08-2017 32 2
08-2017 2500 3
09-2017 2200 2
11-2017 2500 4
#tmp_purchase_qty
month_year purchase_qty prod_id
09-2017 11 2
#tmp_opening_balance
month_year opening_balance prod_id
08-2017 32 2
08-2017 2500 3
09-2017 22 2
09-2017 2300 3
10-2017 2163 2
10-2017 2023 3
11-2017 2500 4
#tmp_closing_balance
month_year closing_balance prod_id
08-2017 2300 3
08-2017 32 2
09-2017 2213 2
09-2017 1998 3
10-2017 1687 3
10-2017 2163 2
11-2017 2400 4
I tried some inner joins but the values were repeating or some were not reflecting. what query could I use to get these combined?
I am looking for the following output:
CREATE TABLE #table_with_all_months_prod_ids_using_cross_join (month_year, prod_id)
GO
INSERT #table_with_all_months_prod_ids_using_cross_join
SELECT t1.month_year, t2.prod_id
FROM monthyeartable t1
CROSS JOIN prodidtable t2
SELECT DISTINCT t0.month_year, t0.prod_id, ISNULL(t1.job_amt,0), ISNULL(t1.spoil,0), ISNULL(t1.good,0), ISNULL(t2.received_qty,0), ISNULL(t3.purchase_qty,0), ISNULL(t4.opening_balance,0), ISNULL(t5.closing_balance,0)
FROM #table_with_all_months_prod_ids_using_cross_join t0
LEFT JOIN #tmp_spoilt_good_job_amt t1 ON t0.month_year = t1.month_year AND t0.prod_id = t1.prod_id
LEFT JOIN #tmp_received_qty t2 ON t0.month_year = t2.month_year AND t0.prod_id = t2.prod_id
LEFT JOIN #tmp_purchase_qty t3 ON t0.month_year = t3.month_year AND t0.prod_id = t3.prod_id
LEFT JOIN #tmp_opening_balance t4 ON t0.month_year = t4.month_year AND t0.prod_id = t4.prod_id
LEFT JOIN #tmp_closing_balance t5 ON t0.month_year = t5.month_year AND t0.prod_id = t5.prod_id
WHERE NOT (t1.job_amt IS NULL AND t1.spoil IS NULL AND t1.good IS NULL AND t2.received_qty IS NULL AND t3.purchase_qty IS NULL AND t4.opening_balance IS NULL AND t5.closing_balance IS NULL)
So, this kind of relation ships have very week performance, first of all it is better to change the tables structure and all other thing that related to fill data in you database.
Any way, this query created for your current needs with current data structure, also if you work on this query or divided it in to some views maybe you can improve performance:
SELECT CASE WHEN ISNULL(srpo.month_year , '-1') <> '-1' THEN srpo.month_year
WHEN ISNULL(c.month_year , '-1') <> '-1' THEN c.month_year
END AS month_year,
CASE WHEN ISNULL(srpo.prod_id, -1) <> -1 THEN srpo.prod_id
WHEN ISNULL (c.prod_id, -1) <> -1 THEN c.prod_id
END AS prod_id,
CASE WHEN ISNULL(c.closing_balance, -1) = -1 THEN 0 else c.closing_balance END AS closing_balance,
srpo.job_amt,srpo.spoil,srpo.good ,srpo.received_qty,srpo.purchase_qty, srpo.opening_balance
FROM #tmp_closing_balance AS c FULL OUTER JOIN
(SELECT CASE WHEN ISNULL(srp.month_year , '-1') <> '-1' THEN srp.month_year
WHEN ISNULL(o.month_year , '-1') <> '-1' THEN o.month_year
END AS month_year,
CASE WHEN ISNULL(srp.prod_id, -1) <> -1 THEN srp.prod_id
WHEN ISNULL (o.prod_id, -1) <> -1 THEN o.prod_id
END AS prod_id,
CASE WHEN ISNULL(o.opening_balance, -1) = -1 THEN 0 else o.opening_balance END AS opening_balance,
srp.job_amt,srp.spoil,srp.good ,srp.received_qty,srp.purchase_qty
FROM #tmp_opening_balance AS o FULL OUTER JOIN
(SELECT CASE WHEN ISNULL(sr.month_year , '-1') <> '-1' THEN sr.month_year
WHEN ISNULL(p.month_year , '-1') <> '-1' THEN p.month_year
END AS month_year,
CASE WHEN ISNULL(sr.prod_id, -1) <> -1 THEN sr.prod_id
WHEN ISNULL (p.prod_id, -1) <> -1 THEN p.prod_id
END AS prod_id,
CASE WHEN ISNULL(p.purchase_qty, -1) = -1 THEN 0 else p.purchase_qty END AS purchase_qty,
sr.job_amt,sr.spoil,sr.good ,sr.received_qty
FROM #tmp_purchase_qty AS p FULL OUTER JOIN
(SELECT CASE WHEN ISNULL(s.month_year , '-1') <> '-1' THEN s.month_year
WHEN ISNULL(r.month_year , '-1') <> '-1' THEN r.month_year
END AS month_year,
CASE WHEN ISNULL(s.prod_id, -1) <> -1 THEN s.prod_id
WHEN ISNULL (r.prod_id, -1) <> -1 THEN r.prod_id
END AS prod_id,
CASE WHEN ISNULL(s.job_amt, -1) = -1 THEN 0 else s.job_amt END AS job_amt,
CASE WHEN ISNULL(s.spoil, -1) = -1 THEN 0 else s.spoil END AS spoil,
CASE WHEN ISNULL(s.good, -1) = -1 THEN 0 else s.good END AS good,
CASE WHEN ISNULL(r.received_qty, -1) = -1 THEN 0 else r.received_qty END AS received_qty
FROM #tmp_spoilt_good_job_amt AS s FULL OUTER JOIN
#tmp_received_qty AS r ON s.prod_id = r.prod_id AND
LTRIM(rtrim(s.month_year)) = LTRIM(rtrim(r.month_year))) AS sr ON
sr.prod_id = p.prod_id AND LTRIM(rtrim(sr.month_year)) = LTRIM(rtrim(p.month_year)) ) AS srp ON
srp.prod_id = o.prod_id AND LTRIM(rtrim(srp.month_year)) = LTRIM(rtrim(o.month_year))) AS srpo ON
srpo.prod_id = c.prod_id AND LTRIM(rtrim(srpo.month_year)) = LTRIM(rtrim(c.month_year))
I use FULL OUTER JOIN for all part of query because you mentioned that, may be all tables have possible values for keys columns(month_year and prod_id).
First if you have these 5 temp tables that probably means there is a much much much better way of doing this at the source table level! But because you asked you most significant problem with combining them is that not 1 of the tables hold a combination of every month_year and prod_id. So you have to create it. The way I choose to do this for completeness sake is to:
Create a Tally table (as a common table expression [CTE]) for use in
generating a month_year table
Create a Products CTE by unioning all distinct prod_ids from your temp tables
Create a MonthYearInputs CTE to be able to determine the Max and Min month_years represented
Generate a MonthYear CTE to house every possible month_year combination between the MIN & MAX years represented in your data
Then a cartisean (CROSS) join between the MonthYear & Product ctes give you all of the combinations to LEFT JOIN the other tables to.
Simply put a where statement in to remove the rows that have no values in ALL of the tables and use ISNULL() or COALESCE() to make the null values 0.
Here is a working example: http://rextester.com/MCEO96178
;WITH cteTen AS (
SELECT n FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n)
)
, cteTally AS (
SELECT
n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cteTen t1
CROSS JOIN cteTen t2 --hundreds
CROSS JOIN cteTen t3 --thousands
--keep cross joining if need more than 1000 month
)
, cteProducts AS (
SELECT DISTINCT prod_id FROM #tmp_spoilt_good_job_amt
UNION
SELECT DISTINCT prod_id FROM #tmp_received_qty
UNION
SELECT DISTINCT prod_id FROM #tmp_purchase_qty
UNION
SELECT DISTINCT prod_id FROM #tmp_opening_balance
UNION
SELECT DISTINCT prod_id FROM #tmp_closing_balance
)
, cteInputMonthYears AS (
SELECT DISTINCT month_year FROM #tmp_spoilt_good_job_amt
UNION
SELECT DISTINCT month_year FROM #tmp_received_qty
UNION
SELECT DISTINCT month_year FROM #tmp_purchase_qty
UNION
SELECT DISTINCT month_year FROM #tmp_opening_balance
UNION
SELECT DISTINCT month_year FROM #tmp_closing_balance
)
, cteMaxMinMonthYears AS (
SELECT
MinMonthYear = CAST(STUFF(MIN(month_year),3,0,'-01') AS DATETIME)
,MonthsDiff = DATEDIFF(MONTH,CAST(STUFF(MIN(month_year),3,0,'-01') AS DATETIME),CAST(STUFF(MAX(month_year),3,0,'-01') AS DATETIME)) + 1
FROM
cteInputMonthYears
)
, cteMonthYears AS (
SELECT
month_year = FORMAT(DATEADD(MONTH, t.n - 1, m.MinMonthYear),'MM-yyyy')
FROM
cteMaxMinMonthYears m
INNER JOIN cteTally t
ON m.MonthsDiff >= t.n
)
SELECT
my.month_year
,job_amt = ISNULL(ja.job_amt,0)
,spoil = ISNULL(ja.spoil,0)
,good = ISNULL(ja.good,0)
,p.prod_id
,received_qty = ISNULL(r.received_qty,0)
,purchase_qty = ISNULL(pur.purchase_qty,0)
,opening_balance = ISNULL(o.opening_balance,0)
,closing_balance = ISNULL(c.closing_balance,0)
FROM
cteMonthYears my
CROSS JOIN cteProducts p
LEFT JOIN #tmp_spoilt_good_job_amt ja
ON my.month_year = ja.month_year
AND p.prod_id = ja.prod_id
LEFT JOIN #tmp_received_qty r
ON my.month_year = r.month_year
AND p.prod_id = r.prod_id
LEFT JOIN #tmp_purchase_qty pur
ON my.month_year = pur.month_year
AND p.prod_id = pur.prod_id
LEFT JOIN #tmp_opening_balance o
ON my.month_year = o.month_year
AND p.prod_id = o.prod_id
LEFT JOIN #tmp_closing_balance c
ON my.month_year = c.month_year
AND p.prod_id = c.prod_id
WHERE
NOT(ja.month_year IS NULL
AND r.month_year IS NULL
AND pur.month_year IS NULL
AND o.month_year IS NULL
AND o.month_year IS NULL
AND c.month_year IS NULL)
ORDER BY
my.month_year
,p.prod_id
Personally, I think you should go with #influent's suggest: derive a template table on to which you can left the values you're looking for.
In the eventuality that you don't have the required logic or data to accurately derive such a template table, there is another option.
1. Pad out each table with dummy 0 values, so that they all have the same fields
2. UNION all the tables together
3. GROUP all the results back down to one row per month per product
WITH
padded_combined
AS
(
SELECT month_year, prod_id, job_amt, spoil, good, 0 AS received_qty, 0 AS purchase_qty, 0 AS opening_balance, 0 AS closing_balance FROM #tmp_spoilt_good_job_amt
UNION ALL
SELECT month_year, prod_id, 0, 0, 0, received_qty, 0, 0, 0 FROM #tmp_received_qty
UNION ALL
SELECT month_year, prod_id, 0, 0, 0, 0, purchase_qty, 0, 0 FROM #tmp_purchase_qty
UNION ALL
SELECT month_year, prod_id, 0, 0, 0, 0, 0, opening_balance, 0 FROM #tmp_opening_balance
UNION ALL
SELECT month_year, prod_id, 0, 0, 0, 0, 0, 0, closing_balance FROM #tmp_closing_balance
)
SELECT
month_year,
prod_id,
SUM(job_amt) AS job_amt,
SUM(spoil) AS spoil,
SUM(good) AS good,
SUM(received_qty) AS received_qty,
SUM(purchase_qty) AS purchase_qty,
SUM(opening_balance) AS opening_balance,
SUM(closing_balance) AS closing_balance
FROM
padded_combined
GROUP BY
month_year,
prod_id
ORDER BY
month_year,
prod_id

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

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

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