Fusion of two tables with Specific SUM operation in SQL Server 2012 - sql

I have two views in my database, here are their structures:
Table 1 (Stock product entries) :
---------------------------------------
| Date | Product | Quantity |
---------------------------------------
|2013-06-06| Procuct001 | 40 |
---------------------------------------
Table 2 (Stock product outputs) :
---------------------------------------
| Date | Product | Quantity |
---------------------------------------
|2013-06-07| Procuct001 | 15 |
---------------------------------------
|2013-06-08| Procuct001 | 5 |
---------------------------------------
I want to have a third view (or table) where I'll store all the stock's movements (entries or outputs) but I don't want to store the entered or retrieved quantity, but the difference (which means the stock's balance). In our case, the new table should contain :
Table 3 (Stock balance) :
---------------------------------------
| Date | Product | Quantity |
---------------------------------------
|2013-06-06| Procuct001 | 40 |
---------------------------------------
|2013-06-07| Procuct001 | 25 |
---------------------------------------
|2013-06-07| Procuct001 | 20 |
---------------------------------------
I am using SQL Server 2012 with SP1.

Here's one option using a Common Table Expression, utilizing ROW_NUMBER to seed your list (grouped by product):
with cte as (
select dt, product, quantity, row_number() over (partition by product order by dt) rn
from (
select * from t1
union
select * from t2
) t
)
select c.dt, c.product,
c2.quantity - coalesce(sum(c3.quantity),0) runningty
from cte c
inner join (
select product, quantity
from cte
where rn = 1) c2 on c.product = c2.product
left join cte c3 on c.product = c3.product and c3.rn <= c.rn and c3.rn <> 1
group by c.dt, c.product, c2.quantity
SQL Fiddle Demo
Depending on your data, you may not need to extra inner join to get your "1st" record -- you could perhaps use something like this instead:
with cte as (
select dt, product, quantity, row_number() over (partition by product order by dt) rn
from (
select * from t1
union
select * from t2
) t
)
select c.dt, c.product,
max(c.quantity) over (partition by c.product) - coalesce(sum(c3.quantity),0) runningty
from cte c
left join cte c3 on c.product = c3.product and c3.rn <= c.rn and c3.rn <> 1
group by c.dt, c.product, c.quantity

Related

SQL selecting the maximum value of multiple items with all the columns

The data:
My query:
SELECT
itemcode, whsecode, MAX(quantity)
FROM
inventoryTable
GROUP BY
itemcode;
This returns this error:
Column 'inventoryTable.whsecode' is invalid in the select list because
it is not contained in either an aggregate function or the GROUP BY
clause.
When I put the whsecode in the GROUP BY clause, it just returns all the data in the table.
The output that I want is to return the whsecode with the highest quantity of the item in it.
The output that it supposed to have is:
whsecode|itemcode|quantity
WHSE2 | SS585 | 50
WHSE2 | SS586 | 50
WHSE1 | SS757 | 30
Eventually I will put that query inside this another query:
SELECT
A.mrno, A.remarks,
B.itemcode, B.description, B.uom, B.quantity,
C.whsecode, C.whseqty, D.rate
FROM
Mrhdr A
INNER JOIN
Mrdtls B ON A.mrno = B.mrno
INNER JOIN
(
SELECT itemcode, whsecode, MAX(quantity) AS whseqty
FROM inventoryTable
GROUP BY itemcode, whsecode
) C ON B.itemcode = C.itemcode
INNER JOIN
Items D ON B.itemcode = D.itemcode
WHERE
A.mrno = #MRNo AND B.quantity < C.whseqty;
with the whsecode inside the GROUP BY clause the output is:
But as I said earlier, the problem is it returns multiple rows of the same itemcode. The output that it supposed to have is:
mrno | remarks| itemcode| description | uom |quantity|whsecode|whseqty| rate
MR211100003008 | SAMPLE | FG 4751 | LONG DRILL 3.4 X 200 L550 | PCS. | 50.00 | WHSE3 | 100 | 0.0000
MR211100003008 | SAMPLE | FG 5092 | T-SPIRAL TAP M3.0 X 0.5 L6904 | PCS | 20.00 | WHSE1 | 80 | 0.0000
I am not sure if the B.quantity < C.whseqty should be there but it eliminates the other values that are not the maximum value.
There are many ways to solve this. For example, by using the ROW_NUMBER function:
SELECT
itemcode,
whsecode,
quantity As whseqty
FROM
(
SELECT
itemcode,
whsecode,
quantity,
ROW_NUMBER() OVER (PARTITION BY itemcode ORDER BY quantity DESC) As RN
FROM
inventoryTable
)
WHERE
RN = 1
;
Edit:
select whsecode,A.itemcode,qty from inventoryTable
join (SELECT itemcode, MAX(quantity) as qty FROM inventoryTable GROUP BY itemcode) as A on A.itemcode = inventoryTable.itemcode and A.qty = inventoryTable.quantity

