SQL cursor over categories? - sql-server

I tried to post full SQL code to walk you through the data and conversions, but it wouldn't post here. Long story short, I end up with a data table like this:
Location Date Direction PreviousDirection Offset
site1 2013-07-22 11:30:45.000 302 302 0
site1 2013-07-22 11:31:45.000 322 302 20
site1 2013-07-22 11:32:45.000 9 322 47
site1 2013-07-22 11:33:45.000 9 9 0
site1 2013-07-22 11:34:45.000 0 9 -9
site2 2013-07-22 11:30:45.000 326 326 0
site2 2013-07-22 11:31:45.000 2 326 36
site2 2013-07-22 11:32:45.000 2 2 0
site2 2013-07-22 11:33:45.000 2 2 0
site2 2013-07-22 11:34:45.000 2 2 0
Location,Date is the primary key. I need help generating an [AdjustedDirection] column calculated as follows:
For first row (for each Location e.g. site1, site2): Since there is no previous row to calculate on, AdjustedDirection = first row's Direction.
After that, Second row AdjustedDirection: It's the first row's AdjustedDirection plus second row's offset.
Third row AdjustedDirection: It's the second row's AdjustedDirection plus third row's offset.
and so on...
I think this requires a cursor, but I don't know the syntax to do a cursor over multiple categories (Locations) and/or maybe there is a different answer. I can't describe how many steps and how complicated the process was to get to this step. I'm so close to the end and totally stuck here!
If anyone has a clue how to populate these AdjustedDirection values, please prove your awesomeness. Thanks!!
Results should look like this (date truncated for spacing, previous adjusted direction shown for clarity of how current row Adjusted is calculated):
Location Date Direction Offset PrevAdjDirection AdjustedDirection
site1 11:30:45.000 302 0 302 302
site1 11:31:45.000 322 20 302 322
site1 11:32:45.000 9 47 322 369
site1 11:33:45.000 9 0 369 369
site1 11:34:45.000 0 -9 369 360
site2 11:30:45.000 326 0 326 326
site2 11:31:45.000 2 36 326 362
site2 11:32:45.000 2 2 362 362
site2 11:33:45.000 2 2 362 362
site2 11:34:45.000 2 2 362 362
thanks!

Here is a solution using correlated subqueries, some of which can be replaced by window functions (the version of SQL Server makes a difference here).
You want to change your logic. Equivalent logic is:
For the first row, use the Direction
For subsequent rows, use the cumulative sum of the offsets excluding the first offset plus the direction from the first row.
The following calculates the appropriate variables using correlated subqueries, and then combines them using simple logic:
select t.*,
FirstOffset + coalesce(SumEarlierOffsets - FirstOffset + Offset, 0) as AdjustedOffset
from (select t.*,
(select Direction
from t t2
where t2.location = t.location
order by date asc
) as FirstDirection,
(select SUM(offset)
from t t2
where t2.location = t.location and
t2.date < t.date
) as SumEarlierOffsets,
(select Offset
from t t2
where t2.location = t.location
order by date asc
) as FirstOffset
from t
) t

I ended up dumping the current data into a temp table and doing a WHILE UPDATE like this
SELECT Location, Date, Direction, Offset, Adjusted = NULL
INTO #results
FROM t1
WHILE (
SELECT COUNT(*) FROM #results WHERE Adjusted IS NULL
) > 0
UPDATE TOP (1) t1
SET Adjusted = ISNULL(t2.Adjusted,ISNULL(t2.Direction,t1.Direction)) + t1.Offset
FROM #results t1
LEFT JOIN #results t2 ON t2.Location = t1.Location AND t2.Date = DateAdd(minute,-1,t1.Date)
WHERE t1.Adjusted IS NULL
Thanks for the input and inspiration!

Related

Get value where ID in one table equals to the ID in another table

I am stuck on this SQL problem which may be easier than I think. So in a nutshell, how do I go about selecting the cost from the appropriate garage when the GarageHistID in the GarageCosts table equals to the ID in the GarageHistory table?
GarageCosts
GarageID Cost Version GarageHistID
950 213 1 455
950 342 3 NULL
GarageHistory
ID VendorID Version GarageID
454 44 1 NULL
455 2 1 950
456 44 2 NULL
Expected Output:
VendorID Cost Version
2 213 1
44 0 1
44 0 2
This is just a left join coalescing a null to zero.
SELECT
gh.VendorID,
ISNULL(gc.Cost,0) AS Cost,
gh.Version
FROM GarageHistory gh
LEFT JOIN GarageCost gc
ON gh.GarageID = gc.GarageID
AND gh.VersionID = gc.VersionID
There is no (specific) need to have bi-directional keys in your 2 tables, but you could use either for the join (along with VersionID).
The following query gives the exact results you mentioned in your question. You can use left join to join the two tables based on GarageHistID field in GarageCosts table and ID field in GarageHistory table
SELECT
gh.VendorID,
ISNULL(gc.Cost,0) AS Cost,
gh.[Version]
FROM GarageHistory gh
left JOIN GarageCosts gc
ON gh.ID = gc.GarageHistID
order by gc.Cost desc

