SQL to also get rest of sums based on highest colum value - sql

I have the following SQL below with an issue i am trying to resolve. I basically need to get the sum of the acreage once the highest parcel_status (3) is detected to also be include in SumofTonnage.
IE once parcel_status is 3 get the SumofTonnage for parcels that have status from 0, 1, 2
SELECT
CROP_CLASS As Closed,
SUM(tonnage_adjusted) AS SumofTonnage,
SUM(ACREAGE) AS SumofAcreage
FROM
CaneParcel
INNER JOIN
DeliveryTons ON CaneParcel.FIELD_ID = DeliveryTons.parcel_id
WHERE
parcel_status = '3'
GROUP BY
CROP_CLASS
ORDER BY
CROP_CLASS ASC
DeliveryTons
id | parcel_id | tonnage_adjusted | parcel_status
1 | 302-234 | 34.56 | 1
2 | 302-234 | 14.56 | 2
3 | 302-234 | 17.56 | 3
4 | 302-235 | 8.56 | 1
5 | 302-236 | 11.56 | 1
6 | 302-236 | 18.56 | 3
CaneParcel
id | ACREAGE | FIELD_ID | CROP_CLASS
1 | 1.34 | 302-234 | RATOON
2 | 1.64 | 302-235 | RATOON
3 | 1.54 | 302-236| PLANTCANE
OUT PUT:
Closed | SumofTonnage | SumofAcreage
RATOON 66.68 | 2.98
PLANTCANE 30.12 | 1.54

If I understand your problem correctly, you want to first aggregate adjusted tonnage in the DeliveryTons table by parcel, but only if that parcel has status 3 appearing. The CTE below does this, and parcels not having status 3 will not appear in the result set of that CTE.
Then, you want to join this result to the CaneParcel table, and again aggregate over crop class. This time, we take the sum of the effective adjusted tonnage as well as the acreage. I said "effective" tonnage, because some parcels will effectively have zero tonnage if they did not meet the criteria.
WITH cte AS (
SELECT parcel_id, SUM(tonnage_adjusted) AS tonnage_adjusted_sum
FROM DeliveryTons
GROUP BY parcel_id
HAVING SUM(CASE WHEN parcel_status = '3' THEN 1 ELSE 0 END) > 0
)
SELECT cp.CROP_CLASS As Closed,
SUM(ISNULL(dt.tonnage_adjusted_sum, 0)) AS SumofTonnage,
SUM(cp.ACREAGE) AS SumofAcreage
FROM CaneParcel cp
LEFT JOIN cte dt
ON cp.FIELD_ID = dt.parcel_id
GROUP BY cp.CROP_CLASS
ORDER BY cp.CROP_CLASS ASC
Contrary to what you seem to be saying in the comments, this query does produce your expected output:
Rextester
I used MySQL for the demo, but the logic would be no different in SQL Server.

Related

SQL logic to determine unsold inventory and corresponding available dates (Available to sell)

