How to fetch records from complex database? - sql

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.

Related

Percentage per user of total - reach best perfomance

I want to get a percentage of items/user in our warehouse (only items which are out of stock).
item_id | partner_id | item_name | stock_sum | manager_id
---------------------------------------------------------------------------
23020 | 232 | cola |  0 | 237
39935 | 232 | sprite | 0 | 89
23030 | 232 | fanta   | 60 | 32
15331 | 232 | water   | 20 | 237
So i have to get:
manager_id = 237 -> 0,25 -> 25%
manager_id = 89 -> 0,25 -> 25%
total -> 0,50 -> 50%
My first idea was something like this:
select skk.manger_id, count(*), count(skk.item_id/sk.item_id) as prcntg
from stock skk
inner join stock sk on skk.item_id = sk.item_id and skk.manager_id = sk.manager_id
where skk.stock_sum = 0
group by skk.manager_id
But i think this is not the right solution.. so i need help.
[Output] expected:
manager_id | total | % of total (prcntg)
----------------------------------------------
237 | 0,5 | 0,25
89 | 0,5 | 0,25
32 | 0,5 | 0
You seem to be describing this a calculation using window functions:
select manager_id, count(*) as num_items,
sum(case when stock_sum = 0 then 1 else 0 end) as num_out_of_stock,
avg(case when stock_sum = 0 then 1.0 else 0 end) as manager_avg,
sum(case when stock_sum = 0 then 1 else 0 end) / count(*) as overall_avg
from stock s
group by manager_id;
The overall_avg is what you seem to be looking for.
select manager_id, sum(av) pcnt
from (
select manager_id, count(case stock_sum when 0 then 1 end) / sum(count(1)) over () as av
from stock group by manager_id)
group by rollup(manager_id)
dbfiddle
Divide counted zeros by number of all entries (analytical sum of all counts). Use outer rollup if you need total value.

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

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.

How to create sql selection based on condition?

I have the following database which shows characteristics of attributes as follows:
attributeId | attributeCode | groupCode
------------+---------------+-----------
1 | 10 | 50
1 | 10 | 50
1 | 12 | 50
My desired result from a select would be:
attributeId | groupcount | code10 | code12
------------+------------+--------+--------
1 | 1 | 2 | 1
Which means: attributeId = 1 has only one groupCode (50), where attributeCode=10 occurs 2 times and attributeCode=12 occurs 1 time.
Of course the following is not valid, but you get the idea of what I'm trying to achieve:
select attributeId,
count(distinct(groupCode)) as groupcount,
attributeCode = 10 as code10,
attributeCode = 12 as code12
from table
group by attributeId;
Try this:
SELECT attributeId, COUNT(DISTINCT groupCode) AS groupcount,
COUNT(CASE WHEN attributeCode = 10 THEN 1 END) AS code10,
COUNT(CASE WHEN attributeCode = 12 THEN 1 END) AS code12
FROM mytable
GROUP BY attributeId
Demo here

Sql Query Compare and Sum

I have these problem I need to match the sum a columns to see if they match with the Final Total of the Invoice by Invoice Number ( I am working in a query to do it)
Example
Invoice No Line _no Total Line Invoice total Field I will create
----------------------------------------------------------------------
45 1 145 300 145
45 2 165 300 300 Match
46 1 200 200 200 Match
47 1 100 300 100
47 2 100 300 200
47 3 100 300 300 Match
You want a cumulative sum. In SQL Server 2012+, just do:
select e.*,
(case when InvoiceTotal = sum(InvoiceTotal) over (partition by invoice_no order by line_no)
then 'Match'
end)
from example e;
In earlier versions of SQL Server, I would be inclined to do it with a correlated subquery:
select e.*
(case when InvoiceTotal = (select sum(InvoiceTotal)
from example e2
where e2.Invoice_no = e.invoice_no and
e2.line_no >= e.line_no
)
then 'Match'
end)
from example e;
You can also do this with a cross apply as M Ali suggests.
EDIT:
Now that I think about the problem, you don't need a cumulative sum. That was just how I originally thought of the problem. So, this will work in SQL Server 2008:
select e.*,
(case when InvoiceTotal = sum(InvoiceTotal) over (partition by invoice_no)
then 'Match'
end)
from example e;
You can't get the cumulative sum out (the second to last column) without more manipulation, but the match column is not hard.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE TEST(InvoiceNo INT, Line_no INT, TotalLine INT, InvoiceTotal INT)
INSERT INTO TEST VALUES
(45 ,1 ,145 ,300),
(45 ,2 ,165 ,300),
(46 ,1 ,200 ,200),
(47 ,1 ,100 ,300),
(47 ,2 ,100 ,300),
(47 ,3 ,100 ,300)
Query 1:
SELECT t.[InvoiceNo]
,t.[Line_no]
,t.[TotalLine]
,t.[InvoiceTotal]
,C.Grand_Total
,CASE WHEN C.Grand_Total = t.[InvoiceTotal]
THEN 'Match' ELSE '' END AS [Matched]
FROM TEST t
CROSS APPLY (SELECT SUM([TotalLine]) AS Grand_Total
FROM TEST
WHERE [InvoiceNo] = t.[InvoiceNo]
AND [Line_no] < = t.[Line_no]) C
Results:
| INVOICENO | LINE_NO | TOTALLINE | INVOICETOTAL | GRAND_TOTAL | MATCHED |
|-----------|---------|-----------|--------------|-------------|---------|
| 45 | 1 | 145 | 300 | 145 | |
| 45 | 2 | 165 | 300 | 310 | |
| 46 | 1 | 200 | 200 | 200 | Match |
| 47 | 1 | 100 | 300 | 100 | |
| 47 | 2 | 100 | 300 | 200 | |
| 47 | 3 | 100 | 300 | 300 | Match |
Is this what you're looking for? I think subquery is what you're asking about, but i'm guessing to get an end result similar to the entire thing.
select t."Invoice No", t."Line no_", t."Invoice total",
calcTotals.lineNum as calcSum, case when t."Invoice total" = calcTotals.lineNum then 'matched' else 'not matched' end
from [table] t
inner join (
select "Invoice No" as invoiceNumber,
sum("Line _no") as lineNum
from [table]
group by "Invoice No"
) calcTotals on t."Invoice No" = calcTotals.invoiceNumber

