SQL SUM() conditions questions - sql-server

I have a table that looks like this:
+----+------+--------+----------+
| ID | Code | OpType | Quantity |
+----+------+--------+----------+
| 0 | A | IN | 7 |
| 1 | B | IN | 8 |
| 2 | A | OUT | 2 |
| 3 | B | IN | 7 |
| 4 | B | OUT | 12 |
+----+------+--------+----------+
I want the SUM(Quantity) depending on the OpType. When OpType is OUT, the Quantity field should be multiplied with -1.
The result of the query should be:
Code IN OUT Final
A 7 2 5
B 15 12 3
I've tried this, but it doesn't work:
SELECT(SELECT SUM(Quantity) FROM Table WHERE OpType = 'IN') AS[IN], (SELECT SUM(Quantity) FROM Table WHERE OpType = 'OUT') AS[OUT], (SELECT SUM(Quantity) FROM Table WHERE OpType = 'IN') - (SELECT SUM(Quantity) FROM Table WHERE OpType = 'OUT') AS[Final]
FROM Table
GROUP BY Code

SQL Server has PIVOT functionality.
SELECT [Code], [IN], [OUT], [IN] - [OUT] AS [Final]
FROM
(
SELECT [Code], OpType, SUM(Quantity) Quantity
FROM TableName
GROUP BY [Code], OpType
) org
PIVOT
(
MAX(Quantity)
FOR OpType IN ([IN],[OUT])
) pvt
SQLFiddle Demo
TSQL PIVOT
OUTPUT
╔══════╦════╦═════╦═══════╗
║ CODE ║ IN ║ OUT ║ FINAL ║
╠══════╬════╬═════╬═══════╣
║ A ║ 7 ║ 2 ║ 5 ║
║ B ║ 15 ║ 12 ║ 3 ║
╚══════╩════╩═════╩═══════╝