I am looking for advice on how to generate SQL to be used in SQL Server that will show available inventory to sell and the corresponding date that said inventory will be available. I am easily able to determine if we have inventory that is available immediately but can't wrap my head around what logic would be needed to determine future available quantities.
In the below table. The +/- column represents the weekly inbound vs outbound and the quantity available is a rolling SUM OVER PARTITION BY of the +/- column. I was able to get the immediate quantity available through this simple logic:
Case when Min(X.Qty_Available) > 0 Then Min(X.Qty_Available) else 0 END
AS Immediate_available_Qty
Table:
+-------------+---------------+---------------+------+---------------+
| Item Number | Item Name | week_end_date | +/- | Qty_Available |
+-------------+---------------+---------------+------+---------------+
| 123456 | Fidget Widget | 7/13/2019 | 117 | 117 |
| 123456 | Fidget Widget | 7/20/2019 | 49 | 166 |
| 123456 | Fidget Widget | 7/27/2019 | -7 | 159 |
| 123456 | Fidget Widget | 8/3/2019 | -12 | 147 |
| 123456 | Fidget Widget | 8/10/2019 | -1 | 146 |
| 123456 | Fidget Widget | 8/17/2019 | 45 | 191 |
| 123456 | Fidget Widget | 8/24/2019 | -1 | 190 |
| 123456 | Fidget Widget | 8/31/2019 | -1 | 189 |
| 123456 | Fidget Widget | 9/7/2019 | 50 | 239 |
+-------------+---------------+---------------+------+---------------+
My desired results of this query would be as follows:
+-----------+-----+
| Output | Qty |
+-----------+-----+
| 7/13/2019 | 117 |
| 7/20/2019 | 29 |
| 8/17/2019 | 43 |
+-----------+-----+
the second availability is determined by taking the first available quantity of 117 out of each line in Qty_Available column and finding the new minimum. If the new min is Zero, find the next continuously positive string of data (that runs all the way to the end of the data). Repeat for the third_available quantity and then stop.
I was on the thought train of pursuing RCTE logic but don't want to dive into that rabbit hole if there is a better way to tackle this issue and I'm not even sure the RCTE work for this problem?
This should return your expected result:
SELECT Item_Number, Min(week_end_date), Sum("+/-")
FROM
(
SELECT *
-- put a positive value plus all following negative values in the same group
-- using a Cumulative Sum over 0/1
,Sum(CASE WHEN "+/-" > 0 THEN 1 ELSE 0 end)
Over (PARTITION BY Item_Number
ORDER BY week_end_date
ROWS UNBOUNDED PRECEDING) AS grp
FROM my_table
) AS dt
WHERE grp <= 3 -- only the 1st 3 groups
GROUP BY Item_Number, grp
So here's what I came up with. I know this is poor, I didn't want to leave this thread high and dry and maybe I can get more insight on a better path. Please know that I've never had any real training so I don't know what I don't know.
I ended up running this into a temp table and altering the commented out section in table "A". then re-running that into a temp table.
Select
F.Upc,
F.name,
F.Week_end_date as First_Available_Date,
E.Qty_Available_1
From
(
Select Distinct
D.Upc,
D.name,
Case When Min(D.Rolling_Qty_Available) Over ( PARTITION BY D.upc) < 1 then 0 else
Min(D.Rolling_Qty_Available) Over ( PARTITION BY D.upc) END as Qty_Available_1,
Case When Max(D.Look_up_Ref) Over ( PARTITION BY D.upc) = 0 then '-1000' else
Max(D.Look_up_Ref) Over ( PARTITION BY D.upc) END as Look_up_Ref_1
From
(
Select
A.Upc,
A.name,
A.Week_end_Date,
A.Rolling_Qty_Available,
CASE WHEN
C.Max_Row = A.Row_num and A.[Rolling_Qty_Available] >1 THEN 1
ELSE
CASE WHEN
Sum(A.Calc_Row_Thing) OVER (Partition by A.UPC Order by A.Row_Num DESC
ROWS BETWEEN UNBOUNDED PRECEDING
AND Current ROW
) = (C.Max_Row - A.Row_num + 1)
THEN
C.Max_Row - A.Row_num + 1
ELSE 0 END
END as Look_up_Ref
FROM (
Select
G.Upc,
G.Name,
G.Week_End_Date,
G.Row_num,
G.Calc_Row_Thing,
G.Rolling_Qty_Available
--CASE When (G.Rolling_Qty_Available -
--isnull(H.Qty_Available_1,0)) > 0 then 1 else - 0 END as
--Calc_Row_Thing,
From [dbo].[ATS_item_detail_USA_vw] as G
--Left Join [dbo].[tmp_ats_usa_qty_1] as H on G.upc = H.upc
) AS A --Need to subtract QTY 1 out of here and below
join (
SELECT
B.upc,
Max(Row_num) AS Max_Row
FROM [dbo].[ATS_item_detail_USA_vw] AS B
GROUP BY B.upc
) as C on A.upc = C.upc
) as D
GROUP BY
D.Upc,
D.name,
D.Rolling_Qty_Available,
D.Look_up_Ref
HAVING Max(D.Look_up_Ref) > 1
) as E
Left join
(
SELECT
A.Upc,
A.name,
A.Week_end_Date,
A.Rolling_Qty_Available,
CASE WHEN
C.Max_Row = A.Row_num and A.[Rolling_Qty_Available] >1 THEN 1
ELSE
CASE WHEN
Sum(A.Calc_Row_Thing) OVER (Partition by A.UPC Order by A.Row_Num DESC
ROWS BETWEEN UNBOUNDED PRECEDING
AND Current ROW
) = (C.Max_Row - A.Row_num + 1)
THEN
C.Max_Row - A.Row_num + 1
ELSE 0 END
END as Look_up_Ref
From (
Select
G.Upc,
G.Name,
G.Week_End_Date,
G.Row_num,
G.Calc_Row_Thing,
G.Rolling_Qty_Available
--CASE When (G.Rolling_Qty_Available -
--isnull(H.Qty_Available_1,0)) > 0 then 1 else - 0 END as
--Calc_Row_Thing,
From [dbo].[ATS_item_detail_USA_vw] as G
--Left Join [dbo].[tmp_ats_usa_qty_1] as H on G.upc = H.upc
) as A --subtract qty_1 out the start qty 2 calc
join (
SELECT
B.upc,
Max(Row_num) as Max_Row
FROM [dbo].[ATS_item_detail_USA_vw] as B
GROUP BY B.upc
) AS C ON A.upc = C.upc
) AS F ON E.upc = F.upc and E.Look_up_Ref_1 = F.Look_up_Ref

