Looping SQL Script with Multiple Select Queries - sql

I have a very specific problem relating to a query I am trying to write.
Basically the inventory table has about 450,000 rows of inventory items, showing the closing balance of each item of inventory for the end of each month. The other tables include a shipping table, which tracks daily data for all items of inventory coming in and leaving a specific location. Then we also have a production table, that tracks items of inventories that are used in the production process daily.
We are trying to create another table that tracks Weekly Inventories using the Shipping and Production Tables, and then calculating an end of month variance.
Here is my Pseudocode for the script I have had to write:
FOR EACH ROW IN INVENTORY_TABLE
#incoming_shipping = SELECT INCOMING_AMOUNT FROM SHIPPING_TABLE WHERE WEEK_OF_MONTH = 1
#outgoing_shipping = SELECT OUTGOING_AMOUNT FROM SHIPPING_TABLE WHERE WEEK_OF_MONTH = 1
#outgoing_production =SELECT OUTGOING_AMOUNT FROM PRODUCTION TABLE WHERE WEEK_OF_MONTH = 1
#closing_inventory=ROW.OpeningAmount + #incoming_shipping - #outgoing_shipping + #outgoing_production
INSERT INTO WEEKLY_INVENTORY (Opening_Amount,Closing_Amount)
'Compute Week 2 Value
#incoming_shipping = SELECT INCOMING_AMOUNT FROM SHIPPING_TABLE WHERE WEEK_OF_MONTH = 2
#outgoing_shipping = SELECT OUTGOING_AMOUNT FROM SHIPPING_TABLE WHERE WEEK_OF_MONTH = 2
#outgoing_production =SELECT OUTGOING_AMOUNT FROM PRODUCTION TABLE WHERE WEEK_OF_MONTH = 2
#closing_inventory=ROW.OpeningAmount + #incoming_shipping - #outgoing_shipping + #outgoing_production
INSERT INTO WEEKLY_INVENTORY (Opening_Amount,Closing_Amount)
'Continue doing this for the Four, Maybe 5 weeks that occur in a month.
NEXT
While it is correct, the query is a nightmare to execute and currently runs approximately 100 rows a minute. I have tried using Loops without Cursors, Cursors etc. and there is no obvious performance difference. I am wondering if there is a better way to write the script without using any Loops.
Thanks in advance for any help.

I believe something in the lines of would work for you:
INSERT INTO WEEKLY_INVENTORY(Opening_Amount, Closing_Amount)
SELECT
OPENING_AMOUNT,
(OPENING_AMOUNT +
(SELECT INCOMING_AMOUNT
FROM SHIPPING_TABLE
WHERE WEEK_OF_MONTH = ST.WEEK_OF_MONTH)
) - (
(SELECT OUTGOING_AMOUNT
FROM SHIPPING TABLE
WHERE WEEK_OF_MONTH = ST.WEEK_OF_MONTH) +
(SELECT OUTGOING_AMOUNT
FROM PRODUCTION_TABLE
WHERE WEEK_OF_MONTH = ST.WEEK_OF_MONTH)
)
FROM SHIPPING_TABLE ST
WHERE ST.WEEK_OF_MONTH <= 5
You don't have to loop over all rows in order to do this. You can calculate for each WEEK_OF_MONTH the value that needs to be inserted into WEEKLY_INVENTORY by using a combination of INSERT INTO SELECT.
In the SELECT part you build the values, for each week of month by using other SELECT statements to determine the value for Closing_Amount.
The above query would be a straightforward option from your code, but the same functionality as above can be achieved, with an increase in speed by using a JOIN, with the below query:
INSERT INTO WEEKLY_INVENTORY(Opening_Amount, Closing_Amount)
SELECT
ST.OPENING_AMOUNT
, (ST.OPENING_AMOUNT + ST.INCOMING_AMOUNT)-(ST.OUTGOING_AMOUNT + PT.OUTGOING_AMOUNT)
FROM SHIPPING_TABLE ST
INNER JOIN PRODUCTION_TABLE PT ON ST.WEEK_OF_MONTH = PT.WEEK_OF_MONTH
Depending on how your database schema is structured, the queries above might not work as you might need to use an aggregate function, SUM() I assume and also GROUPing BY product. But using just the pseudocode you provided, this is the translation into SQL.

Related

Connecting multiple queries and passing criteria from one query to another