Multiple sql query or Cursor?

I need help on something that seems to be complex to me.
I made a query to create a tbl1 which is the Cartesian product of the tables Item and Warehouse. It give’s me back all items in all warehouses:
SELECT i.ItemID, w.WarehouseID
FROM Item i, Warehouse w
I made a second query (tbl2) where I check the date of the last document previous or equal to a variable date (#datevar) and whose quantity rule is 1 (PhysicalQtyRule = 1), this by Item and Warehouse, obtained from StockHistory table
SELECT MAX(CreateDate) AS [DATE1], ItemID, Quantity, WarehouseID
FROM StockHistory
WHERE PhysicalQtyRule = 1 AND CreateDate <= #datevar
GROUP BY ItemID, Quantity, WarehouseID
Now, I need more three steps:
Build a third table containing per item and warehouse the sum of quantity, but the quantity rule is 2 (PhysicalQtyRule = 2) and date between tbl2.date (if exists) and the date of the variable #datevar, obtained from the table StockHistory. Something like that:
SELECT ItemID, WarehouseID, SUM(Quantity)
FROM StockHistory
WHERE PhysicalQtyRule = 2 AND CreateDate > tbl2.DATE1 --If exists
AND CreateDate <= #datevar
GROUP BY ItemID, WarehouseID
Build a fourth table containing per item and warehouse the sum of quantity, but the quantity rule is 3 (PhysicalQtyRule = 3) and date between tbl2.date (if any) and the date of the variable #datevar, obtained from the table StockHistory. Something like that:
SELECT ItemID, WarehouseID, SUM(Quantity)
FROM StockHistory
WHERE PhysicalQtyRule = 3 AND CreateDate > tbl2.DATE1 --If exists
AND CreateDate <= #datevar
GROUP BY ItemID, WarehouseID
Create a final table based on the first one, with an sum quantity column, something like that:
SELECT i.ItemID, w.WarehouseID, tbl2.Quantity + tbl3.Quantity – tbl4.Quantity AS [Qty]
FROM Item i, Warehouse w
I don't know if need cursors (something new for me) or multiple querys, but it's important the best performance because my StockHistory table have millions of records.
Can anyone help-me please? Thank you!
Some sample data, only for one Item and one warehouse:
+--------+-------------+------------+-----------------+----------+
| ItemID | WarehouseID | CreateDate | PhysicalQtyRule | Quantity | Balance | comments
+--------+-------------+------------+-----------------+----------+
| 1234 | 11 | 2013-03-25 | 2 | 35 | 35 | Rule 2 = In
| 1234 | 11 | 2013-03-28 | 3 | 30 | 5 | Rule 3 = Out
| 1234 | 11 | 2013-04-01 | 1 | 3 | 3 | Rule 1 = Reset
| 1234 | 11 | 2013-07-12 | 2 | 40 | 43 | Rule 2 = In
| 1234 | 11 | 2013-09-05 | 3 | 20 | 23 | Rule 3 = Out
| 1234 | 11 | 2013-12-31 | 1 | 25 | 25 | Rule 1 = Reset
| 1234 | 11 | 2014-01-09 | 3 | 11 | 14 | Rule 3 = Out
| 1234 | 11 | 2014-01-16 | 3 | 6 | 8 | Rule 3 = Out
I want to know the balance on any variable date.
Without your data, I can't test this but I believe this should be your solution.
SELECT i.ItemID
,w.WarehouseID
,[Qty] = tbl2.Quantity + tbl3.Quantity – tbl4.Quantity
FROM Item i
CROSS JOIN Warehouse w
OUTER APPLY (
SELECT [DATE1] = MAX(sh.CreateDate)
,sh.ItemID
,sh.Quantity
,sh.WarehouseID
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 1 AND sh.CreateDate <= #datevar
AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.Quantity, sh.WarehouseID ) tbl2
OUTER APPLY (
SELECT sh.ItemID
,sh.WarehouseID
,[Quantity] = SUM(sh.Quantity)
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 2 AND sh.CreateDate > tbl2.DATE1 --If exists
AND sh.CreateDate <= #datevar AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.WarehouseID ) tbl3
OUTER APPLY (
SELECT sh.ItemID
,sh.WarehouseID
,[Quantity] = SUM(sh.Quantity)
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 3 AND sh.CreateDate > tbl2.DATE1 --If exists
AND sh.CreateDate <= #datevar AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.WarehouseID ) tbl4