How to fetch records from complex database?

I need to fetch multiple records and single records at a time with multiple tables from MSSQL. Tables from where I need to collect data is like below :
1. tblExpenseType :
--> ExpenseTypeId | ExpenseTypeName
--> 1 | OVERTIME EXP.
--> 2 | REPAIRING EXP.
--> 3 | LU EXP.
2. tblMemoDetails :
--> MemoID | MemoNumber | FkTruckId
--> 1011 | 1 | 5
--> 1012 | 2 | 6
--> 1013 | 3 | 5
--> 1014 | 4 | 7
3. tblMemoExpense :
--> FkMemoNumber | FkExpenseTypeId | Amount
--> 1 | 1 | 150
--> 1 | 3 | 225
--> 2 | 1 | 50
--> 2 | 2 | 100
--> 2 | 3 | 150
4. tblMemoTrips :
--> FkTripId | FkMemoNumber | TripAmount
--> 11 | 1 | 1000
--> 9 | 2 | 500
--> 3 | 2 | 100
--> 4 | 2 | 2000
I'm trying to fetch data using below logic but it's making me confusing:
with MemoList
As
(select
_jpMemo.MemoId as memoId
from TMS_JPMemo _jpMemo
where _jpMemo.FkTruckId = 5)
select
ML.memoId
--,ME.FkExpenseTypeId
,ME.Amount
,ET.ExpenseTypeName
from TMS_MemoExpense ME
join MemoList ML on ML.memoId = ME.FkMemoId
join TMS_ExpenseTypes ET on ET.ExpenseTypeId = ME.FkExpenseTypeId
I need result like if I select FkTruckId 5 then it will show me below result.
I need results like below for FkTruckId 5 :
MemoNumber | TripDetails | TripAmount | OVERTIME | REPAIRING | LU
1 | 11 | 1000 | 150 | -- | 225
| Total | 1000 | 150 | -- | 225
And If I select FkTruckId 6 then it would show me a result like :
MemoNumber | TripDetails | TripAmount | OVERTIME | REPAIRING | LU
2 | 9 | 500 | -- | -- | --
| 3 | 100 | -- | -- | --
| 4 | 2000 | 50 | 100 | 150
| Total | 2600 | 50 | 100 | 150
So it's making me confusing how to solve this and how to achieve this type of complex data from tables.
You are displaying two different things per memo:
the memo's trips with their trip amount
the memo's expenses
Apart from belonging to the same memo, the two are not related, i.e. there are no expenses per trip. So it's actually two different things you want to show. Make this two separate queries. You can glue them together with UNION ALL, as you obviously want them both in one query result. Something like:
with memo as
(
select memoid
from tblmemodetails
where fktruckid = 5
)
select memonumber, tripid, tripamount, overtime, repairing, lu
from
(
-- -------------------------------------------------------------------------------
-- The memos' trips. They have a trip amount but no further expenses of their own.
select
fkmemonumber as memonumber,
fktripid as tripid,
tripamount,
null as overtime,
null as repairing,
null as lu,
1 sortkey
from tblmemotrips
where fkmemonumber in (select memonumber from memo)
group by fkmemonumber, fktripid -- one row per memo and trip
-- -------------------------------------------------------------------------------
union all
-- -------------------------------------------------------------------------------
-- The memos' expenses. They are not reated to particular trips.
select
fkmemonumber as memonumber,
null as tripid,
null as tripamount,
sum(case when fkexpensetypeid = 1 then amount end) as overtime,
sum(case when fkexpensetypeid = 2 then amount end) as repairing,
sum(case when fkexpensetypeid = 3 then amount end) as lu,
2 as sortkey
from tblmemoexpense
where fkmemonumber in (select memonumber from memo)
group by fkmemonumber -- one row per memo
-- -------------------------------------------------------------------------------
) data
order by memonumber, sortkey;
You could use a series of JOINs to put the tables in relation, and then conditional aggregation to pivot the data. This implies that the number of columns to pivot is fixed (if more values are added to table tblExpenseType, the query would need to be adapted) :
SELECT
md.MemoNumber,
mt.FkTripId AS TripDetails,
mt.TripAmount,
MAX(CASE WHEN et.ExpenseTypeName = 'OVERTIME EXP.' THEN me.amount END) AS OVERTIME,
MAX(CASE WHEN et.ExpenseTypeName = 'REPARINING EXP.' THEN me.amount END) AS REPARING,
MAX(CASE WHEN et.ExpenseTypeName = 'LU EXP.' THEN me.amount END) AS LU
FROM
tblMemoDetails AS md
LEFT JOIN tblMemoExpense AS me ON me.FkMemoNumber = md.MemoNumber
LEFT JOIN tblMemoTrips AS mt ON mt.FkMemoNumber = md.MemoNumber
LEFT JOIN tblExpenseType AS et ON et.ExpenseTypeId = me.FkExpenseTypeId
GROUP BY
md.MemoNumber,
mt.FkTripId AS TripDetails,
mt.TripAmount
Conditional aggregation is the way to go. I think the query looks a little more like this:
SELECT md.TruckId, md.MemoNumber,
COALESCE(mt.FkTripId, 'Total') AS TripDetails, mt.TripAmount,
SUM(CASE WHEN et.ExpenseTypeName = 'OVERTIME EXP.' THEN me.amount ELSE 0 END) AS OVERTIME,
SUM(CASE WHEN et.ExpenseTypeName = 'REPARINING EXP.' THEN me.amount ELSE 0 END) AS REPARING,
SUM(CASE WHEN et.ExpenseTypeName = 'LU EXP.' THEN me.amount ELSE 0 END) AS LU
FROM tblMemoDetails md LEFT JOIN
tblMemoExpense me
ON me.FkMemoNumber = md.MemoNumber LEFT JOIN
tblMemoTrips mt
ON mt.FkMemoNumber = md.MemoNumber LEFT JOIN
tblExpenseType et
ON et.ExpenseTypeId = me.FkExpenseTypeId
WHERE md.TruckId = #TruckId
GROUP BY GROUPING SETS ( (md.TruckId, md.MemoNumber, mt.FkTripId, mt.TripAmount), () );
The GROUPING SETS generates the total.

