insert 0 into successive row with same fields value - sql

I have a table containing the following line:
I'd like to create a view that displays the following result (without changing my original table) :
For each line having the same id,day,month and year I'd like to leave a single line with the cost and count and insert 0 in the others.

Here is a portable approach not requiring PARTITION. I have assumed you will not have the same datetimeIN value for more than one row in a group:
select t.id, t.day, t.month, t.year,
case when tm.id is null then 0 else t.cost end as cost,
case when tm.id is null then 0 else t.Count end as Count,
t.datetimeIN, t.datetimeOUT
from MyTable t
left outer join (
select id, day, month, year, min(datetimeIN) as minIN
from MyTable
group by id, day, month, year
) tm on t.id = tm.id
and t.day = tm.day
and t.month = tm.month
and t.year = tm.year
and t.datetimeIN = tm.minIN

You can do something like this:
SELECT id, day, month, year,
CASE WHEN nNum = 1 then cost else 0 end as cost,
CASE WHEN nNum = 1 then "Count" else 0 end as "Count",
datetimeIN, datetimeOUT
FROM (
SELECT id, day, month, year,
cost, "Count", datetimeIN, datetimeOUT,
row_number() OVER (PARTITION BY id, day, month, year
ORDER BY datetimeIN) as nNum
FROM TableName
) A
It uses row_number() to number the rows, and then a CASE statement to single out the first one and make it behave differently.
See it working on SQL Fiddle here.

or, using a common table expression:
with commonTableExp ([day], [month], [year], minDate) as (
select [day], [month], [year], min(datetimeIn)
from #temp
group by [day], [month], [year])
select id,
dt.[day],
dt.[month],
dt.[year],
case when datetimein = minDate then [cost] else 0 end,
case when datetimein = minDate then [count] else 0 end,
dateTimeIn
from #temp dt join commonTableExp cte on
dt.[day] = cte.[day] and
dt.[month] = cte.[month] and
dt.[year] = cte.[year]
order by dateTimeIn

Query
Select id, [day], [month], [year], Case When K.RowID = 1 Then [cost] Else 0 End as Cost, Case When K.RowID = 1 Then [count] Else 0 End as [count], [DateTimeIN], [DateTimeOut] From
(
select ROW_NUMBER() Over(Partition by id, [day], [month], [year] Order by ID ) as RowID, * From Testing
)K
Drop table Testing
Click here to see SQL Profiler details for Red Filter's Query
Click here to see SQL Profiler details for my Query
For More Information You can see SQL Fiddle

Related

SQL calculation with previous row + current row

I want to make a calculation based on the excel file. I succeed to obtain 2 of the first records with LAG (as you can check on the 2nd screenshot). Im out of ideas how to proceed from now and need help. I just need the Calculation column take its previous data. I want to automatically calculate it over all the dates. I also tried to make a LAG for the calculation but manually and the result was +1 row more data instead of NULL. This is a headache.
LAG(Data ingested, 1) OVER ( ORDER BY DATE ASC ) AS LAG
You seem to want cumulative sums:
select t.*,
(sum(reconciliation + aves - microa) over (order by date) -
first_value(aves - microa) over (order by date)
) as calculation
from CalcTable t;
Here is a SQL Fiddle.
EDIT:
Based on your comment, you just need to define a group:
select t.*,
(sum(reconciliation + aves - microa) over (partition by grp order by date) -
first_value(aves - microa) over (partition by grp order by date)
) as calculation
from (select t.*,
count(nullif(reconciliation, 0)) over (order by date) as grp
from CalcTable t
) t
order by date;
Imo this could be solved using a "gaps and islands" approach. When Reconciliation>0 then create a gap. SUM(GAP) OVER converts the gaps into island groupings. In the outer query the 'sum_over' column (which corresponds to the 'Calculation') is a cumumlative sum partitioned by the island groupings.
with
gap_cte as (
select *, case when [Reconciliation]>0 then 1 else 0 end gap
from CalcTable),
grp_cte as (
select *, sum(gap) over (order by [Date]) grp
from gap_cte)
select *, sum([Reconciliation]+
(case when gap=1 then 0 else Aves end)-
(case when gap=1 then 0 else Microa end))
over (partition by grp order by [Date]) sum_over
from grp_cte;
[EDIT]
The CASE statement could be CROSS APPLY'ed instead
with
grp_cte as (
select c.*, v.gap, sum(v.gap) over (order by [Date]) grp
from #CalcTable c
cross apply (values (case when [Reconciliation]>0 then 1 else 0 end)) v(gap))
select *, sum([Reconciliation]+
(case when gap=1 then 0 else Aves end)-
(case when gap=1 then 0 else Microa end))
over (partition by grp order by [Date]) sum_over
from grp_cte;
Here is a fiddle

SQL query get first punch and lastpunch for every employee