Select N rows in aggregate functions SQL Server

I have a table that looks like this:
+--------+----------+--------+------------+-------+
| ID | CHANNEL | VENDOR | num_PERIOD | SALES |
+--------+----------+--------+------------+-------+
| 000001 | Business | Shop | 1 | 40 |
| 000001 | Business | Shop | 2 | 60 |
| 000001 | Business | Shop | 3 | NULL |
+--------+----------+--------+------------+-------+
With many combinations of ID, CHANNEL and VENDOR, and sales records for each of them over time (num_PERIOD).
The idea is to obtain a new column which returns the number of NULLS in SALES column, but in the first 111 registers according to num_PERIOD column.
I have been trying something like this:
SELECT ID,
CHANNEL,
VENDOR,
sum(CASE
WHEN SALES IS NULL THEN 1
ELSE 0
END) OVER (PARTITION BY ID,
CHANNEL,
VENDOR
ORDER BY num_PERIOD ROWS BETWEEN UNBOUNDED PRECEDING AND 111 FOLLOWING) AS NULL_SALES_SET
FROM TABLE
GROUP BY ID,
CHANNEL,
VENDOR
But I'm not obtaining what I'm looking for.
So to obtain a table simillar to:
+--------+--------------+--------+----------------+
| ID | CHANNEL | VENDOR | NULL_SALES_SET |
+--------+--------------+--------+----------------+
| 000001 | Business | Shop | 1 |
| 000002 | Business | Market | 0 |
| 000002 | Non Business | Shop | 3 |
+--------+--------------+--------+----------------+
The difficulty comes when selecting these first 111 rows per ID, CHANNEL AND VENDOR ordered by num_PERIOD.
Use a CTE (Common Table Expression) with the ROW_NUMBER windowed function and you should be set:
;WITH MyCTE AS
(
SELECT
id,
channel,
vendor,
sales,
ROW_NUMBER() OVER (PARTITION BY id, channel, vendor ORDER BY num_period) AS row_num
FROM
MyTable
)
SELECT
id,
channel,
vendor,
SUM(CASE WHEN sales IS NULL THEN 1 ELSE 0 END) AS null_sales_set
FROM
MyCTE
WHERE
row_num <= 111
GROUP BY
id, channel, vendor
Do you have to use the windowing function?
SELECT ID
, CHANNEL
, VENDOR
, NULL_SALES_SET = SUM(CASE WHEN SALES IS NULL THEN 1 ELSE 0 END)
FROM Table
WHERE num_PERIOD <= 111
GROUP BY ID, CHANNEL, VENDOR
Or are you looking for the first 111 num_PERIOD values allowing for gaps in the num_PERIOD column?
SELECT t.ID
, t.CHANNEL
, t.VENDOR
, NULL_SALES_SET = SUM(CASE WHEN t.SALES IS NULL THEN 1 ELSE 0 END)
FROM Table t
INNER JOIN ( SELECT i.ID
, i.CHANNEL
, i.VENDOR
, i.num_PERIOD
, rowNum = ROW_NUMBER(PARTITION BY i.ID, i.CHANNEL, i.VENDOR ORDER BY i.num_PERIOD)
FROM Table i ) l
ON t.ID = l.ID
AND t.CHANNEL = l.CHANNEL
AND t.VENDOR = l.VENDOR
AND t.num_PERIOD = l.num_PERIOD
WHERE l.rowNum <= 111
GROUP BY ID, CHANNEL, VENDOR
Edit: Not sure how I overlooked it, but it is necessary to JOIN on the num_PERIOD column.
Edit: Add the number of distinct num_PERIOD per ID, Channel, Vendor without affecting the NULL_SALES_SET
SELECT t.ID
, t.CHANNEL
, t.VENDOR
-- Counts the NULL Sales when the num_PERIOD is in the
-- first 111 num_PERIODs
, NULL_SALES_SET = SUM(CASE WHEN l.rowNum IS NOT NULL AND t.SALES IS NULL
THEN 1
ELSE 0 END)
-- Counts the distinct num_PERIOD values
, PERIOD_COUNT = COUNT(DISTINCT t.num_PERIOD)
FROM Table t
LEFT OUTER JOIN ( SELECT i.ID
, i.CHANNEL
, i.VENDOR
, i.num_PERIOD
, rowNum = ROW_NUMBER(PARTITION BY i.ID,
i.CHANNEL,
i.VENDOR
ORDER BY i.num_PERIOD)
FROM Table i ) l
ON t.ID = l.ID
AND t.CHANNEL = l.CHANNEL
AND t.VENDOR = l.VENDOR
AND t.num_PERIOD = l.num_PERIOD
AND l.rowNum <= 111
GROUP BY ID, CHANNEL, VENDOR

