Subtotaling with Ranges and Limited Compatibility - sql

On SQL Server 2008 R2, I am trying to create a stored procedure that will give the user subtotals of commodities based on their inputted range. I would appreciate any assistance.
However, I ran into a problem in which the query displays All commodities that are being calculated so the user ends with values that will eventually exceed their inputted range. I'll include some sample data on the bottom as well as what I have tried.
Here's an example (that I took out some columns in for clarity in this example):
table
As you can tell, for a range between 2000 and 10000, commodity 998-32 is going to exceed 10000 in subtotals but still displays since each PO NO is individually less than 10000.
Here is some sample data:
DROP TABLE ##mytable;
CREATE TABLE ##mytable(
Commodity VARCHAR(15) NOT NULL
,PO_NO INTEGER NOT NULL
,LINE_NO INTEGER NOT NULL
,PO_Line_Description VARCHAR(82)
,Commodity_Description VARCHAR(60) NOT NULL
,Fiscal_Year INTEGER NOT NULL
,Vendor_ID INTEGER NOT NULL
,Vendor_Name VARCHAR(20) NOT NULL
,QUANTITY INTEGER NOT NULL
,UNIT_COST NUMERIC(7,2) NOT NULL
,Line_Amount NUMERIC(7,2) NOT NULL
);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-18',1448923,1,'Face Scholastic book order attached.','Sale of Surplus and Obsolete Books',2015,47650,'SCHOLASTIC INC',1,9999.8,9999.8);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1416311,2,'First 12 months maintenance agreement to be billed quarterly at .0039 per b/w copy','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,5148,5148);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1424377,1,NULL,'Sale of Surplus and Obsolete Copy Machines',2015,300590,'KONICA MINOLTA/ CIT',1,2894.58,2894.58);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1404031,1,'1st (12) months of (36) month Lease payment for (1) MPC4503 copier.','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,2050.68,2050.68);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-75',1401552,1,'Blanket order for 50 teachers - each teacher not to exceed $100.00.','Sale of Surplus and Obsolete Paper and Paper Products',2015,27536,'KNOWLEDGE TREE',1,5000,5000);
INSERT INTO ##mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-78',1521390,1,'PL02286>PRESSURE PLUMBER INSTANT DRAIN OPENER 24 SHOT CARTRIDGE','Sale of Surplus and Obsolete Plumbing Equipment and Supplies',2015,402985,'TECH MECH SUPPLY LLC',480,8,3840);
I have tried using a SUM() OVER (PARTITION BY [ ] ORDER BY [ ] ROWS UNBOUNDED PRECEDING) but apparently that the OVER clause with a ROW was introduced in SQL Server 2012 and does not work with 2008 R2.
I have also tried using GROUP BY ROLLUP but I get a message that says "The CUBE() and ROLLUP() grouping constructs are not allowed in the current compatibility mode. They are only allowed in 100 mode or higher." When I asked our DBA, he said we can't move to 100 mode from 90 mode because a lot of things will break.
So now I'm stuck with the below query, which has the problem stated earlier, the issue of giving me the data I want but also the subtotals of data that will eventually exceed my specified range.
P.S. I have also noticed that if the commodity has PO NOs that are within my selected range but also PO NOs with costs outside of it, this query will give me those within the selected range which is very misleading since it is still a commodity that will be outside the selected range if it were all calculated. They shouldn't be listed into the results at all with the commodities that are truly within my selected range.
ALTER PROCEDURE [dbo].[POreport] (
#Param1 INT
,#Param2 INT
,#Param3 INT
)
AS
BEGIN
SET NOCOUNT ON;
SELECT DISTINCT ROW_NUMBER() OVER (
ORDER BY T.Product_ID
,T.PO_NO
,T.LINE_NO
) AS [RowID]
,ISNULL(T.PRODUCT_ID, 'NULL') AS [Commodity]
,ISNULL(T.PO_NO, 'NULL') AS [PO NO]
,ISNULL(T.LINE_NO, 'NULL') AS [LINE NO]
,QUOTENAME(T.DESCRIPTION, '"') AS [PO Line Description]
,QUOTENAME(C.DESCRIPTION, '"') AS [Commodity Description]
,ISNULL(T.FY, 'NULL') AS [Fiscal Year]
,PH.Vendor_ID AS [Vendor ID]
,QUOTENAME(V.Vendor_Name, '"') AS [Vendor Name]
,T.QUANTITY
,T.UNIT_COST
,T.QUANTITY * T.UNIT_COST AS [Line Amount]
,(
SELECT CAST(0.00 AS NUMERIC(10, 2))
) AS Sub_Total_Cost
INTO ##TmpPOReport
FROM dbo.DBVW_FI_REQ_PO_ITEMS T
INNER JOIN dbo.FI_VENDOR FV ON T.INST_ID = FV.INST_ID
INNER JOIN dbo.FI_REQ_PO_HEADER PH ON T.PO_NO = PH.PO_NO
INNER JOIN dbo.FI_VENDOR V ON PH.VENDOR_ID = V.VENDOR_ID
INNER JOIN dbo.FI_COMMODITY C ON T.PRODUCT_ID = C.FI_COMMODITY_CODE
WHERE T.INST_ID = 'SC00'
AND T.FY = #Param1
AND V.VENDOR_TYPE = 'V'
AND T.PO_NO IS NOT NULL
AND (
T.PRODUCT_ID <> ''
AND T.PRODUCT_ID IS NOT NULL
)
AND T.QUANTITY * T.UNIT_COST BETWEEN #Param2
AND #Param3
GROUP BY T.PRODUCT_ID
,T.PO_NO
,T.LINE_NO
,T.DESCRIPTION
,C.DESCRIPTION
,T.FY
,PH.Vendor_ID
,V.Vendor_Name
,T.QUANTITY
,T.UNIT_COST
,PH.Created_Date
ORDER BY Commodity
DECLARE #PID VARCHAR(15) = 00
,#QUANTITY INT
,#UNIT_COST NUMERIC(10, 2)
,#PrevID VARCHAR(15)
,#RowID BIGINT
,#PrevRowID BIGINT
,#RowAmount NUMERIC(10, 2)
,#SubTotal NUMERIC(10, 2) = 0.00
SET NUMERIC_ROUNDABORT OFF;
WHILE EXISTS (
SELECT TOP 1 *
FROM ##TmpPOReport
WHERE Sub_Total_Cost = 0.00
)
BEGIN
SET #RowAmount = (
SELECT TOP 1 (QUANTITY * UNIT_COST)
FROM ##TmpPOReport
WHERE Sub_Total_Cost = 0.00
)
SELECT TOP 1 #PID = Commodity
,#RowID = RowID
FROM ##TmpPOReport
WHERE Sub_Total_Cost = 0.00
IF (#PID = #PrevID)
AND (#RowID <> #PrevRowID)
BEGIN
SET #SubTotal += #RowAmount;
UPDATE T
SET Sub_Total_Cost = #SubTotal
FROM ##TmpPOReport T
WHERE T.Commodity = #PID
AND RowID = #RowID
SET #PrevID = #PID;
SET #PrevRowID = #RowID
END
ELSE
BEGIN
SET #SubTotal = #RowAmount;
UPDATE T
SET Sub_Total_Cost = #SubTotal
FROM ##TmpPOReport T
WHERE T.Commodity = #PID
AND RowID = #RowID
SET #PrevID = #PID;
SET #PrevRowID = #RowID
END
END
SET NUMERIC_ROUNDABORT ON;
SELECT *
FROM ##TmpPOReport
WHERE [Line Amount] BETWEEN #Param2
AND #Param3
DROP TABLE ##TmpPOReport
END
Thanks!

Sorry if this is not clear. If you want to duplicate the SUM OVER ROWS then you will need to use a CTE or similar sub query below. This is not an optimized query but it will give you the running sum over commodity. It uses RANK() OVER PARTITION to get an instance or row number per PO within a commodity.
DECLARE #mytable TABLE(
Commodity VARCHAR(15) NOT NULL
,PO_NO INTEGER NOT NULL
,LINE_NO INTEGER NOT NULL
,PO_Line_Description VARCHAR(82)
,Commodity_Description VARCHAR(60) NOT NULL
,Fiscal_Year INTEGER NOT NULL
,Vendor_ID INTEGER NOT NULL
,Vendor_Name VARCHAR(20) NOT NULL
,QUANTITY INTEGER NOT NULL
,UNIT_COST NUMERIC(7,2) NOT NULL
,Line_Amount NUMERIC(7,2) NOT NULL
);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-18',1448923,1,'Face Scholastic book order attached.','Sale of Surplus and Obsolete Books',2015,47650,'SCHOLASTIC INC',1,9999.8,9999.8);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1416311,2,'First 12 months maintenance agreement to be billed quarterly at .0039 per b/w copy','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,5148,5148);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1424377,1,NULL,'Sale of Surplus and Obsolete Copy Machines',2015,300590,'KONICA MINOLTA/ CIT',1,2894.58,2894.58);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-32',1404031,1,'1st (12) months of (36) month Lease payment for (1) MPC4503 copier.','Sale of Surplus and Obsolete Copy Machines',2015,341479,'RICOH USA, INC',1,2050.68,2050.68);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-75',1401552,1,'Blanket order for 50 teachers - each teacher not to exceed $100.00.','Sale of Surplus and Obsolete Paper and Paper Products',2015,27536,'KNOWLEDGE TREE',1,5000,5000);
INSERT INTO #mytable(Commodity,PO_NO,LINE_NO,PO_Line_Description,Commodity_Description,Fiscal_Year,Vendor_ID,Vendor_Name,QUANTITY,UNIT_COST,Line_Amount) VALUES ('998-78',1521390,1,'PL02286>PRESSURE PLUMBER INSTANT DRAIN OPENER 24 SHOT CARTRIDGE','Sale of Surplus and Obsolete Plumbing Equipment and Supplies',2015,402985,'TECH MECH SUPPLY LLC',480,8,3840);
DECLARE #FiscalYear INT
DECLARE #LowTotalCost INT
DECLARE #HighTotalCost INT
SET #FiscalYear=2015
SET #LowTotalCost=1000
SET #HighTotalCost=10000
SELECT
*
FROM
(
SELECT Commodity,PO_NO,QUANTITY,UNIT_COST,Line_Amount,
RunningTotal=
(
SELECT SUM(TotalCost) FROM
(
SELECT Commodity,TotalCost =(MT.QUANTITY * MT.UNIT_COST),
Instance=RANK()OVER(PARTITION BY Commodity ORDER BY Commodity,PO_NO DESC)
FROM
#mytable MT
WHERE
(MT.QUANTITY * MT.UNIT_COST) BETWEEN #LowTotalCost AND #HighTotalCost
AND
(MT.Fiscal_Year=#FiscalYear)
)AS Y
WHERE
(Y.Commodity=X.Commodity) AND(Y.Instance<=X.Instance)
)
FROM
(
SELECT Commodity,PO_NO,QUANTITY,UNIT_COST,Line_Amount,
TotalCost = (MT.QUANTITY * MT.UNIT_COST),
Instance=RANK()OVER(PARTITION BY Commodity ORDER BY Commodity,PO_NO DESC)
FROM
#mytable MT
WHERE
--IS FILTER HERE -->(MT.QUANTITY * MT.UNIT_COST) BETWEEN #LowTotalCost AND #HighTotalCost
--AND
(MT.Fiscal_Year=#FiscalYear)
)AS X
)AS Z
WHERE
(Commodity IN
(
SELECT Commodity FROM
(
SELECT
Commodity,
Total=SUM(QUANTITY * UNIT_COST)
FROM
#mytable mt2
WHERE
mt2.Fiscal_Year=#FiscalYear
GROUP BY
Commodity
)AS A
WHERE
A.Total BETWEEN #LowTotalCost AND #HighTotalCost
)
)
SELECT
*
FROM
#mytable MT

Related

Get unique value from lines else take default value in SQL

I have a list of sales lines each having a currency field.
A sale line is represented by the table SALE_LINE which has a field, Currency, to store the currency of the sales line.
I want to do an SQL request which will take a list of sales line and if the currency of these sales lines is unique to them all,
then take that currency or else set the value to a default one like the dollar.
As in if I have 3 lines with currencies: EUR, GBP, ZAR, the request would return me the DOL.
if I have 3 lines with currencies: EUR, EUR, EUR, the request would return me the EUR.
My SQL request is as below:
DECLARE
#salesLineIds VARCHAR(MAX),
#defaultCurrency VARCHAR(10),
#uniqueCurrency VARCHAR(10)
SET #salesLineIds = '1,2,3';
SET defaultCurrency = 'DOL';
DECLARE
#ID_LIST table (Id BIGINT)
INSERT INTO #ID_LIST SELECT TRY_CAST(value AS BIGINT)
FROM STRING_SPLIT(#salesLineIds, ',')
SELECT #uniqueCurrency =(CASE WHEN 1 = (SELECT COUNT(DISTINCT SL.Currency))
THEN
(SELECT DISTINCT SL_CUR.Currency
from SALE_LINE SL_CUR
where SL_CUR.Id in (SELECT * FROM #ID_LIST)
)
ELSE
#defaultCurrency
END)
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST)
The above works but it might not be good performance. Is there any way where I can improve the performance of the above query?
The below query does not give the good currency:
SELECT #uniqueCurrency =(CASE WHEN 1 = (SELECT COUNT(DISTINCT SL.Currency))
THEN
(SELECT DISTINCT SL.Currency
)
ELSE
#defaultCurrency
END)
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST)
group by SL.Currency
Any help of how I can do better in the working SQL will be appreciated. Thanks
You need to group over the whole set, then simply check for distinct Currency values. If there is only one then you can just use any aggregation, such as MIN, to get it.
DECLARE
#salesLineIds VARCHAR(MAX),
#defaultCurrency VARCHAR(10),
#uniqueCurrency VARCHAR(10);
SET #salesLineIds = '1,2,3';
SET defaultCurrency = 'DOL';
DECLARE
#ID_LIST table (Id BIGINT);
INSERT INTO #ID_LIST SELECT TRY_CAST(value AS BIGINT)
FROM STRING_SPLIT(#salesLineIds, ',');
SELECT #uniqueCurrency =
CASE WHEN COUNT(DISTINCT SL.Currency) = 1
THEN MIN(SL.Currency)
ELSE #defaultCurrency
END
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST);
I suggest CHAR(3) as a suitable data type for currency
Assuming no nulls.
SELECT
CASE WHEN min(SL.Currency) = max(SL.Currency)
THEN min(SL.Currency)
ELSE #defaultCurrency
END
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST);

