How do you create a summary column? - sql-server

Here is my current SQL code,
select
coalesce(cast(machinename as varchar(28)), 'Grand Total:') as 'machinename',
(IsNull(cast(CRATE_SMALL / 60 as varchar(24)),'0') + ':' + IsNull(cast(CRATE_SMALL % 60 as varchar(24)),'0') ) as '1001' ,
(IsNull(cast(CRATE_MEDIUM / 60 as varchar(24)),'0') + ':' + IsNull(cast(CRATE_MEDIUM % 60 as varchar(24)),'0'))as '1002',
(IsNull(cast(NO_SCHEDULE / 60 as varchar(24)),'0') + ':' + IsNull(cast(NO_SCHEDULE % 60 as varchar(24)),'0')) as '9999'
from (
select machinename ,
sum(case when vfrm.job_id = '1001' then DateDiff(mi, 0, total_time) end) as CRATE_SMALL ,
sum(case when vfrm.job_id = '1002' then DateDiff(mi, 0, total_time) end) as CRATE_MEDIUM ,
sum(case when vfrm.job_id = '9999' then DateDiff(mi, 0, total_time) end) as NO_SCHEDULE
from ven_fullreportmaster vfrm
INNER JOIN ven_descriptionmaster VDM ON VDM.description_id = vfrm..description_id
inner join ven_machinemaster vm on vm.machine_id = vfrm..machine_id
where vfrm.entry_date = convert(varchar, getdate()-7, 105)
and vfrm.shift_id =1
and vfrm.is_task_completed ='Y'
group by machinename with rollup
) as SubQueryALias
the output :
machinename 1001 1002 9999
ARISTECH 0:0 0:0 10:0
FADAL 0:0 5:0 10:0
Grand Total: 0:0 5:0 20:0
problem:
is there a anyway to show only the column(s) whose total is greater than zero...
So, in the above example I dont want to show the column name '1001'.

In all honesty, you shouldn't. This is a display issue, and as such should be dealt with when you display the data, not retrieve it from the database.
Reports, and datagrids and the like generally have functionality to do exactly this. Perhaps give more info about how you are displaying the data and someone might be able to provide more info.

To add the totals row, you ought to look at the ROLLUP part of the GROUP BY clause. This can produce sub-totals as well as the grand total, depending on what your final requirements are.
For hiding a column, there's not a solution for that in tsql - a SELECT statement always produces result sets with the same shape (names and types of columns).
But both may be better served by a reporting tool, if that's where this data is going (which I expect it is, given the nature of the query). Reporting tools tend to have better post-processing facilities.

try this:
select
entry_date, machinename, [1001], [1002], [9999], [1001]+[1002]+[9999] as Total
FROM ( --your query here
) d
WHERE [1001]+[1002]+[9999]>0

how about use a temp table to store the data query out and then build the output data from the temp table?
just
1.{your select query} into #t {your from where query}
2.select entry_date, machinename, [1001], [1002], [9999] from #t
union select '' as entry_date, 'total', sum([1001]), sum([1002]), sum([9999]) from #t
the logic is more clear with these steps, however, you can also use similar subquery to get the same result

Related

T-SQL - get only latest row for selected condition