I have the following data in SQL Server:
What I need is that for every day by employee (employeeId) I get in the follwing data:
AccessCode column means I = PunchIn and O = PunchOut and we have to filter by lunchtype = 'N'
So basically the result should return only one row per day and all the punch ins and punch outs in the middle of the first entrance and last exist shouldn't be considered.
Any clue?
You can do conditional aggregation :
select employeeid, In, Out,
dateadd(second, datediff(second, in, out), 0) as Hours
from(select employeeid,
min(case when AccessCode = 'I' then timestamp end) as In,
max(case when AccessCode = 'O' then timestamp end) as Out
from table t
where lunchtype = 'N'
group by employeeid, convert(date, times)
) t;
You can try this
with cte as
(select
*,
cast(times as date) as myda
from myTable
)
select
employeeid,
mn as punch_in,
mx as punch_out,
datediff(minute, mn, mx)/60.0 as hours
from
(select
employeeid,
min(times) over (partition by myda) as mn,
max(times) over (partition by myda) as mx
from cte
) t
group by
employeeid, mn, mx
Try this:
select employeeId,
min(case when accessCode = 'I' then timestamp end) punchIn,
max(case when accessCode = 'O' then timestamp end) punchOut
from myTable
where lunchtype = 'N'
group by employeeId

How to get the validity date range of a price from individual daily prices in SQL

