This is the table "tbltask",
task Ship_Operator Pick_Operator Pack_Operator
1 john kevin steve
2 kevin kevin john
3 steve john john
4 steve steve steve
5 john steve john
Now I need to get the total amount for everyone: ship, pick, pack
name ship Total pick Total pack Total
john 2 1 3
kevin 1 2 0
steve 2 2 2
I can use three statement to get the result:
select [Ship_Operator] ,count(*) as task_total from tbltask
where [Ship_Operator] in ('john','kevin','steve')
group by [Ship_Operator]
select [Pick_Operator] ,count(*) as task_total from tbltask
where [Pick_Operator] in ('john','kevin','steve')
group by [Pick_Operator]
select [Pack_Operator] ,count(*) as task_total from tbltask
where [Pack_Operator] in ('john','kevin','steve')
group by [Pack_Operator]
Is it possible to use one SQL statement?
Your help will be appreciated!
unpivot and pivot back:
with t(task,Ship_Operator,Pick_Operator,Pack_Operator) as (
select '1','john','kevin','steve' union all
select '2','kevin','kevin','john' union all
select '3','steve','john','john' union all
select '4','steve','steve','steve' union all
select '5','john','steve','john')
-------Test data set up ends here------------
select
name, Ship_Operator ship_total, Pick_Operator pick_total, Pack_Operator pack_total
from t unpivot (
name for operation in (Ship_Operator, Pick_operator, Pack_Operator)
) as x pivot (
count(task) for operation in ([Ship_Operator],[Pick_Operator],[Pack_Operator])
) as x;
Produces:
Another way is to use UNPIVOT only and then conditionally aggregate:
with t(task,Ship_Operator,Pick_Operator,Pack_Operator) as (
select '1','john','kevin','steve' union all
select '2','kevin','kevin','john' union all
select '3','steve','john','john' union all
select '4','steve','steve','steve' union all
select '5','john','steve','john')
-------Test data set up ends here------------
select
name,
count(case when operation = 'Ship_Operator' then 1 end) ship,
count(case when operation = 'Pick_Operator' then 1 end) pick,
count(case when operation = 'Pack_Operator' then 1 end) pack
from t unpivot (
name for operation in (Ship_Operator, Pick_operator, Pack_Operator)
) as x
group by name;
Produces:
Use UNION to combine all name and add a new column to just identify the different values.
Query
SELECT t.[Name],
SUM(CASE t.[Col1] WHEN 'Ship' THEN 1 ELSE 0 END) AS [Ship Total],
SUM(CASE t.[Col1] WHEN 'Pick' THEN 1 ELSE 0 END) AS [Pick Total],
SUM(CASE t.[Col1] WHEN 'Pack' THEN 1 ELSE 0 END) AS [Pack Total]
FROM(
SELECT 'Ship' AS [col1], [Ship_operator] as [Name]
FROM [tbltask]
UNION ALL
SELECT 'Pick', [Pick_operator]
FROM [tbltask]
UNION ALL
SELECT 'Pack' AS [col1], [Pack_operator]
FROM [tbltask]
)t
GROUP BY t.[Name];
And if you want the result particularly for those three names. Then add a WHERE condition in the sub-queries.
Related
I have two tables
ID
ID2
1
1
1
2
2
3
3
4
3
5
And the second one
ID2
Code
Date1
1
A
01/01/2023
2
B
01/02/2023
3
C
01/03/2023
4
A
01/01/2023
5
D
01/15/2023
The second table has more columns that I need to include, but I'm only including two (Code and Date1) for the sake of brevity.
What I need is to unite everything based on the ID of the first table. So it would look something like
ID1
ID2-1
Code-1
Date1-1
ID2-2
Code-2
Date1-2
1
1
A
01/01/2023
2
B
01/02/2023
2
3
C
01/03/2023
NULL
NULL
NULL
3
4
A
01/01/2023
5
D
01/15/2023
In these examples one ID repeats up to two times in the second table, but the second table can have an indefinite amount of records tied to an ID from the first table. Meaning it might be Code-10, or Code-20, or maybe more or less. I need to do this in a pretty big query for a report I'm doing, so these are not the only fields that will be in the final result, but for this data specifically I only use two tables that have a very similar structure to the one I'm describing here. Any help will be appreciated.
To build on T N's code, this puppy builds a "dynamic" 30 column wide pivot.
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'
SELECT
A.ID
[COLUMNS]
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID2) AS RowNum
FROM TableA
) A
JOIN TableB B
ON B.ID2 = A.ID2
GROUP BY A.ID
ORDER BY A.ID'
SELECT #SQL = REPLACE(#SQL, '[COLUMNS]', (
SELECT CONCAT(N'
, MAX(CASE WHEN A.RowNum = ', x.sort, ' THEN B.ID2 END) AS [ID2-', x.sort, N']
, MAX(CASE WHEN A.RowNum = ', x.sort, ' THEN B.Code END) AS [Code-', x.sort, N']
, MAX(CASE WHEN A.RowNum = ', x.sort, ' THEN B.Date END) AS [Date-', x.sort, N']')
FROM (
SELECT TOP 30 row_number() OVER(ORDER BY (SELECT NULL)) AS sort
FROM sys.objects so
) x
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'))
SELECT #SQL
EXEC(#SQL)
You can accomplish this by using a combination of a windowed ROW_NUMBER() number function and conditional aggregation.
First, ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) is used to assign sequences number to each row with the same ID. The results are then be joined with your second table and a GROUP BY ID is applied. Finally, conditional aggregation functions of the form MAX(CASE WHEN ... THEN .. END) can be used to select the proper value for each column, with specific row numbers assigned to specific columns.
Something like:
SELECT
A.ID,
MAX(CASE WHEN A.RowNum = 1 THEN B.ID2 END) AS [ID2-1],
MAX(CASE WHEN A.RowNum = 1 THEN B.Code END) AS [Code-1],
MAX(CASE WHEN A.RowNum = 1 THEN B.Date END) AS [Date-1],
MAX(CASE WHEN A.RowNum = 2 THEN B.ID2 END) AS [ID2-2],
MAX(CASE WHEN A.RowNum = 2 THEN B.Code END) AS [Code-2],
MAX(CASE WHEN A.RowNum = 2 THEN B.Date END) AS [Date-2]
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID2) AS RowNum
FROM TableA
) A
JOIN TableB B
ON B.ID2 = A.ID2
GROUP BY A.ID
ORDER BY A.ID
Results:
ID
ID2-1
Code-1
Date-1
ID2-2
Code-2
Date-2
1
1
A
2023-01-01
2
B
2023-01-02
2
3
C
2023-01-03
null
null
null
3
4
A
2023-01-01
5
D
2023-01-15
See this db<>fiddle.
If you need to support an arbitrary number of records per ID instead of just two, you will then need to dive into the world of dynamic SQL.
I had this Data,
Table One :
EmpID Date Absent
1 01/01/2018 1
1 01/02/2018 1
1 02/05/2018 1
1 03/25/2018 1
1 04/01/2018 0
1 05/02/2018 1
1 06/03/2018 1
Table Two
ID Amount DateEffective
1 5.00 02/06/2018
2 3.00 05/02/2018
3 10.00 06/03/2018
Desired Output
EmpID Month Year Absent Penalty
1 January 2018 2 5.00
1 February 2018 1 5.00
1 March 2018 1 3.00
1 April 2018 0 3.00
1 May 2018 1 13.00
1 June 2018 1 10.00
This is my Code
SELECT { fn MONTHNAME(one.Date) } AS MonthName, YEAR(one.Date) AS Year, SUM(one.Absent) AS Absent,
(
SELECT top 1 two.DailyRate
FROM table_two as two
WHERE EmpID = '1'
AND one.Date <= two.EffectivityDate
)
FROM table_one as one
WHERE EmpID = '1'
GROUP BY { fn MONTHNAME(one.Date) }, MONTH(one.Date), YEAR(one.DTRDate)
ORDER BY Year(one.Date),month(one.Date)
and it shows an error :
Column 'one.Date' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause
please help for this issue...
Thanks
Try this :
SELECT
one.EmpID
,DATENAME(MONTH,one.Date) AS [MonthName]
,YEAR(one.Date) AS [Year]
,SUM(one.Absent) AS [Absent]
,(SELECT top 1 two.Amount
FROM table_two as two
WHERE two.ID = one.EmpID
AND YEAR(two.DateEffective) >= YEAR(one.Date)
AND MONTH(two.DateEffective) >=MONTH(one.Date)
) AS [Penalty]
FROM table_one as one
WHERE
one.EmpID = '1'
GROUP BY one.EmpID,DATENAME(MONTH,one.Date), MONTH(one.Date), YEAR(one.Date)
ORDER BY Year(one.Date),month(one.Date)
From my understanding to do this,
select e.EmpID
,datename(month,e.Date)[month]
,year(e.Date) [year]
,sum(e.Absent) as [Abscount]
,a.Amount
from
empl e left join abs a
on datename(month,e.Date)=DATENAME(month,a.DateEffective)
group by e.EmpID,DATENAME(MONTH,e.Date), MONTH(e.Date), YEAR(e.Date) , a.Amount
order by Abscount desc
Revert me if any clarifications needed...
is this helpful.?
Create Table #TabOne(EmpID int,[Date] Date,[Absent] Bit)
Create Table #TabTwo(ID int,Amount float,DateEffective Date)
Insert into #TabOne
SELECT 1,'01/01/2018',1 Union All
SELECT 1,'01/02/2018',1 Union All
SELECT 1,'02/05/2018',1 Union All
SELECT 1,'03/25/2018',1 Union All
SELECT 1,'04/01/2018',0 Union All
SELECT 1,'05/02/2018',1 Union All
SELECT 1,'06/03/2018',1
Insert into #TabTwo
Select 1,5.00 ,'02/06/2018' Union All
Select 2,3.00 ,'05/02/2018' Union All
Select 3,10.00,'06/03/2018'
;with cte1
As
(
Select One.EmpID,MONTH(one.[Date]) As [mon],YEAR(one.[Date]) As [Year],two.Amount,one.[Absent],
ROW_NUMBER() OVER(partition by One.EmpID,One.[Date] order by DATEDIFF(dd,two.DateEffective,one.[Date]) desc) as rn
from #TabOne one
LEFT JOIN #TabTwo two on one.[Date]<=two.DateEffective
)
Select EmpID,DATENAME(month, DATEADD(month, [mon]-1, CAST('2008-01-01' AS datetime))) As [Month],
[Year],SUM(CASE WHEN [Absent]=0 then 0 ELSE 1 END) As [Absent] ,MAX(Amount) As Penalty
from cte1
where rn=1
Group by EmpID,[Year],[mon]
order by EmpID,[Year],[mon]
Drop Table #TabOne
Drop Table #TabTwo
I have a below dataset and I am trying to get max occurrences of CID per each OID.
IF OBJECT_ID('TEMPDB..#SS',N'U') IS NOT NULL
DROP TABLE #SS
GO
SELECT * INTO #SS FROM (
SELECT 1 AS OID,501 AS CID
UNION ALL
SELECT 1 AS OID,503 AS CID
UNION ALL
SELECT 1 AS OID,502 AS CID
UNION ALL
SELECT 1 AS OID,501 AS CID
UNION ALL
SELECT 1 AS OID,501 AS CID
UNION ALL
SELECT 2 AS OID,502 AS CID
UNION ALL
SELECT 2 AS OID,502 AS CID
UNION ALL
SELECT 2 AS OID,502 AS CID
UNION ALL
SELECT 2 AS OID,501 AS CID
UNION ALL
SELECT 1 AS OID,503 AS CID
)A
GO
In above sample dataset, I need to get 2 CID per each OID which occurred maximum times. The expected result could be:
OID CID
1 501
1 503
2 501
2 502
This cannot be a duplicate to SQL Select top frequent records because I need this sub-queries and SQL-Server would not accept ORDER BY in sub-query and eventually I need a ranking function to solve my issue. Ranking function was not used in the link provided.
You can do this pretty easily utilizing ROW_NUMBER. Thanks for Tab Alleman for the clarification on your requirements.
select *
from
(
select *
, RowNum = ROW_NUMBER() over (partition by OID order by count(*) desc)
from #SS
group by OID
, CID
) x
where x.RowNum <= 2
order by x.OID
, x.CID
I am trying to take rows with the same ID and return them on the same row. My data looks like the follow:
ID Fruit
1 Banana
1 Apple
1 Grapefruit
2 Cherry
2 Blueberry
3 Lime
3 Pear
And I would like it to look like this:
ID Fruit Fruit1 Fruit2
1 Banana Apple Grapefruit
2 Cherry Blueberry NULL
I have tried this as a query, but I don't seem to be having much luck:
SELECT a.[ID],a.[Fruit],b.[Fruit]
FROM [test].[dbo].[Fruit] a
JOIN [test].[dbo].[Fruit] b
ON a.ID = b.ID
WHERE a.FRUIT <> b.FRUIT
Can anybody help with this?
Thanks!
If the fruit count is not fixed, you can you use dynamic script:
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
CREATE TABLE #t(ID INT,Fruit VARCHAR(100))
INSERT INTO #t(ID,Fruit)
SELECT 1,'Banana' UNION
SELECT 1,'Apple' UNION
SELECT 1,'Grapefruit' UNION
SELECT 2,'Cherry' UNION
SELECT 2,'Blueberry' UNION
SELECT 3,'Lime' UNION
SELECT 3,'Pear'
DECLARE #sql NVARCHAR(max),#cols VARCHAR(max)
SELECT #cols=ISNULL(#cols+',','')+t.col FROM (
SELECT *,'Fruit'+LTRIM(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY(SELECT 1) )) AS col FROM #t AS t
) AS t GROUP BY t.col
SET #sql='
SELECT * FROM (
SELECT *,''Fruit''+LTRIM(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY(SELECT 1) )) AS col FROM #t AS t
) AS t PIVOT(MAX(Fruit) FOR col in ('+#cols+')) p
'
PRINT #sql
EXEC(#sql)
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
ID Fruit1 Fruit2 Fruit3
----------- ---------- ---------- ----------
1 Apple Banana Grapefruit
2 Blueberry Cherry NULL
3 Lime Pear NULL
You can use combination of a windowing function like row_number and then some conditional aggregation using a CASE expression with MAX() to get the result that you want:
select
Id,
Fruit = max(case when rn = 1 then Fruit end),
Fruit1 = max(case when rn = 2 then Fruit end),
Fruit2 = max(case when rn = 3 then Fruit end)
from
(
select
Id,
Fruit,
rn = row_number() over(partition by Id order by Id)
from [test].[dbo].[Fruit]
) d
group by Id;
See a Demo. The row_number() function creates a unique number for each id, then using this number along with CASE and MAX you will convert your rows of data into columns.
you can use pivot to do this as below:
Select Id, [0] as Fruit, [1] as [Fruit1], [2] as [Fruit2] from (
Select *, RowN = Row_Number() over (partition by Id order by Fruit) - 1 from yourtable )
pivot ( max(Fruit) for RowN in ([0], [1],[2]) ) p
I have a table that stores budget quantities for a company whose fiscal year begins 1st April and ends on 31st March the next year.
I have this query to extract figures for a particular month.
SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea'
FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0
INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1
ON T0.DocEntry = T1.DocEntry
WHERE T1.U_Month = DATENAME(MONTH, '2015-04-01') AND T0.U_Source = 'NTEL'
There is an existing report that takes two parameters, a Start and End Date. (type datetime)
Table below: The month column is of type nvarchar.
How do I modify the query such when a user enters StartDate and EndDate e.g.
1st May 2015 and 31st July 2015, I will get a quantity result of 12640.
You can use couple of ways to do this.
One way would be to use PARSE. Like this.
SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea'
FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0
INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1
ON T0.DocEntry = T1.DocEntry
WHERE PARSE((T1.U_Month + CONVERT(VARCHAR(4),YEAR(CURRENT_TIMESTAMP))) as datetime) BETWEEN #StartDate AND #EndDate
AND T0.U_Source = 'NTEL'
Another way would be to use a numbers table to map your month name to a month number and use it in your query.
;WITH CTE AS (
SELECT 1 as rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
MonthMap AS
(
SELECT ROW_NUMBER()OVER(ORDER BY rn ASC) as monthnumber FROM CTE
)
SELECT monthnumber,DATENAME(MONTH,DATEFROMPARTS(2016,monthnumber,1)) FROM MonthMap;
and then join it with your month table like this.
;WITH CTE AS (
SELECT 1 as rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
MonthMap AS
(
SELECT ROW_NUMBER()OVER(ORDER BY rn ASC) as monthnumber FROM CTE
)
SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea'
FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0
INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1
ON T0.DocEntry = T1.DocEntry
INNER JOIN MonthMap M ON T1.U_Month = DATENAME(MONTH,DATEFROMPARTS(2016,monthnumber,1))
WHERE M.monthnumber BETWEEN DATEPART(MONTH,#StartDate) AND DATEPART(MONTH,#EndDate)
AND T0.U_Source = 'NTEL';
You should compare both the approaches for performance. PARSE is simpler to use but would be difficult to index properly.
On a Separate note, you should avoid storing dates or date parts as month names as these take up more storage(even more since you are using NVARCHAR), and are difficult to use efficiently.