SQL - How to get the count of each distinct value?

I have 3 table
**room**
room_id | nurse_needed
----------------------
1 | 2
2 | 3
3 | 1
**doctor_schedule**
doctor_schedule_id| room_id
---------------------------
1 | 1
2 | 2
3 | 3
*nurse_schedule*
nurse_schedule_id | doctor_schedule_id
--------------------------------------
1 | 1
2 | 1
3 | 2
Each Room needs a number of nurse, A doctor work in Room and a nurse work with doctor's schedule. I want to count how many nurse in each room.
The result should be:
room_id | nurse_needed|nurse_have_in_room
---------------------------------------------
1 | 2 | 2
2 | 3 | 1
3 | 1 | 0
Hmmm . . .
select r.*,
(select count(*)
from doctor_schedule ds join
nurse_schedule ns
on ds.doctor_schedule_id = ns.doctor_schedule_id
where ds.room_id = r.room_id
) as nurse_have_in_room
from room r;
select room.*,
(select count(*) from
dotor_schedule docs,
nurse_schedule nurs
where docs.doctor_schedule_id=nurs.dcotor_schedule_id
group by docs.room_id) as nurse_have_in_room
from room;
Result of join on doctor_schedule_id between doctor_schedule and
nurse_schedule
nurse_schedule_id | doctor_schedule_id room_id
--------------------------------------+------------
1 | 1 | 1
2 | 1 | 1
3 | 2 | 2
We group by room_id and then get the result.
select r.room_id,
r.nurse_needed,
ns.nurses_scheduled,
ns.dist_nurses_scheduled
from room r
left join (select ds.room_id,
count(1) nurses_schedule,
count(distinct ns.nurse_schedule_id) dist_nurses_scheduled
from doctor_schedule ds
join nurse_schedule ns
on ds.doctor_schedule_id = ns.doctor_schedule_id
group by ds.room_id) as ns
on r.room_id = ns.room_id
Left join so you find rooms with no nurses scheduled.
Count(distinct ns.nurse_schedule_id) if needed to see how many different nurses make up the count.
Normally you have a time component in there too. Something like "where r.roomdate = ns.date"