I have some prices for the month of January.
Date,Price
1,100
2,100
3,115
4,120
5,120
6,100
7,100
8,120
9,120
10,120
Now, the o/p I need is a non-overlapping date range for each price.
price,from,To
100,1,2
115,3,3
120,4,5
100,6,7
120,8,10
I need to do this using SQL only.
For now, if I simply group by and take min and max dates, I get the below, which is an overlapping range:
price,from,to
100,1,7
115,3,3
120,4,10
This is a gaps-and-islands problem. The simplest solution is the difference of row numbers:
select price, min(date), max(date)
from (select t.*,
row_number() over (order by date) as seqnum,
row_number() over (partition by price, order by date) as seqnum2
from t
) t
group by price, (seqnum - seqnum2)
order by min(date);
Why this works is a little hard to explain. But if you look at the results of the subquery, you will see how the adjacent rows are identified by the difference in the two values.
SELECT Lag.price,Lag.[date] AS [From], MIN(Lead.[date]-Lag.[date])+Lag.[date] AS [to]
FROM
(
SELECT [date],[Price]
FROM
(
SELECT [date],[Price],LAG(Price) OVER (ORDER BY DATE,Price) AS LagID FROM #table1 A
)B
WHERE CASE WHEN Price <> ISNULL(LagID,1) THEN 1 ELSE 0 END = 1
)Lag
JOIN
(
SELECT [date],[Price]
FROM
(
SELECT [date],Price,LEAD(Price) OVER (ORDER BY DATE,Price) AS LeadID FROM [#table1] A
)B
WHERE CASE WHEN Price <> ISNULL(LeadID,1) THEN 1 ELSE 0 END = 1
)Lead
ON Lag.[Price] = Lead.[Price]
WHERE Lead.[date]-Lag.[date] >= 0
GROUP BY Lag.[date],Lag.[price]
ORDER BY Lag.[date]
Another method using ROWS UNBOUNDED PRECEDING
SELECT price, MIN([date]) AS [from], [end_date] AS [To]
FROM
(
SELECT *, MIN([abc]) OVER (ORDER BY DATE DESC ROWS UNBOUNDED PRECEDING ) end_date
FROM
(
SELECT *, CASE WHEN price = next_price THEN NULL ELSE DATE END AS abc
FROM
(
SELECT a.* , b.[date] AS next_date, b.price AS next_price
FROM #table1 a
LEFT JOIN #table1 b
ON a.[date] = b.[date]-1
)AA
)BB
)CC
GROUP BY price, end_date

Joining sub-queries to get data for start and end provided dates optimization

I'm using SQL-Server 2008.
I have to select stock of items at provided start date and stock of items at provided end date from 2 warehouses.
This is how I'm selecting QuantityStock for #startDate:
DECLARE #startDate DATE = '20160111'
SELECT *
FROM (
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_start
LEFT JOIN some_table st ON.....
As you see there are 2 similar queries, just selecting from different tables, for that I'm using UNION ALL
Also I'm using [DATE] <= #startDate that because not every day remaining stock is inserting, so for provided date '20160111' there can be no data, so need to select max date where remaining stock is inserted.
With query above a bit slowly, but working fine.
Problem is that I need to do the same with #endDate to get remaining stock for end date. Query is similar as above just instead of #startDate I need to use #endDate.
I've tried to use query above and LEFT JOIN similar query, just with #endDate instead of #startDate in following:
DECLARE #startDate DATE = '20160111',
#endDate DATE = '20165112'
SELECT stock_start.*, stock_end.QuantityStockEnd
FROM (
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStock, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_start
LEFT JOIN (
SELECT SUM(QuantityStock) AS QuantityStockEnd, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #endDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStockEnd, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #endDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_end ON stock_start.Vendor = stock_end.Vendor AND stock_start.ItemNo = stock_end.ItemNo AND stock_start.Company = stock_end.Company
LEFT JOIN some_table st ON.....
In this way I got desired results, but Its execution time so high (about 10x longer than first query only with #startDate). Have you ideas how could I optimize It? It looks like there should be any other, simpler way, without repeating code...
So final results should be:
QuantityStockStart | Vendor | ItemNo | Company | [Date] | QuantityStockEnd
I suggest use of the analytic function ROW_NUMBER() to locate the wanted source table rows. While there is no sample data to test against it is something of a guess but I think you may be able to do this:
SELECT
whab.Vendor
, whab.ItemNo
, whab.Company
, MIN(CASE WHEN whab.start_rn = 1 THEN whab.[Date] END) start_dt
, SUM(CASE WHEN whab.start_rn = 1 THEN whab.QuantityStock END) qty_at_start
, MAX(CASE WHEN whab.end_rn = 1 THEN whab.[Date] END) end_dt
, SUM(CASE WHEN whab.end_rn = 1 THEN whab.QuantityStock END) qty_at_end
FROM (
SELECT
Vendor
, ItemNo
, Company
, [Date]
, QuantityStock
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #startDate THEN 1 ELSE 2 END, [Date] DESC) AS start_rn
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #endDate THEN 1 ELSE 2 END, [Date] DESC) AS end_rn
FROM WarehouseA
UNION ALL
SELECT
Vendor
, ItemNo
, Company
, [Date]
, QuantityStock
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #startDate THEN 1 ELSE 2 END, [Date] DESC) AS start_rn
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #endDate THEN 1 ELSE 2 END, [Date] DESC) AS end_rn
FROM WarehouseB
) whab
WHERE whab.start_rn = 1
OR whab.end_rn = 1
GROUP BY
whab.Vendor
, whab.ItemNo
, whab.Company
Try removing the subquery that follows WHERE [Date]= but keeping the where conditions from that subquery. Change [Date] to MAX([Date]) and remove [Date] from the GROUP BY in the query that used to use the removed subquery.

Golf Scoring SQL Query

I have the following MS SQL query which cross references three tables, (tTeam, tPlayer and tScores) to get the total "Net score", "Gross score" and "Position" ordered by Net score and Team.
SELECT TeamID, Team, NetScore, Gross,
CASE WHEN cnt > 1 THEN 'T' + CAST(rnk AS VARCHAR(5))
ELSE CAST(rnk AS VARCHAR(5))
END Pos
FROM (
SELECT tTeam.TeamID,
tTeam.Title AS Team,
SUM(CONVERT(INT, tScores.Net_Score)) AS NetScore,
SUM(CONVERT(INT, tScores.Out_Score) + CONVERT(int, tScores.In_Score)) AS Gross,
rank() OVER ( ORDER BY SUM(CONVERT(INT, tScores.Net_Score))) rnk,
COUNT(*) OVER ( PARTITION BY SUM(CONVERT(INT, tScores.Net_Score))) cnt
FROM tScores INNER JOIN tPlayer ON tScores.PlayerID = tPlayer.PlayerID INNER JOIN tTeam ON tPlayer.TeamID = tTeam.TeamID
WHERE tTeam.TournamentID = 13
GROUP BY tTeam.TeamID, tTeam.Title ) temp
ORDER BY NetScore, Team
The query works great but (and here is where i need some help), it is calculating all of the players Net and Gross scores by team when all I need it to do is calculate the "4 lowest Player's Net and Gross Scores" by team only.
I have spent the last day and a half pulling my hair out with this one and any help will be greatly appreciated.
Thanks in advance!
If I understood correctly that you want to sum four lowest scores per player only, you might use another set of row_numbers to isolate lowest scores. I don't think that rn_gross is necessary (based on rank() function) but I included it nevertheless. If there is no need for separate numbering remove conditionals from sums and add and lowestScores.rn_net <= 4 to where clause.
; with lowestScores as
(
select *,
ROW_NUMBER() over (PARTITION by PlayerID
order by CONVERT(INT, Net_Score)) rn_net,
ROW_NUMBER() over (PARTITION by PlayerID
order by CONVERT(INT, Net_Score) + CONVERT(int,In_Score)) rn_gross
from tScores
),
temp as
(
SELECT tTeam.TeamID,
tTeam.Title AS Team,
SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END) AS NetScore,
SUM(CASE WHEN rn_gross <= 4 THEN CONVERT(INT, lowestScores.Out_Score) END
+ CASE WHEN rn_gross <= 4 THEN CONVERT(int, lowestScores.In_Score) END) AS Gross,
rank() OVER ( ORDER BY SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END)) rnk,
COUNT(*) OVER ( PARTITION BY SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END)) cnt
FROM lowestScores
INNER JOIN tPlayer
ON lowestScores.PlayerID = tPlayer.PlayerID
INNER JOIN tTeam
ON tPlayer.TeamID = tTeam.TeamID
WHERE tTeam.TournamentID = 13
GROUP BY tTeam.TeamID, tTeam.Title
)
SELECT TeamID, Team, NetScore, Gross,
CASE WHEN cnt > 1
THEN 'T' + CAST(rnk AS VARCHAR(5))
ELSE CAST(rnk AS VARCHAR(5))
END Pos
FROM temp
ORDER BY NetScore, Team