sum result is being multiplied by number of rows

I'm trying to get 2 sums from my tables, one is a table of ordered items and another is a table of received items. With my current query i'm getting a sum of 2014 which is from 106(the actual sum I want to get) multiplied by 19(the number of records)
select POD.PO_No, SUM(POD.Qty) as Qty, RRD.RR_No, SUM(RRD.QtyRcvd) as QtyReceived
from tbl_PODetail POD inner join tbl_RRDetail RRD on POD.PO_No = RRD.PO_Reference
where POD.PO_No = 'PO-000001'
group by POD.PO_No, RRD.RR_No
What causes this and how can I correct it?
Sample Table
PO No | Item | Qty
-------------------------
PO-0000001 | Item A | 5
PO-0000001 | Item B | 7
PO-0000001 | Item B | 3
RR No | Item | Qty | PO_Reference
----------------------------------------
RR-0000001 | Item A | 5 | PO-0000001
RR-0000001 | Item B | 7 | PO-0000001
RR-0000001 | Item B | 3 | PO-0000001
Expected Output:
PO No | Qty | RR No | Qty Rcvd
-----------------------------------------
PO-0000001 | 15 | RR-0000001 | 15
What I get
PO No | Qty | RR No | Qty Rcvd
-----------------------------------------
PO-0000001 | 45 | RR-0000001 | 45
You missed the POD.Item = RRD.Item on join, that's why you are getting multiplied records.
select POD.PO_No, SUM(POD.Qty) as Qty, RRD.RR_No, SUM(RRD.QtyRcvd) as QtyReceived
from tbl_PODetail POD
inner join tbl_RRDetail RRD on POD.PO_No = RRD.PO_Reference and POD.Item = RRD.Item
where POD.PO_No = 'PO-000001'
group by POD.PO_No, RRD.RR_No
Try aggregating the values in subqueries and joining them afterwards
Select *
From (
Select po_no, sum(qty) qty
From tbl_PODetail
Group by po_no
) p join (
Select rr_no, po_reference, sum(qty) qty_rcvd
From tbl_RRDetail
Group by rr_no, po_reference
) r on p.po_no = r.po_reference
Where p.po_no = 'PO-0000001'
Try this also. Hope this could help.
select * into #table1 from (
select 'PO-0000001' as PO_No,'Item A' as Item,5 as QTY union
select 'PO-0000001' as PO_No,'Item B' as Item,7 as QTY union
select 'PO-0000001' as PO_No,'Item C' as Item,3 as QTY
)t
select * into #table2 from (
select 'RR-0000001' as RR_No,'Item A' as Item,5 as QTY,'PO-0000001' as PO_Referrence union
select 'RR-0000001' as RR_No,'Item B' as Item,7 as QTY,'PO-0000001' as PO_Referrence union
select 'RR-0000001' as RR_No,'Item C' as Item,3 as QTY,'PO-0000001' as PO_Referrence
)t
select t1.PO_No,sum(t2.qty) as 'Qty' ,t2.RR_no,sum(t1.qty) as 'QTY Rcvd'
from #table1 t1
inner join #table2 t2
on t1.PO_no=t2.PO_Referrence and t1.item=t2.item
group by t1.PO_No,t2.RR_no
This answers reflects what my understanding of your problem is. It appears that you want to aggregate the tbl_PODetail by PO number and compute a sum as quantity. Then, you want to aggregate a second table tbl_RRDetail by two columns, and join the first aggregated result to that using only the PO number. If this be correct, then one approach would be to use two separate subqueries for the aggregation and join them together.
WITH cte1 AS (
SELECT RR_No, PO_Reference, SUM(QtyRcvd) AS QtyReceived
FROM tbl_RRDetail
GROUP BY RR_No, PO_Reference
),
WITH cte2 AS (
SELECT PO_No, SUM(Qty) AS Qty
FROM tbl_PODetail
GROUP BY PO_No
)
SELECT t2.PO_No, t2.Qty, t1.RR_No, t1.QtyReceived
FROM cte1 t1
INNER JOIN cte2 t2
ON t1.PO_Reference = t2.PO_No
WHERE t1.PO_No = 'PO-000001'

How to calculate max(column) row from a result set grouped by different items

