T-SQL query suggestion - sql

CREATE TABLE #Emp
(
ID int,
Name varchar(100)
)
INSERT INTO #Emp
VALUES (1, 'AAA'), (2, 'BBB'), (3, 'CCC')
CREATE TABLE #Task
(
EmpID int,
TaskName varchar(100),
[Hours] int
)
INSERT INTO #Task
VALUES (1, 'Task-1', 2), (1, 'Task-2', 4), (1, 'Task-5', 3),
(2, 'Task-3', 2), (2, 'Task-4', 4), (2, 'Task-5', 3),
(3, 'Task-1', 2), (3, 'Task-1', 4), (3, 'Task-1', 6),
(3, 'Task-2', 3), (3, 'Task-6', 1)
#Emp
ID Name
--------
1 AAA
2 BBB
3 CCC
#Task:
EmpID TaskName Hours
-------------------------
1 Task-1 2
1 Task-2 4
1 Task-5 3
2 Task-3 2
2 Task-4 4
2 Task-5 8
3 Task-1 2
3 Task-1 4
3 Task-1 6
3 Task-2 3
3 Task-6 1
For each employee I need to get the sum of hours for (Task-1 and Task-2) and for Task-5
Something like below
Name PrepHours(Task-1 + Task-2) ReviewHours(Task-5)
-------------------------------------------------------
AAA 6 3
BBB 0 8
CCC 15 0
I tried the query shown here, but it fails with error column #Task.TaskName is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
SELECT
Name, PrepHours, ReviewHours
FROM
#Emp AS E
JOIN
(SELECT
empid,
CASE
WHEN Taskname IN ('Task-1','Task-2')
THEN SUM(Hours)
ELSE 0
END AS 'PrepHours',
CASE
WHEN Taskname IN ('Task-5')
THEN SUM(Hours)
ELSE 0
END AS 'ReviewHours'
FROM
#Task
WHERE
Taskname IN ('Task-1', 'Task-2', 'Task-5')
GROUP BY
empid) AS t ON E.id = t.empid
ORDER BY
Name
So if I add the Taskname in the Group by it provides multiple rows for each. I need one row for each employee. Need help please.
Name PrepHours ReviewHours
-------------------------------
AAA 2 0
AAA 4 0
AAA 0 3
BBB 0 8
CCC 12 0
CCC 3 0

You can create your totals using a conditional case espression in a cross apply
select e.name, t.*
from #emp e
cross apply (
select
Sum(case when taskname in ('task-1','task-2') then hours else 0 end) PrepHours,
Sum(case when taskname ='Task-5' then hours else 0 end) ReviewHours
from #task t
where t.EmpId=e.Id
)t

You can make the original query work by moving the Sum outside the case statement:
SELECT [Name],
PrepHours,
ReviewHours
FROM #Emp AS E
JOIN (SELECT empid,
Sum( CASE
WHEN Taskname IN ( 'Task-1', 'Task-2' ) THEN [Hours]
ELSE 0
END) AS 'PrepHours',
sum(CASE
WHEN Taskname IN ( 'Task-5' ) THEN [Hours]
ELSE 0
END) AS 'ReviewHours'
FROM #Task
WHERE Taskname IN ( 'Task-1', 'Task-2', 'Task-5' )
GROUP BY empid) AS t
ON E.id = t.empid
ORDER BY Name

Related

Count and GROUP values from a CASE expression