I am trying to put together a query that will basically run another query on each return.
My current query puts together important info for each client, such as how long an average groom takes and the description of the type of haircut they get:
SELECT Query11.petId AS PetID,
Query11.petName AS PetName,
dbo_Customer.cstLName AS LastName,
Query11.lstValue AS Breed,
qryFindsPetAvgApptTime.TotalAppts,
dbo_vwPetGroom.pgrName AS GroomStyle,
dbo_ListValues.lstValue AS Haircut,
ROUND([AvgPTime]) AS AvgPrep,
ROUND([AvgBTime]) AS AvgBath,
ROUND([AvgDTime]) AS AvgDry,
ROUND([AvgGTime]) AS AvgGroom,
[AvgPrep] + [AvgBath] + [AvgDry] + [AvgGroom] AS AvgMinutes,
qryFindsPetAvgApptTime.AvgHours
FROM((Query11
LEFT JOIN qryFindsPetAvgApptTime ON Query11.petId = qryFindsPetAvgApptTime.PetID)
LEFT JOIN dbo_Customer ON Query11.petCustId = dbo_Customer.cstId)
LEFT JOIN (dbo_vwPetGroom
LEFT JOIN dbo_ListValues ON dbo_vwPetGroom.pgrLengthHairBodyLid = dbo_ListValues.lstId)
ON Query11.petId = dbo_vwPetGroom.pgrPetId;
I want to add in the average length between grooming appts to the above query info. Right now that is done in 2 seperate queries.
The first one pulls days between appts:
SELECT tblTimeLog.PetID,
tblTimeLog.PetName,
[ApptDate] - (SELECT MAX(T.ApptDate)
FROM tblTimeLog T
WHERE T.PetID = tblTimeLog.PetID
AND T.ApptDate < tblTimeLog.ApptDate) AS Diff,
tblTimeLog.ApptDate
FROM tblTimeLog
WHERE (((tblTimeLog.PetID) = [Enter PetID]))
ORDER BY tblTimeLog.ApptDate;
And then averaged out with this:
SELECT qryTimeLogDiffs.PetID,
qryTimeLogDiffs.PetName,
AVG(qryTimeLogDiffs.Diff) AS AvgOfDiff
FROM qryTimeLogDiffs
GROUP BY qryTimeLogDiffs.PetID,
qryTimeLogDiffs.PetName;
Is there a way to pass the PetID criteria from each return into the second query so it adds the average span between appts to the info in the first query??
Consider refactoring your second query to avoid the taxing correlated aggregate subquery. One approach involves first running an aggregate query based on a self join to return last appointment date for each PetID.
Even better, turn this query into an action query to populate a temporary table either with make-table query as shown below using INTO or regularly cleaning out a persistent table via delete and insert-select query:
SELECT curr.PetID,
curr.PetName,
curr.ApptDate,
MAX(prev.[AppDate]) AS MaxPrevDate
INTO lastApptTimeLog
FROM tblTimeLog AS curr
INNER JOIN tblTimeLog AS prev
ON curr.PetID = prev.PetID
WHERE prev.ApptDate < curr.AppDate
GROUP BY curr.PetID,
curr.PetName,
curr.ApptDate;
Then, base third aggregate query on this temp table and run the difference calculation. Finally, incorporate this query into first query.
SELECT PetID,
PetName,
AVG(ApptDate - MaxPrevDate) AS AvgOfDiff
FROM lastApptTimeLog
GROUP BY PetID,
PetName;

SQL Server - Need to SUM values in across multiple returned records