I have table with measurement with column SERIAL_NBR, DATE_TIME, VALUE.
There is a lot of data so when I need them to get the last 48 hours for 2000 devices
Select * from MY_TABLE where [TIME]> = DATEADD (hh, -48, #TimeNow)
takes a very long time.
Is there a way not to receive all the rows for each device, but only the latest entry? Would this speed up the query execution time?
Assuming that there is column named deviceId(change as per your needs), you can use top 1 with ties with window function row_number:
Select top 1 with ties *
from MY_TABLE
where [TIME]> = DATEADD (hh, -48, #TimeNow)
Order by row_number() over (
partition by deviceId
order by Time desc
);
You can simply create Common Table Expression that sorts and groups the entries and then pick the latest one from there.
;WITH numbered
AS ( SELECT [SERIAL_NBR], [TIME], [VALUE], row_nr = ROW_NUMBER() OVER (PARTITION BY [SERIAL_NBR] ORDER BY [TIME] DESC)
FROM MY_TABLE
WHERE [TIME]> = DATEADD (hh, -48, #TimeNow) )
SELECT [SERIAL_NBR], [TIME], [VALUE]
FROM numbered
WHERE row_nr = 1 -- we want the latest record only
Depending on the amount of data and the indexes available this might or might not be faster than Anthony Hancock's answer.
Similar to his answer you might also try the following:
(from MSSQL's point of view, the below query and Anthony's query are pretty much identical and they'll probably end up with the same query plan)
SELECT [SERIAL_NBR] , [TIME], [VALUE]
FROM MY_TABLE AS M
JOIN (SELECT [SERIAL_NBR] , max_time = MAX([TIME])
FROM MY_TABLE
GROUP BY [SERIAL_NBR]) AS L -- latest
ON L.[SERIAL_NBR] = M.[SERIAL_NBR]
AND L.max_time = M.[TIME]
WHERE M.DATE_TIME >= DATEADD(hh,-48,#TimeNow)
Your listed column values and your code don't quite match up so you'll probably have to change this code a little, but it sounds like for each SERIAL_NBR you want the record with the highest DATE_TIME in the last 48 hours. This should achieve that result for you.
SELECT SERIAL_NBR,DATE_TIME,VALUE
FROM MY_TABLE AS M
WHERE M.DATE_TIME >= DATEADD(hh,-48,#TimeNow)
AND M.DATE_TIME = (SELECT MAX(_M.DATE_TIME) FROM MY_TABLE AS _M WHERE M.SERIAL_NBR = _M.SERIAL_NBR)
This will get you details of the latest record per serial number:
Select t.SERIAL_NBR, q.FieldsYouWant
from MY_TABLE t
outer apply
(
selct top 1 t2.FieldsYouWant
from MY_TABLE t2
where t2.SERIAL_NBR = t.SERIAL_NBR
order by t2.[TIME] desc
)q
where t.[TIME]> = DATEADD (hh, -48, #TimeNow)
Also, worth sticking DATEADD (hh, -48, #TimeNow) into a variable rather than calculating inline.

Optimize short part of a SQL Server query

I have this subquery that takes a little to long. Does anyone has any idea about how can I modify it in order to be faster?
ISNULL(ISNULL(
(select top 1 CONVERT(VARCHAR(11), qq.startdate , 111)
from (select a.startdate, a.ownerid
from wfassignment a
where a.ownertable='PM' /*order by a.startdate*/)qq
where qq.ownerid=pm.pmuid ),
(select min(w.reportdate)
from workorder w where w.pmnum=pm.pmnum
and w.siteid=pm.siteid
and w.orgid= pm.orgid)
),CONVERT(DATETIME,'01-02-2015 00:00:00'))
In oracle it's much more faster than in SQL Server. I also want to know for sure if top 1 is equivalent with rownum=1 from oracle.
Thanks :)
I'm assuming you need the minimum startdate in your first subquery, so I worked out this:
select top 1 [sq].[pm_date]
from
(
select convert(tinyint, 1) as [priority]
, min(a.startdate) as [pm_date]
from wfassignment a
where a.ownertable = 'PM'
and a.ownerid = pm.pmuid
union all
select 2
, min(w.reportdate)
from workorder w
where w.pmnum = pm.pmnum
and w.siteid = pm.siteid
and w.orgid = pm.orgid
union all
select 3
, convert(datetime, '2015-02-01 00:00:00')
/* use yyyymmdd format to avoid confusion when casting datetime values */
) as sq
where [sq].[pm_date] is not null
order by [sq].[priority] asc
You need to add the outer reference to the [pm] alias though, but that part wasn't given in your question, so I've just worked it out like this.

Calculate same day start/end dates as 0 days if another occurrence already exists

I have a query where there are instances where a "phase" starts and ends on the same day - this is calculated as 1 day. If, however, another "phase" starts and ends on the same day against the same ref. no. and period no., then I'd like to calculate this as 0 days.
Example:
**Ref. Period. Phase StDt EndDt**
013 3 KAA 01/01/16 01/01/16 - This is one day
013 3 TAA 02/01/16 03/01/16 - this is 2 days
013 3 KAT 01/01/16 01/01/16 - **would like this to be counted as 0 day**
013 3 TTA 04/04/16 04/04/16 - this is one day
I would like this unique calculation to be done in the data grouped by Ref. And Period numbers. This is a tricky one....
Thanks
Try this.
I am assuming that you are using TSQl (Not sure a you have also tagged SQL.
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY ID) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
If there is no ID column then select some column to order by, probably Phase so replace ID with Phase as here ROW_NUMBER() OVER (PARTITION BY StDt,EndDt ORDER BY ID) AS [Order], if there is no candidate column to order by then try this
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY (SELECT NULL)) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
This expression should work on the SSRS side:
=IIF(Fields!StartDate.Value=Fields!EndDate.Value AND Fields!Phase.Value <> LOOKUPSET(Fields!StartDate.Value &"_" & Fields!EndDate.Value,Fields!StartDate.Value & "_" & Fields!EndDate.Value,Fields!Phase.Value,"DatasetName").GetValue(0),0,DATEDIFF("D",Fields!StartDate.Value,Fields!EndDate.Value)+1)
It will return a value of 1 for the first phase returned by the dataset. If the phase-date range combinations are not unique within the grouping, this will not work as written, but you should be able to modify accordingly.
Also, if the rows are sorted differently between SSRS and the dataset, it may not be the first row that appears that gets the 1.
The below did the trick! Basically, I'm using count aggregate to count the number of instances where phases start and end on the same day PER Ref and period. Then, for any where there are more than 1, I just use simple case statments to count the first one as 1 and any subsequent ones as 0. I'm creating the below as a subquery in the joins as a left outer join:
LEFT OUTER JOIN
(SELECT TOP (100) PERCENT Period, Ref,
CONVERT(date, PhaseStartDate) AS stdt, CONVERT(date, PhaseEndDate) AS enddt,
COUNT(*)
AS NoOfSameDayPhases,
MIN(PhaseSequence) AS FirstPhSeq
FROM Phases AS Phases_1
WHERE (CONVERT(date, PhaseStartDate) =
CONVERT(date, PhaseEndDate))
GROUP BY VoidPeriod, Ref, CONVERT(date,
PhaseStartDate), CONVERT(date, PhaseEndDate)) AS SameDayPH ON CONVERT(date,
PhaseEndDate) = SameDayPH.enddt AND CONVERT(date,
PhaseStartDate) = SameDayPH.stdt AND
VoidPeriod = SameDayPH.VoidPeriod AND SameDayPH.Ref =
VoidPhases.Ref

Multiple aggregations with different where clauses

I have a table called Entries that manages transactions for specific Units.
In entries, there is a column called Sales Source that specifies the type of sale completed. I am trying to create a table that shows the total amount of revenue made per unit per sale source type.
For example:
A unit called AB104 has 8000$ in revenue, 5000$ was made via a Phone source and 3000$ was from a Fax source. I want to display a table that shows the unit number and the amount made for each sale type in the same line.
I tried the following query:
SELECT Unit,
sum(MoneyIN + MoneyOUT) AS Revenue,
sum(VolumeOUT+ VolumeIN) AS volume,
sum(WeightOUT + WeightIN)AS weight,
PercentageR = convert(VARCHAR,convert(MONEY,sum(MoneyIN+MoneyOUT)*100 /
(SELECT sum(MoneyIN + MoneyOUT)
FROM Entries)), 1) + '%',
PercentageV = convert(VARCHAR,convert(MONEY,sum(VolumeIN+VolumeOUT)*100 /
(SELECT sum(VolumeIN + VolumeOUT)
FROM Entries)), 1) + '%',
PercentageW = convert(VARCHAR,convert(MONEY,sum(WeightIN+WeightOUT)*100 /
(SELECT sum(WeightIN + WeightOUT)
FROM Entries)), 1) + '%',
LinkRevenue=
(SELECT sum(MoneyIN+MoneyOUT)
WHERE salesSource ='Link'),
PhoneRevenue=
(SELECT sum(MoneyIN+MoneyOUT)
WHERE salesSource ='Phone'),
EmailRevenue=
(SELECT sum(MoneyIN+MoneyOUT)
WHERE salesSource ='Email'),
FaxRevenue=
(SELECT sum(MoneyIN+MoneyOUT)
WHERE salesSource ='Fax'),
NoneRevenue=
(SELECT sum(MoneyIN+MoneyOUT)
WHERE salesSource ='None')
FROM Entries
GROUP BY Unit,
SalesSource
ORDER BY Unit
The problem with that query however, is that it doesnt show the different types of revenues for each unit in one line. This query would output:
Unit | rev | vol | weight | % Revenue | % Weight | % Volume | $ Link |$Phone |$Email|$Fax |$None
AB104|5000$|22|15000| 17%| 10%| 15%| 0$|5000$|0$|0$|0$
AB104|3000$|12|18000| 21%| 21%| 7%| 0$|0$|0$|3000$|0$
instead I want it to group all the details for the same unit in one line, like this:
Unit | rev | vol | weight | % Revenue | % Weight | % Volume | $ Link |$Phone |$Email|$Fax |$None
AB104|8000$|34|33000| 38%| 31%| 22%| 0$|5000$|0$|3000$|0$
How can I accomplish that? when I take salesSource out of the grouping, I get an error.
Your group by has two columns in it. You are seeing a Unit row for every SalesSource. You can add SalesSource as a column by itself and see.
Also, you want to avoid adding additional selects in the select clause cause your making SQL run a separate query for every result, in your case 3 queries for every result. This will make your query not very scalable.
I think you need to remove the SalesSource from you group by clause and use case statements.
Sum(Case when SalesSource = 'Link' then (MoneyIn+MoneyOut) else 0 end) as LinkRevenue,
You can get rid of the select-in-a-select by using a common table expression:
;with totals as (
SELECT sum(WeightIN + WeightOUT) as WeightTotal,
sum(VolumeIN + VolumeOUT) as VolumeTotal,
sum(MoneyIN + MoneyOUT) as MoneyTotal
FROM Entries
)
SELECT Unit,
sum(MoneyIN + MoneyOUT) AS Revenue,
sum(VolumeOUT+ VolumeIN) AS volume,
sum(WeightOUT + WeightIN)AS weight,
PercentageR = convert(VARCHAR,convert(MONEY,sum(MoneyIN+MoneyOUT)*100 /
totals.MoneyTotal), 1) + '%',
PercentageV = convert(VARCHAR,convert(MONEY,sum(VolumeIN+VolumeOUT)*100 /
totals.VolumeTotal), 1) + '%',
PercentageW = convert(VARCHAR,convert(MONEY,sum(WeightIN+WeightOUT)*100 /
totals.WeightTotal), 1) + '%',
Sum(Case when SalesSource = 'Link' then (MoneyIn+MoneyOut) else 0 end) as LinkRevenue,
....
FROM Entries, totals
GROUP BY Unit
ORDER BY Unit
You can clean it up with a couple more cte's, I didn't format the Money columns with the $ sign but you can add that into the final select
With
--create CTE with money columns summed for the pivotcte
TotalsCTEBySource (Unit, [Revenue], SalesSource) AS
(SELECT Unit, MoneyIn+MoneyOut , SalesSource FROM Entries),
--create CTE with table grand totals for % calcs
GrandTotalsCTE ([Revenue], [Volume],[Weight]) AS
(SELECT sum(MoneyIn+MoneyOut) , sum(VolumeIn+VolumeOut) , sum(WeightIn+WeightOut) FROM Entries),
--create totals by unit cte
TotalsCTEByUnit (Unit, [Revenue], [Volume],[Weight]) AS
(SELECT Unit, sum(MoneyIn+MoneyOut) , sum(VolumeIn+VolumeOut) , sum(WeightIn+WeightOut)
FROM Entries GROUP BY Unit),
--create pivot cte to get the sales source values as column headers, use coalesce() to turn NULL into 0
PivotCTE (Unit, [$ Link],[$ Phone],[$ Fax],[$ Email],[$ None])AS
( select Unit,coalesce(sum([Link]),0), coalesce(sum([Phone]),0),coalesce(sum([Fax]),0),
coalesce(sum([Email]),0),coalesce(sum([None]),0) FROM(
select Unit, [Link],[Phone],[Fax],[Email],[None] from TotalsCTEBySource
PIVOT
(sum(Revenue)FOR SalesSource in ([Link],[Phone],[Fax],[Email],[None])) pivottable
) moneybygroup
group by Unit )
--bring it all together
SELECT t.Unit, t.Revenue, t.Volume, t.Weight,
CONVERT(VARCHAR(50),round(t.Revenue/g.revenue*100,0))+' %' AS [% Revenue],
CONVERT(VARCHAR(50),round(t.Volume/g.Volume*100,0))+' %' AS [% Volume],
CONVERT(VARCHAR(50),round(t.Weight/g.Weight*100,0))+' %' AS [% Weight]
p.[$ Link],p.[$ Phone],p.[$ Fax],p.[$ Email],p.[$ None]
FROM TotalsCTEByUnit t
Join PivotCTE p on t.unit=p.unit
join GrandTotalsCTE g on 1=1

SQL - Combine another SELECT query with a JOIN

I found this post on stackoverflow, Add a summary row with totals
What I'm trying to accomplish is just that, but with the converted DURATION field I have listed in this WITH statement below. I'm trying to TOTAL up the Durations column for that day(24 hr period). I don't know if it's possible. Let me know. Thank you!
;WITH dupes AS
(
SELECT
CALLER_PHONE, DIALED_PHONE
FROM
dbo.PBXDATA
GROUP BY
CALLER_PHONE, DIALED_PHONE
)
SELECT
c.CALL_TIME, c.SALES_REP, c.CALL_TYPE, c.FLAG1,
COALESCE(NULLIF(c.FLAG3, 'NULL'),'') AS FLAG3,
ISNULL(dupes.CALLER_PHONE, '') + ISNULL(dupes.DIALED_PHONE,'') AS PHONE,
CONVERT(VARCHAR(8), c.DURATION, 108) AS DURATION
FROM
dupes
JOIN
dbo.PBXDATA c ON dupes.CALLER_PHONE = c.CALLER_PHONE
OR dupes.DIALED_PHONE = c.DIALED_PHONE
WHERE
(c.SALES_REP LIKE 'Doug%' OR
c.SALES_REP LIKE 'Nick%' OR
c.SALES_REP LIKE 'Bob%' OR
c.SALES_REP LIKE 'Joe%' OR
c.SALES_REP LIKE 'John%')
AND (c.CALL_TIME >= DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)))
AND (c.CALL_TIME < DATEADD(DAY, 1, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)))
AND DURATION = (SELECT CAST(DATEADD(S, SUM(DATEDIFF(S, '00:00:00', DURATION)), '00:00:00') AS TIME)
FROM dbo.PBXDATA)
ORDER BY
c.CALL_TIME;
If you just want an overall total for the Duration in your dupes table, you can just sum your Duration there.
;WITH dupes AS
(
SELECT CALLER_PHONE, DIALED_PHONE, convert(varchar(8), SUM(c.DURATION), 108) AS Total_Time
FROM dbo.PBXDATA
GROUP BY CALLER_PHONE, DIALED_PHONE
)
And add , Total_Time to your SELECT statement.
If you have multiple days in your query, you would need to add the date field in the dupes and add it as part of the JOIN's ON clause.

Resources