Trying to count the total number of wins and total times a horse finished in the top 6 and GROUP by the order of favouritism in a horse racing market.
I am referencing this answer since it is looking for a similar outcome Count the occurrences of DISTINCT values
I have tried this expression but when executed, it returns the same number in both case columns
ie;
SPFav WinsByFavouritism PlaceByFavouritism
1 4143 4143
2 3963 3963
3 3853 3853
This is the code I am running - what is causing this?
SELECT SPFav,
COUNT(CASE WHEN FinishingPosition = 1 THEN 1 ELSE 0 END) as WinsByFavouritism,
COUNT(CASE WHEN FinishingPosition <= 6 THEN 1 ELSE 0 END) as PlaceByFavouritism
FROM [NRaceHistory].[dbo].[EachWayBetting]
GROUP BY SPFav
ORDER BY SPFav ;
Working with the first comment this would give the following possible solution.
Sample data
create table EachWayBetting
(
SPFav int,
FinishingPosition int
);
insert into EachWayBetting (SPFav, FinishingPosition) values
(1, 1),
(1, 2),
(1, 2),
(1, 9),
(2, 7),
(2, 8),
(2, 2),
(2, 1);
Solution
SELECT SPFav,
COUNT(CASE WHEN FinishingPosition = 1 THEN 1 END) as WinsByFavouritism,
COUNT(CASE WHEN FinishingPosition <= 6 THEN 1 END) as PlaceByFavouritism
FROM EachWayBetting
GROUP BY SPFav
ORDER BY SPFav
Result
SPFav WinsByFavouritism PlaceByFavouritism
----- ----------------- ------------------
1 1 3
2 1 2
Fiddle
I believe you should replace COUNT in your query with SUM. i.e. it should be:
SELECT SPFav,
SUM(CASE WHEN FinishingPosition = 1 THEN 1 ELSE 0 END) as WinsByFavouritism,
SUM(CASE WHEN FinishingPosition <= 6 THEN 1 ELSE 0 END) as PlaceByFavouritism
FROM [NRaceHistory].[dbo].[EachWayBetting]
GROUP BY SPFav
ORDER BY SPFav;

Issue with Row_Number() Over Partition

I've been trying to reset the row_number when the value changes on Column Value and I have no idea on how should i do this.
This is my SQL snippet:
WITH Sch(SubjectID, VisitID, Scheduled,Actual,UserId,RLev,SubjectTransactionID,SubjectTransactionTypeID,TransactionDateUTC,MissedVisit,FieldId,Value) as
(
select
svs.*,
CASE WHEN stdp.FieldID = 'FrequencyRegime' and svs.SubjectTransactionTypeID in (2,3) THEN
stdp.FieldID
WHEN stdp.FieldID is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.FieldID is NULL
THEN 'FrequencyRegime'
ELSE stdp.FieldID
END AS [FieldID],
CASE WHEN stdp.Value is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.Value IS NULL THEN
(SELECT TOP 1 stdp.Value from SubjectTransaction st
JOIN SubjectTransactionDataPoint STDP on stdp.SubjectTransactionID = st.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
where st.SubjectID = svs.SubjectID
order by st.ServerDateST desc)
ELSE stdp.Value END AS [Value]
from SubjectVisitSchedule svs
left join SubjectTransactionDataPoint stdp on svs.SubjectTransactionID = stdp.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
)
select
Sch.*,
CASE WHEN sch.Value is not NULL THEN
ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID)
ELSE NULL
END as [FrequencyCounter],
CASE WHEN Sch.Value = 1 THEN 1--v.Quantity
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) <> 0
THEN 0
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) = 0
THEN 1
ELSE NULL
END AS [DispenseQuantity]
from Sch
--left join VisitDrugAssignment v on v.VisitID = Sch.VisitID
where SubjectID = '4E80718E-D0D8-4250-B5CF-02B7A259CAC4'
order by SubjectID, VisitID
This is my Dataset:
Based on the Dataset, I am trying to reset the FrequencyCounter to 1 every time the value changes for each subject, Right now it does 50% of what I want, It is counting when the value 1 or 2 is found, but when value 1 comes again after value 2 it continues the count from where it left. I want every time the value is changes the count to also start from the beginning.
It's difficult to reproduce and test without sample data, but if you want to know how to number rows based on change in column value, next approach may help. It's probably not the best one, but at least will give you a good start. Of course, I hope I understand your question correctly.
Data:
CREATE TABLE #Data (
[Id] int,
[Subject] varchar(3),
[Value] int
)
INSERT INTO #Data
([Id], [Subject], [Value])
VALUES
(1, '801', 1),
(2, '801', 2),
(3, '801', 2),
(4, '801', 2),
(5, '801', 1),
(6, '801', 2),
(7, '801', 2),
(8, '801', 2)
Statement:
;WITH ChangesCTE AS (
SELECT
*,
CASE
WHEN LAG([Value]) OVER (PARTITION BY [Subject] ORDER BY [Id]) <> [Value] THEN 1
ELSE 0
END AS [Change]
FROM #Data
), GroupsCTE AS (
SELECT
*,
SUM([Change]) OVER (PARTITION BY [Subject] ORDER BY [Id]) AS [GroupID]
FROM ChangesCTE
)
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY [GroupID] ORDER BY [Id]) AS Rn
FROM GroupsCTE
Result:
--------------------------------------
Id Subject Value Change GroupID Rn
--------------------------------------
1 801 1 0 0 1
2 801 2 1 1 1
3 801 2 0 1 2
4 801 2 0 1 3
5 801 1 1 2 1
6 801 2 1 3 1
7 801 2 0 3 2
8 801 2 0 3 3
As per my understanding, you need DENSE_RANK as you are looking for the row number will only change when value changed. The syntax will be as below-
WITH your_table(your_column)
AS
(
SELECT 2 UNION ALL
SELECT 10 UNION ALL
SELECT 2 UNION ALL
SELECT 11
)
SELECT *,DENSE_RANK() OVER (ORDER BY your_column)
FROM your_table