In the following query I am trying to get TotalQty to SUM across both the locations for item 6112040, but so far I have been unable to make this happen. I do need to keep both lines for 6112040 separate in order to capture the different location.
This query feeds into a Jasper ireport using something called Java.Groovy. Despite this, none of the PDFs printed yet have been either stylish or stained brown. Perhaps someone could address that issue as well, but this SUM issue takes priority
I know Gordon Linoff will get on in about an hour so maybe he can help.
DECLARE #receipt INT
SET #receipt = 20
SELECT
ent.WarehouseSku AS WarehouseSku,
ent.PalletId AS [ReceivedPallet],
ISNULL(inv.LocationName,'') AS [ActualLoc],
SUM(ISNULL(inv.Qty,0)) AS [LocationQty],
SUM(ISNULL(inv.Qty,0)) AS [TotalQty],
MAX(CAST(ent.ReceiptLineNumber AS INT)) AS [LineNumber],
MAX(ent.WarehouseLotReference) AS [WarehouseLot],
LEFT(SUM(ent.WeightExpected),7) AS [GrossWeight],
LEFT(SUM(inv.[Weight]),7) AS [NetWeight]
FROM WarehouseReceiptDetail AS det
INNER JOIN WarehouseReceiptDetailEntry AS ent
ON det.ReceiptNumber = ent.ReceiptNumber
AND det.FacilityName = ent.FacilityName
AND det.WarehouseName = ent.WarehouseName
AND det.ReceiptLineNumber = ent.ReceiptLineNumber
LEFT OUTER JOIN Inventory AS inv
ON inv.WarehouseName = det.WarehouseName
AND inv.FacilityName = det.FacilityName
AND inv.WarehouseSku = det.WarehouseSku
AND inv.CustomerLotReference = ent.CustomerLotReference
AND inv.LotReferenceOne = det.ReceiptNumber
AND ISNULL(ent.CaseId,'') = ISNULL(inv.CaseId,'')
WHERE
det.WarehouseName = $Warehouse
AND det.FacilityName = $Facility
AND det.ReceiptNumber = #receipt
GROUP BY
ent.PalletId
, ent.WarehouseSku
, inv.LocationName
, inv.Qty
, inv.LotReferenceOne
ORDER BY ent.WarehouseSku
The lines I need partially coalesced are 4 and 5 in the above return.
Create a second dataset with a subquery and join to that subquery - you can extrapolate from the following to apply to your situation:
First the Subquery:
SELECT
WarehouseSku,
SUM(Qty)
FROM
Inventory
GROUP BY
WarehouseSku
Now apply to your query - insert into the FROM clause:
...
LEFT JOIN (
SELECT
WarehouseSKU,
SUM(Qty)
FROM
Inventory
GROUP BY
WarehouseSKU
) AS TotalQty
ON Warehouse.WarehouseSku = TotalQty.WarehouseSku
Without seeing the actual schema DDL it is hard to know the exact cardinality, but I think this will point you in the right direction.

Select information from different table SQL

SELECT Boeking.reisnummer, (aantal_volwassenen + aantal_kinderen) AS totaal_reizigers
FROM Boeking
WHERE Boeking.reisnummer = Reis.Reisnummer
AND Reis.Reisnummer = Reis.Bestemmingscode;
Table 1 (Boeking) has aantal_volwassen and aantal_kinderen, and Reisnummer.
Table 2 (Reis) has Reisnummer and Bestemmingscode.
I have to show the total of aantal_volwassenen and aantal_kinderen. But i also have to show Reis.bestemmingscode which comes from a different table. Currently i have to enter a parameter, how can i solve this?
You need to specify all the tables in the FROM part of your query. The tables should then be joined (JOIN) to get the data you need.
SELECT Boeking.reisnummer
,(aantal_volwassenen + aantal_kinderen) AS totaal_reizigers
,Reis.Bestemmingscode
FROM Boeking INNER JOIN Reis
ON Boeking.reisnummer = Reis.Reisnummer

SQL Query - combine 2 rows into 1 row