I would use a CASE statement inside the SUM for each column so that you only sum the desired values (i.e. for the IN column only SUM up the quantities with OpType='IN' and for the OUT column only SUM up the quantities with OpType='OUT')
SELECT Code,
SUM(CASE WHEN OpType = 'IN' THEN Quantity ELSE 0 END) as [IN],
SUM(CASE WHEN OpType = 'OUT' THEN Quantity ELSE 0 END) as [OUT],
SUM(CASE WHEN OpType = 'OUT' THEN -1 * Quantity ELSE Quantity END) as [FINAL]
FROM Table
GROUP BY Code
UPDATE: In a comment to JW 웃's answer, you asked about getting the IN, OUT and FINAL sums between two dates. Below is an updated query which will do that.
DECLARE #InitialSumDate DATETIME = '4/22/2013', #EndDate DATETIME = '4/23/2013'
SELECT
#InitialSumDate as [InitialSumDate], #EndDate as [EndDate],
Code,
SUM(CASE WHEN PurchaseDate <= #InitialSumDate AND OpType = 'OUT' THEN -1 * Quantity WHEN PurchaseDate <= #InitialSumDate AND OpType = 'IN' THEN Quantity ELSE 0 END) as [InitialSum],
SUM(CASE WHEN PurchaseDate > #InitialSumDate AND PurchaseDate <= #EndDate AND OpType = 'IN' THEN Quantity ELSE 0 END) as [IN],
SUM(CASE WHEN PurchaseDate > #InitialSumDate AND PurchaseDate <= #EndDate AND OpType = 'OUT' THEN Quantity ELSE 0 END) as [OUT],
SUM(CASE WHEN OpType = 'OUT' AND PurchaseDate <= #EndDate THEN -1 * Quantity WHEN OpType = 'IN' AND PurchaseDate <= #EndDate THEN Quantity ELSE 0 END) as [FINAL]
FROM #Table
GROUP BY Code

Related

SQL server Query for SUM group

I have table:
-------------------------------------
| Code | Name | Type | Qty |
-------------------------------------
| 0001 | Mouse | Purchase | 5 |
| 0002 | KeyBrd | Purchase | 8 |
| 0003 | Mouse | Sale | 2 |
-------------------------------------
i want to select them like this:
------------------------------------------------------------
| Code | Name | Total Purchase | Total Sale | Stock |
------------------------------------------------------------
| 0001 | Mouse | 5 | 2 | 3 |
| 0002 | KeyBrd | 8 | 0 | 8 |
------------------------------------------------------------
Im so confused, please help me with the query, thank you in advance
Try This
SELECT
Code,
Name,
TotalPurchase = SUM(CASE WHEN [Type] = 'Purchase' THEN Qty ELSE 0 END),
TotalSale = SUM(CASE WHEN [Type] = 'Sale' THEN Qty ELSE 0 END),
Stock = SUM(CASE WHEN [Type] = 'Purchase' THEN Qty ELSE 0 END) - SUM(CASE WHEN [Type] = 'Sale' THEN Qty ELSE 0 END)
FROM YourTable
group by Code,
Name
Conditional aggregation with the help of case expression is one approach
select min(Code) Code,
Name,
sum(case when Type = 'Purchase' then Qty else 0 end) [Total Purchase],
sum(case when Type = 'Sale' then Qty else 0 end) [Total Sale],
sum(case when Type = 'Purchase' then -Qty else Qty end) Stock
from table t
group by Name
We can also use like below.(Using Pivot) Please try this.
Data Generation
CREATE TABLE TableCase
(
Code VARCHAR(10)
,[Name] VARCHAR(10)
,[Type] VARCHAR(20)
,Qty INT
)
GO
INSERT INTO TableCase VALUES
('0001','Mouse' ,'Purchase',5),
('0002','KeyBrd' ,'Purchase',8),
('0003','Mouse' ,'Sale',2 )
GO
SOLUTION
SELECT MIN(Code) Code,[Name]
, ISNULL(SUM([Purchase]),0) [TotalPurchase]
, ISNULL(SUM([Sale]),0) TotalSale
, ISNULL(SUM([Purchase]),0) - ISNULL(SUM([Sale]),0) Stock
FROM TableCase
PIVOT
(
MAX(Qty) FOR [Type] IN ([Purchase],[Sale])
)t
GROUP By [Name]
ORDER BY [code]
OUTPUT
Code Name TotalPurchase TotalSale Stock
---------- ---------- ------------- ----------- -----------
0001 Mouse 5 2 3
0002 KeyBrd 8 0 8
(2 rows affected)

Convert a (<=4) x 2 SQL result set to 1 x 8 result set

I've spent much too long trying to figure out how TSQL pivoting works, so I'm posting this hoping someone that understands pivots can help me.
So here's a representative source result set:
Notice that there are only 3 rows, but that's still <=4.
| Code | Date |
|-------|------------|
| 12345 | 2018-01-01 |
| 67890 | NULL |
| 13579 | 2018-01-02 |
This is target result set I want from the source result set:
| Code_1 | Date_1 | Code_2 | Date_2 | Code_3 | Date_3 | Code_4 | Date_4 |
|--------|------------|--------|--------|--------|------------|--------|--------|
| 12345 | 2018-01-01 | 67890 | NULL | 13579 | 2018-01-02 | NULL | NULL |
Here's something that does literally what you're asking by joining 2 pivot queries together:
DECLARE #tbl TABLE (code INT, dt DATETIME)
INSERT INTO #tbl VALUES (12345, '1/1/2018')
INSERT INTO #tbl VALUES (67890, null)
INSERT INTO #tbl VALUES (13579, '1/1/2018')
SELECT SQ1.Code_1, SQ2.Code_1, SQ1.Code_2, SQ2.Code_2, SQ1.Code_3, SQ2.Code_3, SQ1.Code_4, SQ2.Code_4
FROM
(
SELECT [Code_1], [Code_2], [Code_3], [Code_4]
FROM
(
SELECT 'Code_' + CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS VARCHAR(3)) AS RowID, code FROM #tbl
) source
PIVOT
(
MAX(code)
FOR RowID IN ([Code_1], [Code_2], [Code_3], [Code_4])
)sq
)SQ1
CROSS APPLY
(
SELECT [Code_1], [Code_2], [Code_3], [Code_4]
FROM
(
SELECT 'Code_' + CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS VARCHAR(3)) AS RowID, dt FROM #tbl
) source
PIVOT
(
MAX(dt)
FOR RowID IN ([Code_1], [Code_2], [Code_3], [Code_4])
)sq
)SQ2
Outputs:
Code_1 Code_1 Code_2 Code_2 Code_3 Code_3 Code_4 Code_4
12345 2018-01-01 67890 NULL 13579 2018-01-01 NULL NULL
Assuming you want a max of 4 groups (8 columns).
You may also notice that in the Order By I use (Select null). Without a proper sequence key, there is no gtd of order.
Example
Select Code_1 = max(case when Col=1 then Code end)
,Date_1 = max(case when Col=1 then Date end)
,Code_2 = max(case when Col=2 then Code end)
,Date_2 = max(case when Col=2 then Date end)
,Code_3 = max(case when Col=3 then Code end)
,Date_3 = max(case when Col=3 then Date end)
,Code_4 = max(case when Col=4 then Code end)
,Date_4 = max(case when Col=4 then Date end)
From (
Select *
,Col = Row_Number() over (Order by (Select null))
From #YourTable
) A
Returns
Code_1 Date_1 Code_2 Date_2 Code_3 Date_3 Code_4 Date_4
12345 2018-01-01 67890 NULL 13579 2018-01-01 NULL NULL

Change output according to LAG

I have the current issue:
I'm trying to get the amount of time each of our workers have worked in a day to calculate our company's productivity. We have the time each of our workers has entered and left the building.
The rule is, sometimes our workers leaves the building to smoke or get something from the news stand outside, so we don't take that into consideration and count as if the person never left the building.
We have a cafeteria inside our building so most people don't actually leave the building to have lunch/dinner, so we just remove 1 hour from their productivity calculation, but, if they leave for more then 45 minutes, we will consider that the worker left to lunch/dinner.
I need the end result to look like this:
+----------+----------------+----------------+---------+----------+
| PersonID | IN | OUT | MINUTES | EatOut |
+----------+----------------+----------------+---------+----------+
| 1 | 20170807 08:00 | 20170807 17:25 | 465 | 1 |
+----------+----------------+----------------+---------+----------+
| 2 | 20170807 08:00 | 20170807 17:00 | 540 | 0 |
+----------+----------------+----------------+---------+----------+
My query I have so far:
DECLARE #mytable TABLE(
PersonId INT,
Situation VARCHAR(3),
SituationDtm DATETIME
);
INSERT INTO #mytable VALUES
(1, 'IN', '20170807 08:00'),
(1, 'OUT', '20170807 12:30'),
(1, 'IN', '20170807 14:00'),
(1, 'OUT', '20170807 17:15'),
(2, 'IN', '20170807 08:00'),
(2, 'OUT', '20170807 09:15'),
(2, 'IN', '20170807 09:30'),
(2, 'OUT', '20170807 17:00');
WITH CTE AS (
SELECT
[PersonId],
Situation AS 'CUR.Situation',
SituationDtm AS 'CUR.SituationDtm',
LEAD(Situation) OVER(PARTITION BY PersonId ORDER BY SituationDtm) AS 'NEXT.Situation',
LEAD(SituationDtm) OVER(PARTITION BY PersonId ORDER BY SituationDtm) AS 'NEXT.SituationDtm'
FROM
#mytable
)
SELECT
[CUR.Situation],
[CUR.SituationDtm],
[NEXT.Situation],
[NEXT.SituationDtm],
DATEDIFF(MINUTE, [CUR.SituationDtm], [NEXT.SituationDtm]) AS 'MINUTES'
FROM
CTE
Thanks in advance
You can further query as below: Since you are looking your solution in SQL Server 2008 where you do not have lead/lag you can query as below:
;With Cte as (
Select *, RowN = Row_Number() over(Partition by PersonId order by SituationDtm) from #mytable
), Cte2 as (
Select c1.*, c2.Situation as NextSituation, c2.SituationDtm as NextSituationDtm from cte c1 left join cte c2 on c1.RowN+1 = c2.RowN
and c1.PersonId = c2.PersonId
)
Select PersonId,
Min(SituationDTM) as [In],
Max(Situationdtm) as [Out],
Sum(Case when Situation = 'OUT' and NextSituation = 'IN' and datediff(mi,SituationDtm, NextSituationDTM) > 60 then 1 else 0 end) EatOut,
Sum(Case when Situation = 'OUT' and NextSituation = 'IN' and datediff(mi,SituationDtm, NextSituationDTM) > 60 then 0 else datediff(mi,SituationDtm, NextSituationDTM) end) as [minutes]
from Cte2
group by PersonId
In later versions after >= 2012 you can query as below:
Select PersonId,
Min(SituationDTM) as [In],
Max(Situationdtm) as [Out],
Sum(Case when Situation = 'OUT' and NextSituation = 'IN' and datediff(mi,SituationDtm, NextSituationDTM) > 60 then 1 else 0 end) EatOut,
Sum(Case when Situation = 'OUT' and NextSituation = 'IN' and datediff(mi,SituationDtm, NextSituationDTM) > 60 then 0 else datediff(mi,SituationDtm, NextSituationDTM) end) as [minutes]
from (
Select *, NextSituationDTM = lead(situationdtm) over (partition by personid order by situationdtm),
NextSituation = lead(Situation) over (partition by personid order by situationdtm) from #mytable
) a
group by PersonId
Output as below:
+----------+-------------------------+-------------------------+--------+---------+
| PersonId | In | Out | EatOut | minutes |
+----------+-------------------------+-------------------------+--------+---------+
| 1 | 2017-08-07 08:00:00.000 | 2017-08-07 17:15:00.000 | 1 | 465 |
| 2 | 2017-08-07 08:00:00.000 | 2017-08-07 17:00:00.000 | 0 | 540 |
+----------+-------------------------+-------------------------+--------+---------+

Rearranging a table in tsql

So I have a table with the following columns:
Type Test Min Max
-----------------------------
1 a 1 2
1 b Null Null
2 a 0 Null
2 b Null 1
Trying to get all of them like this
Type Test1 Test1 min Test1max Test2 Test2min Test2max
------------------------------------------------------------
1 a 1 2 b Null Null
2 a 0 Null b Null 1
Tried using unpivot first before I use pivot but it's still giving duplicate tests and removing null any help with this is much appreciated
Need null values to show up as well
Select type, result
(
From
Select type, min
From table t
)
Unpivot
(result for minmax in (min1)
)
Select
Thanks
using row_number() and conditional aggregation:
select
[Type]
, Test_1 = max(case when rn = 1 then Test end)
, Test_1_Min = max(case when rn = 1 then Min end)
, Test_1_Max = max(case when rn = 1 then Max end)
, Test_2 = max(case when rn = 2 then Test end)
, Test_2_Min = max(case when rn = 2 then Min end)
, Test_2_Max = max(case when rn = 2 then Max end)
from (
select *
, rn = row_number() over (partition by [Type] order by [Test])
from t
) as s
group by s.Type
rextester demo: http://rextester.com/BKL48976
returns:
+------+--------+------------+------------+--------+------------+------------+
| Type | Test_1 | Test_1_Min | Test_1_Max | Test_2 | Test_2_Min | Test_2_Max |
+------+--------+------------+------------+--------+------------+------------+
| 1 | a | 1 | 2 | b | NULL | NULL |
| 2 | a | 0 | NULL | b | NULL | 1 |
+------+--------+------------+------------+--------+------------+------------+
select *
from table t1
join table t2
on t1.type = t2.type
and t1.test < t2.test

SQL SERVER How to combine two query on the same table that has "group by"

I want to combine two queries on the same table that has group by.
here is my table :
Date##### | Value1 | Value2 | Value3 | Type
23/04/2014 | 1,2 | 12,3 | 10 | Green
23/04/2014 | 11,2 | 3 | 10,3 | Non-Green
24/04/2014 | 10,9 | 3 | 11 | Green
24/04/2014 | 2,3 | 12,3 | 8 | Green
25/04/2014 | 10 | 2 | 10,8 | Non-Green
25/04/2014 | 1,4 | 5 | 12 | Green
I want it to display:
Date##### | Count |Type
23/04/2014 | 2 | Green
23/04/2014 | 2 | Non-Green
24/04/2014 | 3 | Green
25/04/2014 | 2 | Non-Green
25/04/2014 | 1 | Green
here is my first query :
Dim strCommand As String = "SELECT d, LW, cnt FROM(SELECT TOP 7 [date] AS d, [Type] as LW, SUM(CASE WHEN ManualAssists1 >= 10 THEN 1 ELSE 0 END + CASE WHEN ManualAssists2 >= 10 THEN 1 ELSE 0 END + CASE WHEN ManualAssists3 >= 10 THEN 1 ELSE 0 END) AS cnt FROM tbBooth where Type = 'Green' GROUP BY [date],[Type] ORDER BY [date] DESC) x ORDER BY d ASC"
It display :
Date##### | Count |Type
23/04/2014 | 2 | Green
24/04/2014 | 3 | Green
25/04/2014 | 1 | Green
my second query :
Dim strCommand As String = "SELECT d, LW, cnt FROM(SELECT TOP 7 [date] AS d, [Type] as LW, SUM(CASE WHEN ManualAssists1 >= 10 THEN 1 ELSE 0 END + CASE WHEN ManualAssists2 >= 10 THEN 1 ELSE 0 END + CASE WHEN ManualAssists3 >= 10 THEN 1 ELSE 0 END) AS cnt FROM tbBooth where Type = 'Non-Green' GROUP BY [date],[Type] ORDER BY [date] DESC) x ORDER BY d ASC"
It display :
Date##### | Count |Type
23/04/2014 | 2 | Non-Green
25/04/2014 | 2 | Non-Green
I want to combine both query to become one, and take the date, result of green and result of non-green. Assume that I have a lot of group of date, I want it to display only the last 7 group of date based on ASC order. I try to add UNION between my two query. Here is my code so far:
Dim strCommand As String = "Select 'Test1', * from tbBooth where type ='Green' and exist(SELECT d, LW, cnt FROM(SELECT TOP 7 [date] AS d, [Type] as LW, SUM(CASE WHEN Value1 >= 10 THEN 1 ELSE 0 END + CASE WHEN Value2 >= 10 THEN 1 ELSE 0 END + CASE WHEN Value3 >= 10 THEN 1 ELSE 0 END) AS cnt FROM tbBooth GROUP BY [date],[Type] ORDER BY [date] DESC) x ORDER BY d ASC) Union Select 'Test2', * from tbBooth where type ='Non-Green' and exist(SELECT d, LW, cnt FROM(SELECT TOP 7 [date] AS d, [Type] as LW, SUM(CASE WHEN Value1 >= 10 THEN 1 ELSE 0 END + CASE WHEN Value2 >= 10 THEN 1 ELSE 0 END + CASE WHEN Value3 >= 10 THEN 1 ELSE 0 END) AS cnt FROM tbBooth GROUP BY [date],[Type] ORDER BY [date] DESC) x ORDER BY d ASC)
it give me an error Incorrect syntax near the keyword 'SELECT'.
is ther anyway how to do it? I tried to look on other SO post but I don't have any clue to do it because both my query are complex...
Thanks in advances....
It's not that your combined query is complex, it's just that it's wrong.
What I don't understand is why you have the WHERE Type = 'Green' and 'Non-green'.
Given what you want as result, you could just remove that where and get pretty much what you're after:
SELECT d ,
LW ,
cnt
FROM ( SELECT TOP 7
[date] AS d ,
[Type] AS LW ,
SUM(CASE WHEN ManualAssists1 >= 10 THEN 1
ELSE 0
END + CASE WHEN ManualAssists2 >= 10 THEN 1
ELSE 0
END + CASE WHEN ManualAssists3 >= 10 THEN 1
ELSE 0
END) AS cnt
FROM tbBooth
--WHERE Type = 'Green'
GROUP BY [date] ,
[Type]
ORDER BY [date] DESC
) x
ORDER BY d ASC
Which gives:
2014-04-23 Green 2
2014-04-23 Non-Green 2
2014-04-24 Green 3
2014-04-25 Green 1
2014-04-25 Non-Green 2
I see no need to "combine" the two queries when the two queries are identical besides the 'Type' in the WHERE?
Bro, in your code change exist to exists
:) this should Work
Updated:
Add Top 7 to select
Select 'Test1', * from tbBooth
where type ='Green' and exists(SELECT **top 7** d, LW, cnt FROM(SELECT TOP 7 [date] AS d,
[Type] as LW,
SUM(CASE WHEN Value1 >= 10 THEN 1 ELSE 0 END
+ CASE WHEN Value2 >= 10 THEN 1 ELSE 0 END
+ CASE WHEN Value3 >= 10 THEN 1 ELSE 0 END) AS cnt
FROM tbBooth GROUP BY [date],[Type]
ORDER BY [date] DESC) x ORDER BY d ASC)
Union
Select 'Test2', * from tbBooth where type ='Non-Green'
and exists(SELECT **top 7** d, LW, cnt FROM(SELECT TOP 7
[date] AS d, [Type] as LW, SUM(CASE WHEN Value1 >= 10 THEN 1 ELSE 0 END
+ CASE WHEN Value2 >= 10 THEN 1 ELSE 0 END
+ CASE WHEN Value3 >= 10 THEN 1 ELSE 0 END) AS cnt
FROM tbBooth GROUP BY [date],[Type] ORDER BY [date] DESC) x ORDER BY d ASC)

Resources