How to achieve this output?

I have a table with data like this :
create table test (transferID int, customerNumber varchar(10), txnstatus int);
insert into test
values
(1, 1001, 1),
(2, 1001, 2),
(3, 1001, 1),
(4, 1002, 2),
(5, 1002, 1),
(6, 1002, 2),
(7, 1002, 1),
(8, 1002, 1),
(9, 1003, 2),
(10, 1003, 1),
(11, 1003, 1),
(12, 1003, 1),
(13, 1003, 1),
(14, ' ', 1),
(15, ' ', 2),
(16, NULL, 2);
and the excepted output is to display the fields with customer number, total number of txns for each customer, successfulTxns, failedTxns. Note that:
txnStatus 1 and 2 represent "success" and "fail" respectively.
customer number may be empty or NULL in some cases like last three rows
This is how I tried, but didn't get the excepted result
select customerNumber,
count(*) over (partition by 1) as TotalTxns,
case when txnstatus = 1 then count(txnstatus) else 0 end as successFulTrxn,
case when txnstatus = 2 then count(txnstatus) else 0 end as failedFulTrxn
from test
group by customerNumber, txnstatus
I expect the output to be:
CustNumber TotalTxns SuccessFulTxns FailedTxns
1001 3 2 1
1002 5 3 2
1003 5 4 1
2 1 1
NULL 1 0 1
Your logic is somewhat correct, you just need to put the CASE expression inside COUNT, not the other way round:
SELECT customerNumber
, COUNT(*) AS TotalTxns
, COUNT(CASE WHEN txnstatus = 1 THEN 1 END) AS SuccessFulTxns
, COUNT(CASE WHEN txnstatus = 2 THEN 1 END) AS FailedTxns
FROM test
GROUP BY customerNumber
Note that there is not such thing as empty INT. Empty strings/whitespace becomes 0 when converted to INT.
Instead of blank I inserted 0 as customerNumber is int. If you want the same order as expected result, If you need 0 and NULL at the end, you can use conditional orderby.
SELECT customerNumber
, COUNT(*) AS TotalTxns
, COUNT(CASE WHEN txnstatus = 1 THEN 1 END) AS successFulTrxn
, COUNT(CASE WHEN txnstatus = 2 THEN 1 END) AS failedFulTrxn
FROM test
GROUP BY customerNumber
ORDER BY CASE WHEN customerNumber IS NULL THEN 100000
WHEN customerNumber = 0 THEN 99999
ELSE customerNumber END
Conditional aggregation is the way to achieve this :
select customerNumber, count(transferID) as TotalTxns,
sum(case when txnstatus = 1 then 1 else 0 end) as successFulTrxn,
sum(case when txnstatus = 2 then 1 else 0 end) as failedFulTrxn
from test t
group by customerNumber;
If you have to do a grouping action on txnstatus, you obviously can't include that field in the grouping list.
Furthermore, you can't do the count inside the case, you have to do it outside, and you don't have to count the status, because if the status is '2', the result will be clearly uncorrect.
I would approach the problem as below.
select customerNumber, count(*) as TotalTxns,
sum(case when txnstatus = 1 then 1 else 0 end) as successFulTrxn,
sum(case when txnstatus = 2 then 1 else 0 end) as failedFulTrxn
from test
group by customerNumber
It gives to me the results you are looking for.

SQL How do I transpose and group data into static columns? [duplicate]