I have the following query below (view) in SQL Server. The query produces a result set that is needed to populate a grid. However, a new requirement has come up where the users would like to see data on one row in our app. The tblTasks table can produce 1 or 2 rows. The issue becomes when they're is two rows that have the same job_number but different fldProjectContextId (1 or 31). I need to get the MechApprovalOut and ElecApprovalOut columns on one row instead of two.
I've tried restructuring the query using CTE and over partition and haven't been able to get the necessary results I need.
SELECT TOP (100) PERCENT
CAST(dbo.Job_Control.job_number AS int) AS Job_Number,
dbo.tblTasks.fldSalesOrder, dbo.tblTaskCategories.fldTaskCategoryName,
dbo.Job_Control.Dwg_Sent, dbo.Job_Control.Approval_done,
dbo.Job_Control.fldElecDwgSent, dbo.Job_Control.fldElecApprovalDone,
CASE WHEN DATEDIFF(day, dbo.Job_Control.Dwg_Sent, GETDATE()) > 14
AND dbo.Job_Control.Approval_done IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 1
THEN 1 ELSE 0
END AS MechApprovalOut,
CASE WHEN DATEDIFF(day, dbo.Job_Control.fldElecDwgSent, GETDATE()) > 14
AND dbo.Job_Control.fldElecApprovalDone IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 31
THEN 1 ELSE 0
END AS ElecApprovalOut,
dbo.tblProjectContext.fldProjectContextName,
dbo.tblProjectContext.fldProjectContextId, dbo.Job_Control.Drawing_Info,
dbo.Job_Control.fldElectricalAppDwg
FROM dbo.tblTaskCategories
INNER JOIN dbo.tblTasks
ON dbo.tblTaskCategories.fldTaskCategoryId = dbo.tblTasks.fldTaskCategoryId
INNER JOIN dbo.Job_Control
ON dbo.tblTasks.fldSalesOrder = dbo.Job_Control.job_number
INNER JOIN dbo.tblProjectContext
ON dbo.tblTaskCategories.fldProjectContextId = dbo.tblProjectContext.fldProjectContextId
WHERE (dbo.tblTaskCategories.fldTaskCategoryName = N'Approval'
OR dbo.tblTaskCategories.fldTaskCategoryName = N'Re-Approval')
AND (CASE WHEN DATEDIFF(day, dbo.Job_Control.Dwg_Sent, GETDATE()) > 14
AND dbo.Job_Control.Approval_done IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 1
THEN 1 ELSE 0
END = 1)
OR (dbo.tblTaskCategories.fldTaskCategoryName = N'Approval'
OR dbo.tblTaskCategories.fldTaskCategoryName = N'Re-Approval')
AND (CASE WHEN DATEDIFF(day, dbo.Job_Control.fldElecDwgSent, GETDATE()) > 14
AND dbo.Job_Control.fldElecApprovalDone IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 31
THEN 1 ELSE 0
END = 1)
ORDER BY dbo.Job_Control.job_number, dbo.tblTaskCategories.fldProjectContextId
The above query gives me the following result set:
I've created a work around via code (which I don't like but it works for now) where i've used code to populate a "temp" table the way i need it to display the data, that is, one record if duplicate job numbers to get the MechApprovalOut and ElecApprovalOut columns on one row (see first record in following screen shot).
Example:
With the desired result set and one row per job_number, this is how the form looks with the data and how I am using the result set.
Any help restructuring my query to combine duplicate rows with the same job number where MechApprovalOut and ElecApproval out columns are on one row is greatly appreciated! I'd much prefer to use a view on SQL then code in the app to populate a temp table.
Thanks,
Jimmy
What I would do is LEFT JOIN the main table to itself at the beginning of the query, matching on Job Number and Sales Order, such that the left side of the join is only looking at Approval task categories and the right side of the join is only looking at Re-Approval task categories. Then I would make extensive use of the COALESCE() function to select data from the correct side of the join for use later on and in the select clause. This may also be the piece you were missing to make a CTE work.
There is probably also a solution that uses a ranking/windowing function (maybe not RANK itself, but something that category) along with the PARTITION BY clause. However, as those are fairly new to Sql Server I haven't used them enough personally to be comfortable writing an example solution for you without direct access to the data to play with, and it would still take me a little more time to get right than I can devote to this right now. Maybe this paragraph will motivate someone else to do that work.

SUM(a*b) not working

I have a PHP page running in postgres. I have 3 tables - workorders, wo_parts and part2vendor. I am trying to multiply 2 table column row datas together, ie wo_parts has a field called qty and part2vendor has a field called cost. These 2 are joined by wo_parts.pn and part2vendor.pn. I have created a query like this:
$scoreCostQuery = "SELECT SUM(part2vendor.cost*wo_parts.qty) as total_score
FROM part2vendor
INNER JOIN wo_parts
ON (wo_parts.pn=part2vendor.pn)
WHERE workorder=$workorder";
But if I add the costs of the parts multiplied by the qauntities supplied, it adds to a different number than what the script is doing. Help....I am new to this but if someone can show me in SQL I can modify it for postgres. Thanks
Without seeing example data, there's no way for us to know why you're query totals are coming out differently that when you do the math by hand. It could be a bad join, so you are getting more/less records than you expected. It's also possible that your calculations are off. Pick an example with the smallest number of associated records & compare.
My suggestion is to add a GROUP BY to the query:
SELECT SUM(p.cost * wp.qty) as total_score
FROM part2vendor p
JOIN wo_parts wp ON wp.pn = p.pn
WHERE workorder = $workorder
GROUP BY workorder
FYI: MySQL was designed to allow flexibility in the GROUP BY, while no other db I've used does - it's a source of numerous questions on SO "why does this work in MySQL when it doesn't work on db x...".
To Check that your Quantities are correct:
SELECT wp.qty,
p.cost
FROM WO_PARTS wp
JOIN PART2VENDOR p ON p.pn = wp.pn
WHERE p.workorder = $workorder
Check that the numbers are correct for a given order.
You could try a sub-query instead.
(Note, I don't have a Postgres installation to test this on so consider this more like pseudo code than a working example... It does work in MySQL tho)
SELECT
SUM(p.`score`) AS 'total_score'
FROM part2vendor AS p2v
INNER JOIN (
SELECT pn, cost * qty AS `score`
FROM wo_parts
) AS p
ON p.pn = p2v.pn
WHERE p2n.workorder=$workorder"
In the question, you say the cost column is in part2vendor, but in the query you reference wo_parts.cost. If the wo_parts table has its own cost column, that's the source of the problem.