Related
I've seen this question, this and this, however these are not what I want.
Please, do not close my question as it is not duplicate. It is really important to me.
I've managed to pivot a table, but this is not desired result:
The desired result looks like this:
My sample data is(it is just an example as OperatorX and OC columns should be 50 times):
DECLARE #OperatorPrice TABLE (ID INT NOT NULL, OperatorId INT NULL, Price
NUMERIC(18,3) NULL, FName VARCHAR(50) NULL)
INSERT INTO #OperatorPrice (
ID, OperatorId, Price, FName
)
VALUES
(226, 996, 22954,'Operator1')
, (266, 1016, 79011.2, 'Operator3')
, (112, 1029, 14869, 'Operator4')
, (93, 1031, 10568.96, 'Operator5')
DECLARE #TR TABLE
(
ID INT NULL ,
Operator1 DECIMAL(18,3) NULL, OC1 DECIMAL(18,3) NULL, Operator2 DECIMAL(18,3) NULL,
OC2 DECIMAL(18,3) NULL, Operator3 DECIMAL(18,3) NULL, OC3 DECIMAL(18,3) NULL,
Operator4 DECIMAL(18,3) NULL, OC4 DECIMAL(18,3) NULL, Operator5 DECIMAL(18,3) NULL,
OC5 DECIMAL(18,3) NULL
)
Example code:
INSERT #TR
(ID ,
Operator1, OC1, Operator2, OC2, Operator3, OC3, Operator4, OC4,
Operator5, OC5)
SELECT ID ,
Operator1, OC1, Operator2, OC2, Operator3, OC3, Operator4, OC4,
Operator5, OC5
FROM
(SELECT Price, id, FName
FROM #OperatorPrice) AS SourceTable
PIVOT
(
sum(Price)
FOR FName IN (Operator1, OC1, Operator2, OC2, Operator3, OC3,
Operator4, OC4, Operator5, OC5)
) AS PivotTable
SELECT * FROM #TR
How can I insert data into OC columns?
Perhaps something like this.
In your alias SOURCETABLE, we just add a UNION ALL of possible combinations for an ID with NULL values. In this case the MIN(ID) and values 1 - 50
Just be sure to
1) Define #TR with columns Operator1,OC1,..,Operator50,OC50 <<< OC# can be an INT
2) in the FOR Item IN(Operator1,OC1,..,Operator50,OC50)
Example -- Edit Corrected to allow for >9 operators
INSERT #TR
SELECT *
FROM (
Select A.ID
,B.*
From #OperatorPrice A
Cross Apply ( values (FName,Price)
,('OC'+replace(FName,'Operator',''),OperatorID)
) B (Item,Value)
Union All
Select ID=(select min(ID) From #OperatorPrice)
,B.*
From ( Select Top 50 N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) A
Cross Apply ( values (concat('Operator',N),NULL)
,(concat('OC',N),NULL)
) B (Item,Value)
) AS SourceTable
PIVOT ( sum(Value) FOR Item IN (Operator1, OC1, Operator2, OC2, Operator3, OC3, Operator4, OC4, Operator5, OC5) ) AS PivotTable
Select * from #TR
Returns -- Notice Operator2
Using your sample tables and data this is pretty easy with conditional aggregation. Not really clear though how you determine which operator number is which. Hopefully you have something better than parsing numbers out of the values but who knows.
select op.ID
, Operator1 = max(case when convert(int, replace(FName, 'Operator', '')) = 1 then Price end)
, OC1 = max(case when convert(int, replace(FName, 'Operator', '')) = 1 then OperatorID end)
, Operator2 = max(case when convert(int, replace(FName, 'Operator', '')) = 2 then Price end)
, OC2 = max(case when convert(int, replace(FName, 'Operator', '')) = 2 then OperatorID end)
, Operator3 = max(case when convert(int, replace(FName, 'Operator', '')) = 3 then Price end)
, OC3 = max(case when convert(int, replace(FName, 'Operator', '')) = 3 then OperatorID end)
, Operator4 = max(case when convert(int, replace(FName, 'Operator', '')) = 4 then Price end)
, OC4 = max(case when convert(int, replace(FName, 'Operator', '')) = 4 then OperatorID end)
, Operator5 = max(case when convert(int, replace(FName, 'Operator', '')) = 5 then Price end)
, OC5 = max(case when convert(int, replace(FName, 'Operator', '')) = 5 then OperatorID end)
from #OperatorPrice op
cross apply
(
values
(1)
,(2)
,(3)
,(4)
)x(N)
group by op.ID
I am looking to find a solution to this problem. I have a table called LogEntry that stores information used by multiple offices, where they have to log any visitors that come in to their office on any given day. If no visitors come in, they are still required to log "No Visitors" for the day. How do I run a query that pulls all dates where an office failed to create even a "No Visitors" log?
I've looked at this question (and the article linked within), but even adapting that query, I'm only able to create a blank row for a date where an office is missing an entry for a date, not specify the actual office that did not create an entry. Is there a way to do what I'm trying to do?
declare #temp table (
CDate datetime,
loc_id varchar(50)
)
insert into #temp SELECT DISTINCT entryDate, locationID FROM LogEntry WHERE entryDate >= '05/01/2017' AND entryDate <= '07-31-2017'
;with d(date) as (
select cast('05/01/2017' as datetime)
union all
select date+1
from d
where date < '07/31/2017'
)
select DISTINCT t.loc_id, CONVERT(date, d.date)
FROM d LEFT OUTER JOIN #temp t ON d.date = t.CDate
GROUP BY t.loc_id, d.date
ORDER BY t.loc_id
As I said, this query returns me a list of dates in the date range, and all locations that submitted entries on that date, but I'd like to find a way to extract essentially the opposite information: if an office (specified by locationID) did not submit an entry on a given day, return only those locationIDs and the dates that they missed.
Sample data
EntryID | locationID | entryDate
=================================
1 1 07-01-2017
2 1 07-02-2017
3 2 07-02-2017
4 1 07-04-2017
Expected Result (for date range of 07-01 to 07-04)
locationID | missedEntryDate
============================
1 07-03-2017
2 07-01-2017
2 07-03-2017
2 07-04-2017
Your first step was good, you create a list of all dates, but you also need a list of all locations. Then you create a cross join to have all combinations and then you perform the left join to find out what is missing.
;with allDates(date) as (
select cast('05/01/2017' as datetime)
union all
select date+1
from d
where date < '07/31/2017'
), allLocations as (
SELECT DISTINCT loc_id
FROM #temp
), allCombinations as (
SELECT date, loc_id
FROM allDates
CROSS JOIN allLocations
)
SELECT AC.loc_id, AC.date
FROM allCombinations AC
LEFT JOIN #temp t
ON AC.date = t.CDate
AND AC.loc_id = t.loc_id
WHERE t.loc_id IS NULL -- didnt find a match on #temp
If your dataset is not too large you can try this:
select t.loc_id, CONVERT(date, d.date)
FROM d
-- Cross join dates to all available locs
CROSS JOIN (SELECT DISTINCT loc_id FROM #temp ) AS Locs
LEFT JOIN
( SELECT loc_id, t.CDate
FROM #temp
GROUP BY loc_id, d.date ) AS t ON d.date = t.CDate AND Locs.loc_id = t.loc_id
ORDER BY Locs.loc_id
This should be a bit faster:
;WITH cte AS (
SELECT a.LocID, RangeStart.CDate, ( CASE WHEN Input.LocID IS NULL THEN 1 ELSE 0 END ) AS IsMissing
FROM ( SELECT DISTINCT LocID FROM #temp ) AS a
CROSS JOIN ( SELECT CONVERT( DATETIME, '2017-05-01' ) AS CDate ) AS RangeStart
LEFT JOIN
( SELECT LocID, MIN( CDate ) AS CDate
FROM #temp
WHERE CDate = '2017-05-01'
GROUP BY LocID ) AS Input ON a.LocID = Input.LocID AND RangeStart.CDate = Input.CDate
UNION ALL
SELECT a.LocID, a.CDate + 1 AS CDate,
ISNULL( ItExists, 0 ) AS IsMissing
FROM cte AS a
OUTER APPLY( SELECT LocID, 1 AS ItExists FROM #temp AS b WHERE a.LocID = b.LocID AND a.CDate + 1 = b.CDate ) AS c
WHERE a.CDate < '2017-07-01'
)
SELECT * FROM cte OPTION( MAXRECURSION 0 )
You can also add an index:
CREATE INDEX IX_tmp_LocID_CDate ON #temp( LocID, CDate )
Sample data set for the second query:
CREATE TABLE #temp( LocID VARCHAR( 50 ), CDate DATETIME )
INSERT INTO #temp
VALUES
( '1', '2017-05-01' ), ( '1', '2017-05-02' ), ( '1', '2017-05-03' ), ( '1', '2017-05-04' ), ( '1', '2017-05-05' ),
( '2', '2017-05-01' ), ( '2', '2017-05-02' ), ( '2', '2017-05-03' ), ( '2', '2017-05-04' ), ( '2', '2017-05-05' )
;WITH d AS (
SELECT CAST( '05/01/2017' AS DATETIME ) AS date
UNION ALL
SELECT date + 2
FROM d
WHERE date < '2018-07-31'
)
INSERT INTO #temp
SELECT LocID, d.date
FROM ( SELECT DISTINCT LocID FROM #temp ) AS a
CROSS JOIN d
OPTION( MAXRECURSION 0 )
I have a problem, I'm trying to join the same table and make column of it. For example the table contains id, name, type.
And the data can be:
id name type Date
--------------------------------------------------
1 KKKK BP 05/05/2017
2 MMMM KS 07/10/2016
3 LLL TL 04/05/2017
4 NNN BP 06/01/2016
I want to make a table with the following design:
- id name BP KS TL
-------------------------------------------------------------
1 KKK 05/05/2017
2 MMM 07/10/2016
3 LLL 04/05/2017
4 NNN 06/01/2017
I tried Pivot table and didn't work.
Any idea?
You can use the PIVOT function in SQL Server 2008 to convert your rows of data into columns:
select
id,
name,
BP, KS, TL
from
(
select id,
name,
type,
[date]
from mytable
) d
pivot
(
max([date])
for type in (BP, KS, TL)
) piv;
This could also be written using conditional logic, like a CASE expression with some aggregation:
select id,
name,
BP = max(case when type = 'BP' then [date] end),
KS = max(case when type = 'KS' then [date] end),
TL = max(case when type = 'TL' then [date] end)
from mytable
group by id, name;
Based on your comment that you could have multiple dates for each name and type combination, you can still use similar queries you'll just have to use a windowing function like row_number to get the final result you want.
If you want to use the conditional logic version, you'd change the query to be:
select
name,
BP1 = max(case when type = 'BP' and rn =1 then [date] end),
BP2 = max(case when type = 'BP' and rn =2 then [date] end),
BP3 = max(case when type = 'BP' and rn =3 then [date] end),
KS1 = max(case when type = 'KS' and rn =1 then [date] end),
KS2 = max(case when type = 'KS' and rn =2 then [date] end),
TL1 = max(case when type = 'TL' and rn =1 then [date] end)
from
(
select
name,
[type],
[date],
rn = row_number() over(partition by name, [type] order by [date] desc)
from mytable
) d
group by name;
The PIVOT version would be:
select
name,
BP1, BP2, BP3, KS1, KS2, TL1
from
(
select
name,
type = type + cast(rn as varchar(2)),
[date]
from
(
select
name,
type,
[date],
rn = row_number() over(partition by name, [type] order by [date] desc)
from mytable
)s
) d
pivot
(
max([date])
for type in (BP1, BP2, BP3, KS1, KS2, TL1)
) piv;
As you can see there is a lot of typing to get all of these columns, so you could use dynamic SQL to get the final result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Type + cast(rn as varchar(2)))
from
(
select type,
rn = row_number() over(partition by name, type order by date desc)
from mytable
) d
group by type, rn
order by type, rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT name, ' + #cols + N'
from
(
select
name,
type = type + cast(rn as varchar(2)),
[date]
from
(
select
name,
type,
[date],
rn = row_number() over(partition by name, [type] order by [date] desc)
from mytable
)s
) x
pivot
(
max(date)
for type in (' + #cols + N')
) p '
exec sp_executesql #query;
I created a demo to show that they all return the same result.
SELECT SUM(Marks) AS tot
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
the output of the above query i want to pass to the another query stating that max of the "Marks"
select MAX(?) from [Tbl_Results]
? how to pass an argument to get an max of marks from the table
There are a number of ways to do this.
You can create a cte from your first query, like this:
;WITH cte AS
(
SELECT SUM(Marks) AS tot
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
)
SELECT MAX(tot)
FROM cte
Or you could create a view from it:
CREATE VIEW vwTot
AS
SELECT SUM(Marks) AS tot
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
GO
SELECT MAX(tot)
FROM vwTot
You can also insert the results into a temporary table:
SELECT SUM(Marks) AS tot INTO #Temp
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
SELECT MAX(tot)
FROM #Temp
Or use it as a derived table:
SELECT MAX(tot)
FROM (
SELECT SUM(Marks) AS tot
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
) dt
You can go with an older style and select your first set of results into a temp table. This can be helpful if you need to perform other operations on that intermediate set of results. For example:
SELECT SUM(Marks) AS tot
INTO #results
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
SELECT MAX(tot)
FROM #results
DROP TABLE #results
Or, you can go with a more up-to-date style and use a Common Table Expression to achieve your result in a single query:
;WITH cte AS (
SELECT SUM(Marks) AS tot
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname)
SELECT MAX(tot)
FROM cte;
you can use a subquery. since you're using a groupby clause you also need to add sname to your select statement.
select MAX(tot) from
(
SELECT sname, SUM(Marks) AS tot
FROM [Tbl_Results]
WHERE [month] = 'Mar'
AND [year] = '2016'
GROUP BY sname
) b
I have a following table:
State LAB GROUP DATE CODE ID
UP A I 1-Jan 1 345
UP R S 1-Feb 1 456
UP A S 1-Jan 2 567
DL R S 1-Feb 3 678
DL T S 1-Jan 1 789
DL A S 1-Jan 2 900
MN T S 1-Jan 3 1011
MN R I 1-Feb 1 1122
MN S I 1-Feb 2 1233
I need a pivot table of following type:
STATE A R T TOTAL
UP 2 1 0 3
DL 1 1 1 3
MN 0 1 1 2
DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE.
I then need the pivot tables filtered for following columns:
GROUP
DATE
CODE
So 1st table will have the pivot table above counting only those records which have GROUP=S
2nd table will have the pivot table above counting only those records which have CODE=1
and so on, I wish to put multiple conditions. and generate several tables one by one and export them.
If this is possible in SQL please let me know! I ruled out excel vba due to the size of table (source table will have 800,000 records approx).
Try this :-
Select [State],[A],[R],[T],Total = [A] + [R]+ [T]
from
(
Select [State],
[A] = Sum(Case when LAB='A' then 1 else 0 END) ,
[R] = Sum(Case when LAB='R' then 1 else 0 END) ,
[T] = Sum(Case when LAB='T' then 1 else 0 END)
from YourTable
group by [State]
)a
SQL FIDDLE
CREATE TABLE #t(States VARCHAR(10),LAB VARCHAR(5),GROUPs VARCHAR(5),DATEs VARCHAR(10),CODE INT,ID INT)
INSERT INTO #t values('UP','A','I','1-Jan',1,345)
INSERT INTO #t values('UP','R','S','1-Feb',1,456)
INSERT INTO #t values('UP','A','S','1-Jan',2,567)
INSERT INTO #t values('DL','R','S','1-Feb',3,678)
INSERT INTO #t values('DL','T','S','1-Jan',1,789)
INSERT INTO #t values('DL','A','S','1-Jan',2,900)
INSERT INTO #t values('MN','T','S','1-Jan',3,1011)
INSERT INTO #t values('MN','R','I','1-Feb',1,1122)
INSERT INTO #t values('MN','S','I','1-Feb',2,1233)
SELECT States,ISNULL(A,0) A,ISNULL(R,0) R,ISNULL(T,0) T,ISNULL(A,0)+ISNULL(R,0)+ISNULL(T,0) total
FROM
(
SELECT States,LAB,Count(ID) AS cnt FROM #t GROUP BY States,LAB /*apply GROUP DATE CODE condition here*/
) AS PVT
PIVOT(MAX(cnt) FOR LAB IN (A,R,T)) pvt
Another solution using PIVOT :
WITH PivotInUse AS (
SELECT state,lab,COUNT(*) AS cnt
FROM YourTable
GROUP BY state,lab
)
SELECT STATE
,COALESCE([A], 0) AS A
,COALESCE([R], 0) AS R
,COALESCE([T], 0) AS T
,COALESCE([A], 0) + COALESCE([R], 0) + COALESCE([T], 0) AS TOTAL
FROM PivotInUse
PIVOT(SUM(cnt) FOR lab IN ([A],[R],[T])) AS p;
Your sample table
SELECT * INTO #TEMP FROM
(
SELECT 'UP' [State],'A' LAB,'I' [GROUP],'1-Jan' [DATE],1 CODE,345 ID
UNION ALL
SELECT 'UP','R','S','1-Feb',1,456
UNION ALL
SELECT 'UP','A','S','1-Jan',2,567
UNION ALL
SELECT 'DL','R','S','1-Feb',3,678
UNION ALL
SELECT 'DL','T','S','1-Jan',1,789
UNION ALL
SELECT 'DL','A','S','1-Jan',2,900
UNION ALL
SELECT 'MN','T','S','1-Jan',3,1011
UNION ALL
SELECT 'MN','R','I','1-Feb',1,1122
UNION ALL
SELECT 'MN','S','I','1-Feb',2,1233
)TAB
Now you need to get the distinct count of each state and get the sum as the result to show Total
in pivoted result.
SELECT DISTINCT [State],LAB,SUM(CNT) CNT
INTO #NEWTABLE
FROM
(
SELECT DISTINCT
[State],LAB,
CASE WHEN [State] IS NULL THEN NULL ELSE COUNT([State]) OVER(PARTITION BY [State],LAB) END CNT
FROM #TEMP
)TAB
GROUP BY [State],LAB
WITH ROLLUP
Now we need to get the distinct columns for pivot(#cols) and columns to identify and replace null with zero in pivot(#NullToZeroCols)
DECLARE #cols NVARCHAR (MAX)
DECLARE #NullToZeroCols NVARCHAR (MAX)
SET #cols = SUBSTRING((SELECT DISTINCT ',['+LAB+']' FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
SET #NullToZeroCols = SUBSTRING((SELECT DISTINCT ',ISNULL(['+LAB+'],0) AS ['+LAB+']'
FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
Join the pivotted query with the #NEWTABLE to get the Total for each State
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT P.State,' + #NullToZeroCols + ',T2.CNT TOTAL FROM
(
SELECT DISTINCT [State],LAB,CNT FROM #NEWTABLE
) x
PIVOT
(
SUM(CNT)
FOR [LAB] IN (' + #cols + ')
) p
JOIN #NEWTABLE T2 ON P.[STATE]=T2.[STATE]
WHERE P.State IS NOT NULL AND T2.LAB IS NULL AND T2.[STATE] IS NOT NULL;'
EXEC SP_EXECUTESQL #query
Here is your result
Here is the SQLFiddle http://sqlfiddle.com/#!3/c2588/1 (If it shows any error while loading the page, just click RUNSQL, it will work)
Now if you want to get the result as you said DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE, just change
OVER(PARTITION BY [State],LAB)
to
OVER(PARTITION BY [State],LAB,Id)
which will show the following result after executing the pivot query