This question already has answers here:
TSQL Pivot without aggregate function
(9 answers)
Closed 4 years ago.
I have a table with the following data:
UID LAST FIRST FUND AMOUNT STATUS
1 Smith John C 100 1
1 Smith John B 250 1
1 Smith John E 150 1
2 Jones Meg B 275 1
2 Jones Meg F 150 1
3 Carter Bill A 100 1
I would like to transpose the FUND, AMOUNT and STATUS values for each UID into a single row for each UID. The resulting table would have columns added for FUND_1, AMT_1, STATUS_1, FUND_2, AMT_2, STATUS_2, FUND_3, AMT_3, STATUS_3. Each UID may or may not have a total of 3 funds. If they do not, the remaining fund, amt, and status columns are left blank. The resulting table would appear as:
UID LAST FIRST FUND_1 AMT_1 STATUS_1 FUND_2 AMT_2 STATUS_2 FUND_3 AMT_3 STATUS_3
1 Smith John C 100 1 B 250 1 E 150 1
2 Jones Meg B 275 1 F 150 1
3 Carter Bill A 100 1
For clarification, this is how the data would move from the existing table to the resulting table for UID 1:
It seems I am unable to use PIVOT because FUND_1, FUND_2, FUND_3 will be different fund categories for each person. The question, TSQL Pivot without aggregate function helps but doesn't answer my question since I have multiple rows in what would be the the DBColumnName in that question.
This is a pretty common conditional aggregation. Notice how I posted consumable data as a table and insert statements. To be honest it took longer to do that portion than the actual code to select the data. You should do this in the future. Also you should avoid using keywords as column names.
declare #Something table
(
UID int
, LAST varchar(10)
, FIRST varchar(10)
, FUND char(1)
, AMOUNT int
, STATUS int
)
insert #Something values
(1, 'Smith', 'John', 'C', 100, 1)
, (1, 'Smith', 'John', 'B', 250, 1)
, (1, 'Smith', 'John', 'E', 150, 1)
, (2, 'Jones', 'Meg', 'B', 275, 1)
, (2, 'Jones', 'Meg', 'F', 150, 1)
, (3, 'Carter', 'Bill', 'A', 100, 1)
;
with SortedValues as
(
select *
, RowNum = ROW_NUMBER() over(partition by UID order by (select null))
from #Something
)
select UID
, Last
, First
, Fund_1 = max(case when RowNum = 1 then Fund end)
, Amt_1 = max(case when RowNum = 1 then Amount end)
, Status_1 = max(case when RowNum = 1 then Status end)
, Fund_2 = max(case when RowNum = 2 then Fund end)
, Amt_2 = max(case when RowNum = 2 then Amount end)
, Status_2 = max(case when RowNum = 2 then Status end)
, Fund_3 = max(case when RowNum = 3 then Fund end)
, Amt_3 = max(case when RowNum = 3 then Amount end)
, Status_3 = max(case when RowNum = 3 then Status end)
from SortedValues
group by UID
, Last
, First
order by UID
, Last
, First

Ms Sql: query with multiple row criteria

I have a table:
CREATE TABLE Cl
(
PropId int,
ClId int
);
INSERT INTO Cl
(PropId, ClId)
VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 2),
(3, 1),
(4, 1),
(4, 2),
(5, 1),
(5, 2);
PropId ClId
1 1
1 2
1 3
2 1
2 2
3 1
4 1
4 2
5 1
5 2
I would like to build a query that returns:
PropId
2
4
5
when in my "WHERE" condition I have only PropId=2. The table join must be through ClId values. Thanks in advance. sqlfiddle
You can use group by and having. Here is one method:
select propid
from c1
group by propid
having sum(case when clid = 1 then 1 else 0 end) > 0 and
sum(case when clid = 2 then 1 else 0 end) > 0 and
count(distinct clid) = 2;
As to the comment to the question:
(...) I have only 2 in my PropId and first of all I have to go to the CliId
values and according to them (values and their segmentations) achive
my PropId output. (...)
If by segmentation you mean a set of records that count is equal or greater than 1, you need to join data this way:
SELECT C.PropId
FROM Cl C INNER JOIN (
SELECT B.ClId
FROM Cl B
WHERE B.PropId = 2
) D ON D.Clid = C.Clid
GROUP BY C.PropID
HAVING COUNT(c.ClId )>1
Note: The result will be: {1, 2, 4, 5}. Why?
Because a D subquery returns set of ClId: {1, 2} and there's 4 PropId records which meet criteria.