SELECT T1.* FROM
(
SELECT
MAX(no_of_orders) [no_of_orders],
ord.customer_id [customer_id],
ord_dtl.item_id [item_id]
FROM order_main ord
INNER JOIN order_detail ord_dtl
ON ord.order_id = ord_dtl.order_id
GROUP BY
ord.customer_id, ord_dtl.item_id
) T1
INNER JOIN
(
SELECT
MAX(no_of_orders) [no_of_orders],
ord.customer_id
FROM order_main ord
INNER JOIN order_detail ord_dtl
ON ord.order_id = ord_dtl.order_id
GROUP BY
ord.customer_id
) T2
ON
T1.customer_id = T2.customer_id
AND T1.no_of_orders = T2.no_of_orders
I calculated the most ordered item by customer from table order_main and order_detail.
What I did is like I calculated the no of orders grouping by customer id and item id and to reduce the result set to most ordered item by customer, I joined the resultset by same query removing the grouping by item id.
I don't feel this is a good query.
Any better alternative ?
SQL Fiddle
Most ordered item by customer
Query:
SQLFIDDLEExample
SELECT a.no_of_orders,
a.customer_id,
a.item_id
FROM (SELECT
MAX(no_of_orders) [no_of_orders],
ord.customer_id [customer_id],
ord_dtl.item_id [item_id],
ROW_NUMBER() OVER(PARTITION BY ord.customer_id ORDER BY MAX(no_of_orders) DESC) AS rnk
FROM order_main ord
INNER JOIN order_detail ord_dtl
ON ord.order_id = ord_dtl.order_id
GROUP BY
ord.customer_id, ord_dtl.item_id) a
WHERE a.rnk = 1
Result:
| NO_OF_ORDERS | CUSTOMER_ID | ITEM_ID |
---------------|-------------|---------|
| 20 | 2 | 6 |
| 50 | 7 | 6 |
| 1500 | 8 | 7 |
| 10 | 9 | 3 |

Select from results of query?

I have a query like this:
SELECT Weighings.Member, MIN(Sessions.DateTime) AS FirstDate, MAX(Sessions.DateTime) AS LastDate
FROM Weighings AS Weighings INNER JOIN
Sessions ON Sessions.SessionGUID = Weighings.Session
WHERE (Sessions.DateTime >= '01/01/2011')
GROUP BY Weighings.Member
ORDER BY Weighings.Member
It returns this:
Member | FirstDate | LastDate
Blah | 01/01/11 | 06/07/11
Blah2 | 02/03/11 | 05/07/11
I need to get the value of a cell Weight_kg in table Weighings for the returned values FirstDate and LastDate to give results like so:
Member | FirstWeight | LastWeight
Blah | 150Kg | 60KG
Blah2 | 70Kg | 72KG
I have tried all combinations of things but just can't work it out, any ideas?
EDIT
Tables:
Sessions
______________________
SessionGUID | DateTime
----------------------
12432524325 | 01/01/11
12432524324 | 01/08/11
12432524323 | 01/15/11
34257473563 | 03/05/11
43634574545 | 06/07/11
Weighings
_____________________________________
Member | Session | Weight_kg
-------------------------------------
vffd8fdg87f | 12432524325 | 150
vffd8fdg87f | 12432524324 | 120
vffd8fdg87f | 12432524323 | 110
ddffv89sdv8 | 34257473563 | 124
32878vfdsv8 | 43634574545 | 75
;with C as
(
select W.Member,
W.Weight_kg,
row_number() over(partition by W.Member order by S.datetime desc) as rnLast,
row_number() over(partition by W.Member order by S.datetime asc) as rnFirst
from Weighings as W
inner join Sessions as S
on S.sessionguid = W.Session and
S.DateTime >= '20110101'
)
select CF.Member,
CF.Weight_kg as FirstWeight,
CL.Weight_kg as LastWeigth
from C as CF
inner join C as CL
on CF.Member = CL.Member
where CF.rnFirst = 1 and
CL.rnLast = 1
Try here: https://data.stackexchange.com/stackoverflow/q/118518/
You can use the RANK..OVER stmt (works only on SQL 2k5+)
select fw.Member, st.Weight, en.Weight
from
(
select Member, Weight, RANK() OVER(PARTITION BY Member ORDER BY Weight) rnk
from Weighings
) st
inner join
(
select Member, Weight, RANK() OVER(PARTITION BY Member ORDER BY WeightDESC) rnk
from Weighings
) en on en.Member= st.Member and st.rnk = 1 and en.rnk = 1
You have two possibilities.
If you want to reuse the first SELECT more times, I'd suggest to sreate temporary table
CREATE TEMPORARY TABLE `tmpTable` AS SELECT /*the first select*/ ;
/*and then*/
SELECT * FROM `tmpTable` /*the second select from the first select*/
If you require the first select only once
SELECT first.*
FROM (SELECT /*the first select*/) AS first