Improve INSERT INTO SELECT Query performance?

I am trying to improve old while loop query. So far tried this but it is still slow. Not sure may be SUM perform slow.
Old query (10 mins+)
ALTER PROCEDURE [dbo].[process_tax]
#userid VARCHAR(10),
#remark NVARCHAR(500),
#tdate DATE,
#roadno NVARCHAR(10),
#inst INT
AS
BEGIN
IF OBJECT_ID('tempdb..#tempProcess_tax_1') IS NOT NULL
DROP TABLE #tempProcess_tax_1
CREATE TABLE #tempProcess_tax_1
(
RowID INT IDENTITY(1, 1),
clid_ INT,
hlid_ INT,
holdinNo_ NVARCHAR(500),
holding_ NVARCHAR(50),
clientid_ NVARCHAR(500),
clientName_ NVARCHAR(500)
)
INSERT INTO #tempProcess_tax_1 (clid_, hlid_, holdinNo_, holding_, clientid_, clientName_)
SELECT
cl.clid AS clid_, cl.id AS hlid_, holdinNo, holding,
ClientID AS clientid_, ClientName AS clientName_
FROM
tx_holding AS cl
WHERE
cl.status = 1 AND cl.roadno = #roadno
AND cl.id IN (SELECT hlid FROM tx_asset WHERE asset IS NOT NULL)
AND cl.clid IN (SELECT id FROM tbl_client WHERE client_type = 'Non-Govt.')
AND cl.id NOT IN (SELECT hlid FROM tx_bill_pay
WHERE YEAR(date_month) = YEAR(#tdate)
AND hlid IS NOT NULL
GROUP BY hlid)
DECLARE #NumberRecords_1 INT, #RowCounter_1 INT
SET #NumberRecords_1 = (SELECT COUNT(*) FROM #tempProcess_tax_1)
SET #RowCounter_1 = 1
WHILE #RowCounter_1 <= #NumberRecords_1
BEGIN
DECLARE #clid_ INT
DECLARE #hlid_ INT
DECLARE #holdinNo_ NVARCHAR(50)
DECLARE #holding_ NVARCHAR(50)
DECLARE #clientid_ NVARCHAR(100)
DECLARE #clientName_ NVARCHAR(250)
DECLARE #bill AS MONEY
DECLARE #sr AS MONEY;
SELECT
#clid_ = clid_,
#hlid_ = hlid_,
#holdinNo_ = holdinNo_, #holding_ = holding_,
#clientid_ = clientid_, #clientName_ = clientName_
FROM
#tempProcess_tax_1
WHERE
RowID = #RowCounter_1
SET #bill = (SELECT
CASE WHEN SUM(netvalue) IS NULL
THEN 0
ELSE SUM(netvalue)
END
FROM
tx_bill
WHERE
hlid = #hlid_
AND itemID NOT IN (8, 6)
AND YEAR(date_month) = YEAR(#tdate))
SET #sr = (SELECT
CASE WHEN SUM(asset * rate / 100) IS NULL
THEN 0
ELSE SUM(asset * rate / 100)
END
FROM
tx_bill
WHERE
hlid = #hlid_
AND itemID = 6
AND YEAR(date_month) = YEAR(#tdate))
INSERT INTO tx_bill_pay(clid, hlid, swercharge, pay_bill, pdate, bill_id, holdingNo, holding, ClientID, ClientName, billno, date_month, bill, install, inserted_by, inserted_date)
VALUES (#clid_, #hlid_, #sr, #bill / 4, DATEADD(day, -1, DATEADD(m, 3, #tdate)), CONCAT(#holdinNo_, YEAR(#tdate), '1'), #holdinNo_, #holding_, #clientid_, #clientName_, CONCAT(#holdinNo_, YEAR#tdate)), #tdate, #bill, 1, #userid, GETDATE())
INSERT INTO tx_bill_pay(clid, hlid, swercharge, pay_bill, pdate, bill_id, holdingNo, holding, ClientID, ClientName, billno, date_month, bill, install, inserted_by, inserted_date)
VALUES (#clid_, #hlid_, 0, 2 * (#bill / 4), DATEADD(day, -1, DATEADD(m, 6, #tdate)), CONCAT(#holdinNo_, YEAR(#tdate), '2'), #holdinNo_, #holding_, #clientid_, #clientName_, CONCAT(#holdinNo_, YEAR(#tdate)), #tdate, #bill, 2, #userid, GETDATE())
SET #RowCounter_1 = #RowCounter_1 + 1
END
DROP TABLE #tempProcess_tax_1
END
New query (1-2 mins)
ALTER PROCEDURE [dbo].[process_tax]
#userid varchar(10),
#remark nvarchar(500),
#tdate date ,
#roadno nvarchar(10),
#inst int
as
BEGIN
insert into tx_bill_pay(
clid,
hlid,
swercharge,
pay_bill,
pdate,
bill_id,
holdingNo,
holding,
ClientID,
ClientName,
billno,
date_month,
bill,
install ,
inserted_by,
inserted_date)
select
cl.clid,
cl.id,
swercharge=(select case when sum(asset*rate/100) is null then 0 else
sum(asset*rate/100) end from tx_bill where hlid=cl.id and
itemID =6 and year(date_month)=YEAR(#tdate)),
pay_bill=(select case when sum(netvalue) is null then 0 else
sum(netvalue) end from tx_bill where hlid=cl.id and itemID not
in(8,6) and year(date_month)=YEAR(#tdate))/4,
DATEADD(day,-1,
DATEADD(m,3,#tdate)),
CONCAT(cl.holdinNo, year(#tdate),'1'),
cl.holdinNo,
cl.holding,
cl.ClientID,
cl.clientName,
CONCAT(cl.holdinNo,
year(#tdate)),
#tdate,
bill=(select case when sum(netvalue) is null then 0 else sum(netvalue)
end from tx_bill where hlid=cl.id and itemID not in(8,6) and
year(date_month)=YEAR(#tdate))/4,
1,
#userid, getdate()
from
(select *
from tx_holding as cl
where cl.status=1 and cl.roadno=#roadno) AS cl
INNER JOIN (
select DISTINCT hlid from tx_asset where asset is not null
) AS A
ON Cl.id = A.hlid
INNER JOIN (
select DISTINCT id from tbl_client where client_type='Non-Govt.'
) AS C
ON cl.clid=C.id
WHERE NOT EXISTS
( SELECT 1
FROM tx_bill_pay as bp
WHERE year(date_month)=year(#tdate)
and bp.hlid=cl.id
)
insert into tx_bill_pay(clid,hlid
,swercharge,pay_bill,pdate,bill_id,holdingNo,holding,ClientID,
ClientName, billno, date_month, bill, install ,inserted_by,
inserted_date)
select
cl.clid,
cl.id,
0,
pay_bill=2*((select case when sum(netvalue) is null then 0 else sum(netvalue) end from tx_bill where hlid=cl.id and itemID not in(8,6) and year(date_month)=YEAR(#tdate))/4),
DATEADD(day,-1,
DATEADD(m,3,#tdate)),
CONCAT(cl.holdinNo, year(#tdate),'2'),
cl.holdinNo,
cl.holding,
cl.ClientID,
cl.clientName,
CONCAT(cl.holdinNo, year(#tdate)) ,
#tdate,
bill=(select case when sum(netvalue) is null then 0 else sum(netvalue)
end from tx_bill where hlid=cl.id and itemID not in(8,6) and year(date_month)=YEAR(#tdate))/4,
2,
#userid, getdate()
from
(select *
from tx_holding as cl
where cl.status=1 and cl.roadno=#roadno) AS cl
INNER JOIN (
select DISTINCT hlid from tx_asset where asset is not null
) AS A
ON Cl.id = A.hlid
INNER JOIN (
select DISTINCT id from tbl_client where client_type='Non-Govt.'
) AS C
ON cl.clid=C.id
WHERE cl.id not in
( SELECT hlid
FROM tx_bill_pay
WHERE year(date_month)=year(#tdate)
and hlid is not null group by hlid
)
Great Job removing the loop!
I will point out one possible performance problem, specifically year(date_month)=year(#tdate).
Because a column is wrapped in a function, it is non-SARGABLE. This means that date_month values cannot be evaluated directly and therefore, indexes and statistics for this column cannot be used.
To resolve this I suggest the following changes:
At the top of the SP define two more variables:
DECLARE #YearStart AS DATETIME, #NextYearStart DATETIME
SET #YearStart = DATEADD(yy, DATEDIFF(yy, 0, #tdate ), 0 )
SET #NextYearStart = DATEADD( yy, #YearStart, 1 )
Then replace all instances of year(date_month)=year(#tdate) with
#YearStart <= date_month AND date_month < #NextYearStart
This above expression looks for date_month values greater than or equal to midnight (notice that time component is accounted for) of the first day of the year and less than midnight of the next year.
Next, I would look at the query plan and see if SQL Server gives a "Missing Index" recommendation (it should appear just above the plan diagram, if it does suggest an index). Try adding the recommended missing indexes, then check to see if you get a performance improvement. If you don't get an improvement, remove the index - sometimes suggestions don't help.
Updated
Having 3 sub-queries (populating columns swercharge, pay_bill, bill) using tx_bill table with different WHERE conditions will cause at least 3 evaluations of this table per one main query execution.
It may, depending on the size of the table, be more efficient to calculate all 3 sub-queries once and save results to a temp table (or a table variable) as follows:
SELECT hlid,
ISNULL( SUM( CASE WHEN itemID NOT IN (8, 6) THEN netvalue END ), 0 ) AS Bill,
ISNULL( SUM( CASE WHEN itemID = 6 THEN asset * rate / 100 END ), 0 ) AS Sr
INTO #Bills
FROM tx_bill
WHERE #YearStart <= date_month AND date_month < #NextYearStart
AND NOT hlid IS NULL
GROUP BY hlid
In your main queries, join to this table as follows:
LEFT JOIN #SumBills AS SumBills ON SumBills.hlid = cl.hlid
and in SELECT change assignments as per below example:
pay_bill= ISNULL( SumBills.Bill, 0 ) / 4,
Note: I believe you have a bug in bill column, as the value is divided by 4, where it is not in the original cursor.
The last point:
As #GarethD has said in his answer to your previous question, properly formatting your code significantly reduces the time it takes to understand and change the code. I would go one step further and say that code format represents your attitude to and understanding of the problem at hand.
I added non clustered index then my query take 5 sec to run.That's almost solve my problem. #Alex Thanks for your hard work and time. I will check your tips.I Up vote your comment

Invoice creation in sql - create a stored procedure

I want to create a procedure in which the following details should be displayed:-
I have created this query but I am not getting the right results. I have attached the table schema.
TABLES WE ARE USING:- InvoiceData, CustomerDetails and InvoiceItems
Table Schema ->
1. InvoiceItems TABLE1
2. CustomerDetails Table2
3. InvoiceDetails enter image description here
There are 2 sections for invoice:-
In the first section of the Invoice, Below details should be displayed.
Invoice Information section
In the second section of the invoice, the below details should be displayed:-
Invoice Items description section
I am attaching the query below:-
alter Procedure SaveInvoiceDetails
(
#CustomerId varchar(50),
#InvoiceNumber varchar(50),
#InvoiceDate date,
#InvoiceMonth int,
#FromDate date,
#ToDate date,
#Rate int,
#Quantity int,
#ActualAmount int,
#ZoneId int
)
as
set nocount on;
begin
Declare #TotalRows int
Declare #NumPages int
set #TotalRows = 0
Select ROW_NUMBER() over (order by C.CustomerId) as InvoiceRow,
C.CustomerId, I.InvoiceNumber, I.InvoiceDate, I.FromDate, I.ToDate,
I.InvoiceMonth, I.Rate, I.ActualAmount, I.Quantity, C.ZoneId,
C.BillingAmount
into #tempInvoice
from ConsumerMST_LKO C
inner join InvoiceDetails I
on C.CustomerId = I.CustomerId
inner join INVOICEITEMS II
on I.InvoiceNumber = II.INVOICEID
where InvoiceNumber = #InvoiceNumber AND InvoiceDate = #InvoiceDate AND
InvoiceMonth = #InvoiceMonth
AND FromDate =#FromDate AND ToDate = #ToDate AND ActualAmount =
#ActualAmount
set #TotalRows = ##ROWCOUNT
If #TotalRows = 0
Begin
set #TotalRows = #TotalRows + 1
Insert #tempInvoice
(
InvoiceNumber,
InvoiceDate,
InvoiceMonth,
ZoneId,
Rate,
Quantity,
BillingAmount,
FromDate,
ToDate
)
VALUES
(#TotalRows
, ''
,''
,''
,0
,0
,0
,0
,''
,0)
End
End
SELECT * FROM #tempInvoice ORDER BY InvoiceRow asc
return
So I am expecting that you got all the records in your #tempInvoice from your procedure. And face problem in only generating invoice number with the format you provided.
For each region you already have a specific code generated at your end ( I am guessing).
#regioncode ---- this table contain record of zoneid and zoneocde for each area
If you don't have any table for region then prepare one before going forward as this needs you everywhere in your code section.
At the end pf your procedure need to update this to return your required result.
; with cte as (
select row_number() over (partition by ZoneId order by InvoiceDate) as Slno, * from #tempInvoice as t inner join #regioncode as r on t.zoneid=r.zoneid
)
select zonecode + '_' + cast(slno as varchar(10)) as Uniquecode, * from cte

SQL Run query multiple times changing variable

I have a query which checks the status of a specific tank, however I have 50 tanks which I'd like to run this for (FV101, FV102, FV103 etc.)
I could union together 50 queries with the WHERE changed, but I suspect that there must be a better way.
The query is very simple:
DECLARE #tank varchar(5)
SET #tank = 'FV101'
SELECT
ROW_NUMBER() OVER (PARTITION BY [Item] ORDER BY [TankName]) [Row],
*
FROM
(
SELECT TOP 1
#tank [TankName],
T1.[Item],
CASE WHEN AVG(CAST(T0.[pHValue] AS dec (10,5))) NOT BETWEEN MAX(T2. [pH_lower]) AND MAX(T2.[pH_upper]) THEN 'Red' ELSE 'Black' END [Spec]
FROM
t005_pci_data T0 INNER JOIN t001_fvbatch T1 ON T1.[FVBatch] = T0. [FVBatch] LEFT JOIN t024_specifications T2 ON T2.[BeerBrand] = T1.[BeerBrand] AND [Type] = 'Finished Product'
WHERE
T0.[FVBatch] = (SELECT TOP 1 T0.[FVBatch] FROM t005_pci_data T0 INNER JOIN t001_fvbatch T1 ON T1.[FVBatch] = T0.[FVBatch] WHERE T1.[TankName] = 'FV101' ORDER BY T0.[DateTime] DESC) AND
EXISTS (SELECT [FVBatch] FROM t005_pci_data WHERE [TankName] = #tank AND [DateTime] >= DATEADD(day,-2,GETDATE()))
GROUP BY
T1.[Item],T0.[DateTime]
ORDER BY
T0.[DateTime] DESC
) a
Is there a way to pass a list of values to this query for it to use instead of repeating the query manually multiple times?
EDIT as per iamdave's suggestion
Schema (simplified) is as below:
The goal is to list everything which is currently in each tank and check if the value of the pH is within the acceptable limits.
Desired output (up to FV105) would be:
In this example there is nothing in tanks FV101 or FV104, as decided by the following code in the WHERE
EXISTS (SELECT [FVBatch] FROM t005_pci_data WHERE [TankName] = #tank AND [DateTime] >= DATEADD(day,-2,GETDATE()))
The end result is I would like to create a table in MSSRS which shows what item is in each tank and whether it is within specifications or not.
FURTHER EDIT with sample data as requested
(Not very imaginative I'm afraid)
declare #t1 table(FVBatch int, TankName nvarchar(5), Item nvarchar(20));
declare #t2 table(Item nvarchar(20), ph_lower decimal(10,2), ph_upper decimal(10,2));
declare #t3 table(FVBatch int, pHValue decimal(10,2), DateValue datetime);
insert into #t1 values
(3160001,'FV101','Stout')
,(3160002,'FV102','Stout')
,(3160003,'FV103','Stout')
,(3160004,'FV104','Pale Ale')
,(3160005,'FV105','Pale Ale')
,(3160070,'FST04','IPA');
insert into #t2 values
('Pale Ale',3.5,5.5)
,('Stout',2,3.5);
insert into #t3 values
(3160001,4 ,'20161209')
,(3160001,4 ,'20161210')
,(3160001,4 ,'20161212')
,(3160002,4 ,'20161218')
,(3160002,4 ,'20161220')
,(3160002,4 ,'20161222')
,(3160003,4 ,'20161218')
,(3160003,4 ,'20161220')
,(3160003,4 ,'20161222')
,(3160004,4 ,'20161209')
,(3160004,4 ,'20161210')
,(3160004,4 ,'20161212')
,(3160005,4 ,'20161218')
,(3160005,4 ,'20161220')
,(3160005,4 ,'20161222')
,(3160070,4.26,'20161218')
,(3160070,4.26,'20161216')
,(3160070,4.24,'20161215')
,(3160070,4.24,'20161214')
,(3160070,4.26,'20161213')
,(3160070,4.2 ,'20161212')
,(3160070,4.21,'20161211')
,(3160070,4.12,'20161209')
,(3160070,4.09,'20161208')
,(3160070,4.1 ,'20161207');
How does this do?
select row_number() over (partition by t1.Item order by t1.TankName) as RowNum
,t1.TankName
,t1.Item
,case when avg(t3.pHValue) between t2.ph_lower and t2.ph_upper
then 'Black'
else 'Red'
end as Spec
from #t1 t1
left join #t2 t2
on(t1.Item = t2.Item)
inner join(select FVBatch
,pHValue
,max(DateValue) as DateValue
from #t3
where DateValue >= cast(dateadd(d,-2,getdate()) as date)
group by FVBatch
,pHValue
) t3
on(t1.FVBatch = t3.FVBatch)
group by t1.TankName
,t1.Item
,t2.ph_lower
,t2.ph_upper
order by t1.TankName
,RowNum
Using your test data, the above query returns:
RowNum TankName Item Spec
1 FV102 Stout Red
2 FV103 Stout Red
1 FV105 Pale Ale Black
Edit based on conversation
/*
The full requirement is this:
there could be multiple of each measurement taken on any one date,
so I want to check that the average of these measurements per day is withing the range
each batch goes through several stages
if the stage isn't "bottle" or "can" then we look back 2 days
if it is one of those then we look back 5 days instead
*/
declare #t001_fvbatch table(FVBatch int
,TankName nvarchar(5)
,BeerBrand nvarchar(20)
);
declare #t024_specifications table(BeerBrand nvarchar(20)
,pH_lower decimal(10,2)
,pH_upper decimal(10,2)
,OG_lower decimal(10,2)
,OG_upper decimal(10,2)
,PG_lower decimal(10,2)
,PG_upper decimal(10,2)
,EBCHaze decimal(10,2)
,ABV_lower decimal(10,2)
,ABV_upper decimal(10,2)
,[Type] nvarchar(50)
);
declare #t005_pci_data table(FVBatch int
,pHValue decimal(10,2)
,Alcohol decimal(10,2)
,OG decimal(10,2)
,PG decimal(10,2)
,EBCHaze decimal(10,2)
,[DateTime] datetime
,Stage nvarchar(20)
,TankName nvarchar(20)
);
select b.FVBatch
,b.TankName
,b.BeerBrand
,case when((d.Stage in('CAN','BOTTLE','BBT')
and avg(cast(d.EBCHaze as dec(10,5))) > 5
)
or (avg(cast(d.Alcohol as dec(10,5))) not between max(s.ABV_lower) and max(s.ABV_upper)
or avg(cast(d.OG as dec(10,5))) not between max(s.OG_lower) and max(s.OG_upper)
or avg(cast(d.PG as dec(10,5))) not between max(s.PG_lower) and max(s.PG_upper)
or avg(cast(d.pHValue as dec(10,5))) not between max(s.pH_lower) and max(s.pH_upper)
)
)
then 'Red'
else 'Black'
end as Spec
from #t001_fvbatch b -- Always start at the table with the most central piece of data. In this case, the specifications and measurements all relate to a single batch.
left join #t024_specifications s
on(b.BeerBrand = s.BeerBrand
and s.[Type] = 'Finished Product'
)
inner join (select d2.FVBatch -- This sub select returns the most recent DateTime value per each FVBatch and TankName combination.
,d2.TankName
,cast(max(d2.[DateTime]) as date) as MostRecentMeasurement -- Cast/convert to DATE type to remove the TIME element. This means 2016-12-22 12:00:00 and 2016-12-22 13:59:43 both become 2016-12-22.
from #t005_pci_data d2
where d2.[DateTime] >= cast(dateadd(d -- This case statement filters the DateTime values by a number of days based on the value in the Stage column.
,case when d2.Stage in('can','bottle')
then -5
else -2
end
,getdate()
)
as date)
group by d2.FVBatch
,d2.TankName
) dm
on(b.FVBatch = dm.FVBatch
and b.TankName = dm.TankName
)
inner join #t005_pci_data d -- We then join into the data table again to get all the measurements taken on the same day as the most recent.
on(b.FVBatch = d.FVBatch
and b.TankName = d.TankName
and d.[DateTime] >= dm.MostRecentMeasurement -- Filtering like this allows any indexes on your DateTime column to be used to speed execution time. (This is called sargability).
and d.[DateTime] < dateadd(d,1,dm.MostRecentMeasurement) -- We could use functions to convert the DateTime to a DATE data type to match the MostRecentMeasurement value, but this would then need to be calculated for every single measurement in the table, which is wasteful.
)
-- Using INNER JOIN for the measurements means we are only return batches that have measurements that meet our filtering criteria.
-- If we wanted to return a batch even if there were no measurements matching, we would use LEFT JOIN instead.
group by b.FVBatch
,b.TankName
,b.BeerBrand
,d.Stage

Fastest method to insert numbers into sql server table?

SELECT TOP 1000000 row_number() over(ORDER by sv.number) AS num
INTO numbertest
from master..spt_values sv CROSS JOIN master..spt_values sv2
SELECT TOP 1000000 IDENTITY(int,1,1) AS Number
INTO NumberTest
FROM master..spt_values sv1
CROSS JOIN master..spt_values s2
I've come across two methods to insert 1 to 1000000 numbers in a table which works perfectly but doesn't insert 1 to 1000000 sequentially? how can i insert sequentially with fast insertion rate?
I have table Numbers in my database that i fill with the following query.
CREATE TABLE [dbo].[NUMBERS] (
[number] INT IDENTITY (1, 1) NOT NULL
);
set Identity_insert dbo.Numbers oN
declare
#row_count int,
#target_rows int
set #target_rows = 1048576
set #row_count = null
while ( 1 = 1 ) begin
if ( #row_count is null ) begin
insert into Numbers ( [number] ) values ( 1 )
end
else begin
insert into Numbers ( [number] )
select [number] = [number] + #row_count
from Numbers
end
set #row_count = isnull( #row_count, 0 ) + ##rowcount
if ( #row_count >= #target_rows ) begin
break
end
end
what I understood that you need to add/insert rows with serial number 1 to 10,00,000 (ten lac rows)
your two command seems select command instead of insert command
do really required to add serial number field in one of your filed
or
you can run query to any existing table to get result with serial number like
for eg, table name : employee
filed name : name
Note : no fileds existing like SerialNumber
you can run a command to get output with serial number
e.g. select ROW_NUMBER() over (order by employee.name) as SerialNumber, Employee.name from employee
your result will be like
SerialNumber name
1 abc
2 xyz