subtract data from single column

I have a database table with 2 columns naming piece and diff and type.
Here's what the table looks like
id | piece | diff | type
1 | 20 | NULL | cake
2 | 15 | NULL | cake
3 | 10 | NULL | cake
I want like 20 - 15 = 5 then 15 -10 = 5 , then so on so fort with type as where.
Result will be like this
id | piece | diff | type
1 | 20 | 0 | cake
2 | 15 | 5 | cake
3 | 10 | 5 | cake
Here's the code I have so far but i dont think I'm on the right track
SELECT
tableblabla.id,
(tableblabla.cast(pieces as decimal(7, 2)) - t.cast(pieces as decimal(7, 2))) as diff
FROM
tableblabla
INNER JOIN
tableblablaas t ON tableblabla.id = t.id + 1
Thanks for the help
Use LAG/LEAD window function.
Considering that you want to find Difference per type else remove Partition by from window functions
select id, piece,
Isnull(lag(piece)over(partition by type order by id) - piece,0) as Diff,
type
From yourtable
If you are using Sql Server prior to 2012 use this.
;WITH cte
AS (SELECT Row_number()OVER(partition by type ORDER BY id) RN,*
FROM Yourtable)
SELECT a.id,
a.piece,
Isnull(b.piece - a.piece, 0) AS diff,
a.type
FROM cte a
LEFT JOIN cte b
ON a.rn = b.rn + 1

How to calculate the value of a previous row from the count of another column

I want to create an additional column which calculates the value of a row from count column with its predecessor row from the sum column. Below is the query. I tried using ROLLUP but it does not serve the purpose.
select to_char(register_date,'YYYY-MM') as "registered_in_month"
,count(*) as Total_count
from CMSS.USERS_PROFILE a
where a.pcms_db != '*'
group by (to_char(register_date,'YYYY-MM'))
order by to_char(register_date,'YYYY-MM')
This is what i get
registered_in_month TOTAL_COUNT
-------------------------------------
2005-01 1
2005-02 3
2005-04 8
2005-06 4
But what I would like to display is below, including the months which have count as 0
registered_in_month TOTAL_COUNT SUM
------------------------------------------
2005-01 1 1
2005-02 3 4
2005-03 0 4
2005-04 8 12
2005-05 0 12
2005-06 4 16
To include missing months in your result, first you need to have complete list of months. To do that you should find the earliest and latest month and then use heirarchial
query to generate the complete list.
SQL Fiddle
with x(min_date, max_date) as (
select min(trunc(register_date,'month')),
max(trunc(register_date,'month'))
from users_profile
)
select add_months(min_date,level-1)
from x
connect by add_months(min_date,level-1) <= max_date;
Once you have all the months, you can outer join it to your table. To get the cumulative sum, simply add up the count using SUM as analytical function.
with x(min_date, max_date) as (
select min(trunc(register_date,'month')),
max(trunc(register_date,'month'))
from users_profile
),
y(all_months) as (
select add_months(min_date,level-1)
from x
connect by add_months(min_date,level-1) <= max_date
)
select to_char(a.all_months,'yyyy-mm') registered_in_month,
count(b.register_date) total_count,
sum(count(b.register_date)) over (order by a.all_months) "sum"
from y a left outer join users_profile b
on a.all_months = trunc(b.register_date,'month')
group by a.all_months
order by a.all_months;
Output:
| REGISTERED_IN_MONTH | TOTAL_COUNT | SUM |
|---------------------|-------------|-----|
| 2005-01 | 1 | 1 |
| 2005-02 | 3 | 4 |
| 2005-03 | 0 | 4 |
| 2005-04 | 8 | 12 |
| 2005-05 | 0 | 12 |
| 2005-06 | 4 | 16 |