Which select statement is suitable for the below SQLServer View?

I am trying to get the total number of trips a meter has undergone based on a set of records.
-- MeterRecord Table
Id IVoltage ICurrent
--------------------
1 340 0 <<<-- (Trip is zero at this point)
2 288 1
3 312 2
4 236 1
5 343 0 <<<-- (Trip is one at this point)
6 342 0
7 264 1
8 269 0 <<<-- (Trip is two at this point)
Trip is incremented by one only when 'ICurrent' value returns back to zero from a previous non-zero state.
What i have tried using Count function:
Select SUM(IVoltage) as Sum_Voltage, COUNT(case when ICurrent = 0 then 1 else 0 end) as Trips
This returns
Sum_Voltage Trips
---------------------
45766 8
What i am trying to achieve based on the table above
--MeterRecord View
Sum_Voltage Trips
---------------------
45766 2
Use LAG to determine if you have a trip:
DROP TABLE IF EXISTS #meterRecord
CREATE TABLE #meterRecord
(
Id INT,
IVoltage INT,
ICurrent INT
);
INSERT INTO #meterRecord
VALUES
(1,340,0),
(2,288,1),
(3,312,2),
(4,236,1),
(5,343,0),
(6,342,0),
(7,264,1),
(8,269,0);
WITH cte AS
(
SELECT IVoltage,
CASE WHEN ICurrent = 0 AND LAG(ICurrent,1) OVER(ORDER BY Id) != 0 THEN 1 ELSE 0 END isTrip
FROM #meterRecord
)
SELECT SUM(cte.IVoltage) AS Sum_Voltage,
SUM(isTrip) AS Trips
FROM cte

SUM of Column based on Distinct value of other Column

I have a Job Work flow table in SQL Server which is having the task start_time and end_time on a page and total points for that page.
The Page start_time and end_time on a particular date will be as duplicated i.e the employee will have started and stopped the task on that page so many times like below
My Query
SELECT DISTINCT tu.user_id,tjd.job_id, tjd.job_case_no,isnull(tu.first_name,tu.user_name)Name,(Count(tjw.total_pages)) total_pages,tjd.job_status_id_fk,tjd.drawing_type_id_fk,SUM(tjw.total_points) Points
FROM dbo.tbl_job_workflow tjw
LEFT JOIN dbo.tbl_user tu ON tu.user_id = tjw.user_id_fk
LEFT JOIN dbo.tbl_job_details tjd ON job_id=job_id_fk
WHERE isnull(tjd.job_case_no,'')<>'' AND tjw.start_time>='2016-06-28' AND tjw.end_time<='2016-06-28'
GROUP BY tjd.job_case_no,tu.first_name,tu.user_name,tu.user_id,tjd.job_id,job_status_id_fk,tjd.drawing_type_id_fk
Sample Output
user_id job_id job_case_no Name total_pages Points
4 298 Testcase_17062016_0244PM Emp1 1 6
4 346 TestCase-01 Emp1 2 4
27 346 TestCase-01 Emp2 11 11
27 350 5435435 Emp2 1 1
4 350 5435435 Emp1 5 5
In the above Output for case TestCase-01 for uemployee Emp2 the points should be 10 not 11
I need to get Sum of the Points of distinct pages on the for a day.
i.e if a job with 4 pages haven been worked on with page no 1 having 2 Points have been worked on the same day twice then the Sum of the Points for that day should be 2 not 4
Kindly anyone guide me how to get the Sum based on the above condition
I had took the Sum by passing the variables to a function
;WITH CTE_Table
AS
(
SELECT DISTINCT tjw.total_points,tjw.total_pages
FROM dbo.tbl_job_workflow tjw WHERE USER_id_fk=#user_id and job_id_fk=#job_id
)
SELECT SUM(total_points) FROM CTE_Table

T-SQL how to calculate datediff from previous or next row on log?

I use MS SSMS 2008 R2 to extract data from our company management software, which registers our employee actions and schedules. The table has and ID field, which is unique to each entry. job is the activity the user is performing. user is the user ID. start_time and duration are exactly that. Then there is a "type" where 0 is login (the user logs into the job) and 1 is available time (while performing a job the user may be available or not). "reason" is the reason why the user has become unavailable (break, coffee, lunch, training, etc). Type 0 entries have no reason so reason is always null.
I need to extract the unavailable times by reason and all I'm being able to achieve is to do a DATEADD of duration to start_time in order to get end_time and then use Excel to manually calculate the times for each row.
The SQL table looks like this:
id job user start_time duration type reason
4436812 3 758 05-06-2015 09:00 125670 0 NULL
4436814 3 758 05-06-2015 09:00 6970 1 1004
4436944 3 758 05-06-2015 09:14 39280 1 1004
4437119 3 758 05-06-2015 10:20 0 1 1002
4437172 3 758 05-06-2015 10:35 18470 1 1004
4437312 3 758 05-06-2015 11:09 3960 1 1004
4437350 3 758 05-06-2015 11:16 0 1 1006
4437360 3 758 05-06-2015 11:19 30080 1 1004
4437638 3 758 05-06-2015 12:13 6730 1 1004
4437695 3 758 05-06-2015 12:24 0 1 1007
4438227 3 758 05-06-2015 13:43 NULL 0 NULL
4438228 3 758 05-06-2015 13:43 NULL 1 NULL
(job = 3 and user = 758)
This is the query I made:
select CONVERT(date,start_time) Data, a.job, a.user, convert(varchar(15),convert(datetime,a.start_time),108) StartTime, a.duration duracao,
convert(varchar(15),convert(datetime,DATEADD(second,a.duration/10,a.start_time)),108) EndTime, a.type, a.reason
from schedule_log a
where a.job = 3
and a.user = 758
and CONVERT(date,start_time) = '20150605'
order by a.start_time, a.type
Which translates to:
Date job user LogTime Avail NotAvail
2015-06-05 3 758 04:44:01 04:10:23 00:33:38
So, for each reason, I have to do a DATEDIFF from end time (start+duration) to either the next type 1 start_time or the previous type 0 end time, which ever happened first (the user may become unavailable and then logoff).
How do I do this?
ps: duration is in tenths of second.
Ok, here is my updated suggestion. It is broken into three steps for clarity, but the temp tables are unnecessary - they could become subqueries.
Step 1: Calculate the end time for each period of activity, excluding logins.
Step 2: Join each row to the row that occurred immediately after it, to get the unavailable time following each reason. Note: some of your timestamps do not line up properly, possibly as a result of storing duration in seconds but timestamps only to the minute.
Step 3: Total the unavailable time, and subtract from the duration of the login to get the available time.
Step 4: Total the unavailable time by reason.
SELECT *
,dateadd(s, duration / 10, start_time) AS Endtime
,row_number() OVER (
PARTITION BY job ,[user] ORDER BY start_time, [type]
) AS RN
INTO #temp2
FROM MyTable
WHERE [type] = 1
SELECT a.[user]
,a.job
,a.reason
,a.start_time
,a.type
,a.duration / 10 AS AvailableSeconds
,datediff(s, a.Endtime, b.start_time) AS UnavailableSeconds
INTO #temp3
FROM #temp2 a
LEFT JOIN #temp2 b
ON a.[user] = b.[user]
AND a.job = b.job
AND a.RN = b.RN - 1
SELECT cast(a.start_time AS DATE) AS [Date]
,a.job
,a.[user]
,b.duration / 10 AS LogTime
,b.duration / 10 - sum(UnavailableSeconds) AS Avail
,sum(UnavailableSeconds) AS NotAvail
FROM #temp3 a
LEFT JOIN MyTable b
ON a.job = b.job
AND a.[user] = b.[user]
AND b.[type] = 0
AND b.duration IS NOT NULL
GROUP BY cast(a.start_time AS DATE)
,a.job
,a.[user]
,b.duration
SELECT cast(a.start_time AS DATE) AS [Date]
,a.job
,a.[user]
,a.reason
,sum(UnavailableSeconds) AS NotAvail
FROM #temp3 a
where reason is not null
GROUP BY cast(a.start_time AS DATE)
,a.job
,a.[user]
,a.reason

group by with 'pre-defined row'

Say I have to following PaymentTransaction Table:
ID Amount PayMethodID
----------------------------
10254 100 1
15789 150 1
15790 200 0
16954 300 0
17864 400 1
19364 500 1
PayMethodID Desc
----------------------------
0 CASH
1 VISA
2 MASTER
3 AMEX
4 ETC
I can simply use a group by to group the PayMethodID under 1 and 0.
What i am trying to do is to show also the non-exist PayMethodID under GROUP BY
My current result with simple group by statement is
PayMethodID TotalAmount
-------------------------
0 500
1 1150
Expected result (to show 0 if its not exits in the transaction table):
PayMethodID TotalAmount
-------------------------
0 500
1 1150
2 0
3 0
4 0
This might be a simple and duplicated question, but i just cant find the keyword to search around. I would remove this post if you can find me any duplication. Thanks.
You can use LEFT JOIN, so all rows from leftmost table (TableA) will be shown whether it has a matching values on the other table or not.
SELECT a.PayMethodID,
TotalAmount = ISNULL(SUM(b.Amount), 0)
FROM TableA AS a -- <== contains list of card type
LEFT JOIN TableB AS b -- <== contains the payment list
ON a.PayMethodID = b.PayMethodID
GROUP BY a.PayMethodID
A regular OUTER (LEFT) JOIN will give you all rows from the PayMethod table no matter if they exist in the PaymentTransaction table, the rest of the sums being NULL. You can then use a COALESCE to make the null rows zero;
SELECT pm.PayMethodID, COALESCE(SUM(pt.Amount), 0) TotalAmount
FROM PayMethod pm
LEFT JOIN PaymentTransaction pt
ON pm.PayMethodID = pt.PayMethodID
GROUP BY pm.PayMethodID
An SQLfiddle to test with.

Resources