Query to retain latest non-zero value - sql-server

I'm using SQL Server 2016 and have a table with the following data:
PlaySeq
TransMaxValue
1
250
2
500
3
0
4
400
5
0
6
300
7
500
8
0
9
0
I'm trying to construct a query that adds a column TransMaxValueContd to that dataset.
That TransMaxValueContd column should retain the last non-zero value encountered in column TransMaxValue.
Ordering is done by PlaySeq.
My expected result from the query is this:
PlaySeq
TransMaxValue
TransMaxValueContd
1
250
250
2
500
500
3
0
500
4
400
400
5
0
400
6
300
300
7
500
500
8
0
500
9
0
500
I've been using windowing functions like LAST_VALUE() and LAG(), but can't seem to get the results right.
Maybe I'm overcomplicating it.
Does anyone know how to do this?
ps: I'm just looking for the query. No need to modify the source table.
Edit: Added a SQLFiddle example with a failed attempt, which is:
SELECT a.PlaySeq,
a.TransMaxValue,
IIF(ISNULL(LAG(a.TransMaxValue,1) OVER (ORDER BY a.PlaySeq), a.TransMaxValue) = a.TransMaxValue, a.TransMaxValue, LAG(a.TransMaxValue,1) OVER (ORDER BY a.PlaySeq)) AS TransMaxValueContd
FROM myTable AS a;
Edit: Thank you for all the answers, which provided new useful insights!
I ended up using this, inspired by the answer from SteveC.
SELECT a.PlaySeq,
a.TransMaxValue,
(SELECT TOP 1 x.TransMaxValue
FROM myTable AS x
WHERE x.PlaySeq <= a.PlaySeq
AND x.TransMaxValue != 0
ORDER BY x.PlaySeq DESC) AS TransMaxValueContd
FROM myTable AS a;

A simple way could use OUTER APPLY and SELECT TOP(1). Something like this
select m.PlaySeq, m.[TransMaxValue],
case when m.[TransMaxValue]=0
then oa.TransMaxValue
else m.TransMaxValue end TransMaxValueContd
from myTable m
outer apply (select top(1) mm.[TransMaxValue]
from myTable mm
where m.PlaySeq>mm.PlaySeq
and mm.[TransMaxValue]>0
order by mm.PlaySeq desc) oa;
PlaySeq TransMaxValue TransMaxValueContd
1 250 250
2 500 500
3 0 500
4 400 400
5 0 400
6 300 300
7 500 500
8 0 500
9 0 500

Perhaps this will help:
DECLARE #Tab TABLE(PlaySeq INT, TransMaxValue INT)
INSERT #Tab
VALUES(1,250),(2,500),(3,0),(4,400),(5,0)
,(6,300),(7,500),(8,0),(9,0)
SELECT PlaySeq,
TransMaxValue,
FIRST_VALUE(TransMaxValue) OVER(PARTITION BY Grp ORDER BY PlaySeq) AS TransMaxValueContd
FROM (
SELECT
PlaySeq,
TransMaxValue,
SUM(CASE WHEN TransMaxValue = 0 THEN 0 ELSE 1 END) OVER(ORDER BY PlaySeq) AS Grp
FROM #Tab
) AS tbl
ORDER BY PlaySeq

Please try the following solution.
It is based on Windows function ROW_NUMBER() and non-equal relationship clause:
FROM #tbl AS t1 INNER JOIN
#tbl AS t2 ON t1.PlaySeq > t2.PlaySeq
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (PlaySeq INT, TransMaxValue INT);
INSERT INTO #tbl (PlaySeq, TransMaxValue) VALUES
(1, 250),
(2, 500),
(3, 0 ),
(4, 400),
(5, 0 ),
(6, 300),
(7, 500),
(8, 0 ),
(9, 0 );
-- DDL and sample data population, end
;WITH rs AS
(
SELECT t1.*
, t2.TransMaxValue AS prev_value
, ROW_NUMBER() OVER (PARTITION BY t1.PlaySeq ORDER BY t2.PlaySeq DESC) AS seq
FROM #tbl AS t1 INNER JOIN
#tbl AS t2 ON t1.PlaySeq > t2.PlaySeq
WHERE t2.TransMaxValue > 0
)
SELECT PlaySeq, TransMaxValue, TransMaxValue AS TransMaxValueContd
FROM #tbl WHERE PlaySeq = 1
UNION ALL
SELECT PlaySeq, TransMaxValue, IIF(TransMaxValue > 0, TransMaxValue, prev_value) AS TransMaxValueContd
FROM rs
WHERE seq = 1
ORDER BY PlaySeq ASC;
Output
+---------+---------------+--------------------+
| PlaySeq | TransMaxValue | TransMaxValueContd |
+---------+---------------+--------------------+
| 1 | 250 | 250 |
| 2 | 500 | 500 |
| 3 | 0 | 500 |
| 4 | 400 | 400 |
| 5 | 0 | 400 |
| 6 | 300 | 300 |
| 7 | 500 | 500 |
| 8 | 0 | 500 |
| 9 | 0 | 500 |
+---------+---------------+--------------------+

There may be a better way, but you could try using correlated subqueries:
SELECT q.PlaySeq, q.TransMaxValue
, (CASE
WHEN q.TransMaxValue <> 0 THEN q.TransMaxValue
ELSE (SELECT d.TransMaxValue FROM myTable d WHERE d.PlaySeq = q.PlaySeqRef)
END) TransMaxValueContd
FROM (
SELECT PlaySeq
, TransMaxValue
, (SELECT MAX(PlaySeq)
FROM myTable b
WHERE b.PlaySeq < a.PlaySeq
AND b.TransMaxValue <> 0) PlaySeqRef
FROM myTable a) q;

Related

Filter datetime columns in SQL Server

I have a datetime column that has a 5 min interval between the next data, however I want to see if that column contains any time interval less than 5 mins, particularly 5 secs.
So for example:
one date would read 2018-05-04 19:21:46.000
the next row would read 2018-05-04 19:26:46.000
and 2018-05-04 19:31:46.000.
However, we sometimes get rows that read:
2018-05-04 19:36:46.000
then 2018-05-04 19:36:51.000
then 2018-05-04 19:36:56.000
What SQL script would be best to filter the column to distinguish the erroneous data (the 5 secs interval) from the correct data (5 min interval) especially in a table with thousands of rows?
Hi #Andrea, thanks for that. I have a couple of questions. What does the 'q' stand for? and when i rewrite the query as
SELECT ProductID, MyTimestamp, DATEDIFF(second, xMyTimestamp, MyTimestamp) as DIFFERENCE_IN_SECONDS
FROM (
SELECT *,
Lag(MyTimestamp) OVER (ORDER BY MyTimestamp, ProductID) as xMyTimestamp
FROM TableName
) q
WHERE xMyTimestamp IS NOT NULL and ProductID= 31928
I get this result which doesn't compute the time accurately.
+-----------+-------------------------+-----------------------+
| ProductID | MyTimestamp | DIFFERENCE_IN_SECONDS |
+-----------+-------------------------+-----------------------+
| 31928 | 2017-03-21 13:36:30.000 | 0 |
| 31928 | 2017-03-21 13:46:30.000 | 0 |
| 31928 | 2017-03-21 13:56:32.000 | 0 |
| 31928 | 2017-03-21 14:01:32.000 | 0 |
| 31928 | 2017-03-21 14:11:32.000 | 0 |
| 31928 | 2017-03-21 14:16:32.000 | 0 |
| 31928 | 2017-03-21 14:26:32.000 | 0 |
| 31928 | 2017-03-21 14:36:32.000 | 0 |
+-----------+-------------------------+-----------------------+
Any reason why
Since you are on 2014, you can use LEAD to compare the value of one row, to the value of the next.
declare #table table(id int identity(1,1), interval datetime)
insert into #table
values
('2018-05-04 19:21:46.000'),
('2018-05-04 19:26:46.000'),
('2018-05-04 19:31:46.000'),
('2018-05-04 19:36:46.000'),
('2018-05-04 19:36:51.000'),
('2018-05-04 19:36:56.000')
select
id
,interval
,issue_with_row = case
when
isnull(datediff(minute,interval,lead(interval) over (order by id, interval)),0) < 5
then 1
else 0
end
from #table
order by id
Or if you wanted to only see those,
;with cte as(
select
id
,interval
,issue_with_row = case
when
isnull(datediff(minute,interval,lead(interval) over (order by id, interval)),0) < 5
then 1
else 0
end
from #table)
select *
from cte
where issue_with_row = 1
You can use LAG:
declare #tmp table(MyTimestamp datetime)
insert into #tmp values
('2018-05-04 19:21:46.000')
,('2018-05-04 19:26:46.000')
,('2018-05-04 19:31:46.000')
,('2018-05-04 19:36:46.000')
,('2018-05-04 19:36:51.000')
,('2018-05-04 19:36:56.000')
SELECT DATEDIFF(second, xMyTimestamp, MyTimestamp) as DIFFERENCE_IN_SECONDS
FROM (
SELECT *,
LAG(MyTimestamp) OVER (ORDER BY MyTimestamp) xMyTimestamp
FROM #tmp
) q
WHERE xMyTimestamp IS NOT NULL
results:
So you should use it like this:
SELECT DATEDIFF(second, xMyTimestamp, MyTimestamp) as DIFFERENCE_IN_SECONDS
FROM (
SELECT *,
LAG(MyTimestamp) OVER (ORDER BY MyTimestamp) xMyTimestamp
FROM [YOUR_TABLE_NAME_HERE]
) q
WHERE xMyTimestamp IS NOT NULL
Edit
Here is another sample based on new data posted by OP:
declare #tmp table(ProductID int, MyTimestamp datetime)
insert into #tmp values
(31928, '2017-03-21 13:36:30.000')
,(31928, '2017-03-21 13:46:30.000')
,(31928, '2017-03-21 13:56:32.000')
,(31928, '2017-03-21 14:01:32.000')
,(31928, '2017-03-21 14:11:32.000')
,(31928, '2017-03-21 14:16:32.000')
,(31928, '2017-03-21 14:26:32.000')
,(31928, '2017-03-21 14:36:32.000')
SELECT ProductID
,MyTimestamp
,DATEDIFF(second, xMyTimestamp, MyTimestamp) AS DIFFERENCE_IN_SECONDS
FROM (
SELECT *
,Lag(MyTimestamp) OVER (
ORDER BY MyTimestamp
,ProductID
) AS xMyTimestamp
FROM #tmp
) q
WHERE xMyTimestamp IS NOT NULL
AND ProductID = 31928
Output:
Here you can check that the results are calculated correctly.

How to generate an External ID based ID's that has negative value on price

I have this Data set
InvoiceID CDamount companyname
1 2500 NASA
1 -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5 -8000 SpaceX
I want to be able to get that as shown below:
External ID CDamount companyname
1 2500 NASA
1-C -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5-C -8000 SpaceX
I cannot use CASE WHEN CDamount < 0 THEN InvoiceID + '-' + 'C' ELSE InvoiceID END AS "External ID" because some of other companies have negative amount as well that do not fall under this category.
I was wondering how can I say IF InvoiceID is Duplicated AND CDAmount is Negative then Create a new External ID?
Is this something possible?
Below you can create the sample data
Create Table #Incident (
InvoiceID int,
CDamount int,
Companyname Nvarchar(255))
insert into #Incident Values (1,2500,'NASA')
insert into #Incident Values (1,-2500,'NASA')
insert into #Incident Values (2,1600,'Airjet')
insert into #Incident Values (3, 5000, 'Boeing')
insert into #Incident Values (4, -600, 'ExEarth')
insert into #Incident Values (5,8000,'SpaceX')
insert into #Incident Values (5, -8000, 'SpaceX')
Here is What I used but as I mentioned since ID number 4 has negative value as well I get "-C" for it which I do not want to.
Select CASE WHEN T1.CDamount < 0
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.Companyname
from #Incident AS T1
So I got this based on my knowledge of SQL and that works for my case.
Not sure if it is an smart way to go with but can be a good start for someone who is struggling with a Scenario like this:
;With CTE1 AS (
SELECT Count(*) AS Duplicate, T1.InvoiceID
From #Incident AS T1
Group by T1.InvoiceID
),
Main AS (
Select CASE WHEN T1.CDamount < 0 AND T2.Duplicate > 1
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.InvoiceID AS count,
T1.CDamount,
T1.Companyname
from #Incident AS T1
Join CTE1 AS T2 ON T1.InvoiceID = T2.InvoiceID
)
SELECT * FROM Main
Alternative solution without CTE, using ROW_NUMBER() function.
SELECT
CASE WHEN CDAmount < 0 AND RowID > 1
THEN InvoiceID + '-C'
ELSE InvoiceID
END AS ExternalID
, CDAmount
, CompanyName
FROM
(
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
) AS SourceTable
The trick is using ROW_NUMBER() function to generate a sequence which resets when InvoiceID changes. Here's the subquery and its result. Use CASE statement when CDAmount is negative and RowID greater than 1.
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
Subquery result:
+-----------+----------+-------------+-------+
| InvoiceID | CDAmount | CompanyName | RowID |
+-----------+----------+-------------+-------+
| 1 | 2500 | NASA | 1 |
| 1 | -2500 | NASA | 2 |
| 2 | 1600 | Airjet | 1 |
| 3 | 5000 | Boeing | 1 |
| 4 | -600 | ExEarth | 1 |
| 5 | 8000 | SpaceX | 1 |
| 5 | -8000 | SpaceX | 2 |
+-----------+----------+-------------+-------+

SQL Server running total with dbmail when total met or exceeded

I am able to get a running total via script--but what I can't seem to do is isolate the line where I have met or exceeded a certain value...
SELECT
column1,
(SELECT SUM(column1) FROM table WHERE column2 <= t1.column2)
FROM
table t1
Ultimately what I would want to do is create a trigger to send dbmail when the Sum of column1 meets or exceeds (n)...help me obi-wan
On SQL-Server 2008 you can use next solution:
DECLARE #TBL TABLE(id int, amount int);
INSERT INTO #TBL VALUES
(1, 100), (2, 100), (3, 60), (4, 200), (5, 100);
SELECT t1.ID, t1.amount, SUM(t2.amount) as CumTotal
FROM #TBL t1
CROSS APPLY (SELECT *
FROM #TBL
WHERE ID <= t1.id) t2
GROUP BY t1.ID, t1.amount
HAVING SUM(t1.amount) < 300
ORDER BY t1.ID
;
This is the result:
ID | amount | CumTotal
-: | -----: | -------:
1 | 100 | 100
2 | 100 | 200
3 | 60 | 260
dbfiddle here
Although this solution works well, it is not recommendable on terms of performance.
In this case the best option is to use a CURSOR.
DECLARE #CS table (id int, amount int, total int);
DECLARE #id int, #amount int;
DECLARE #CumSum int = 0;
DECLARE c CURSOR
LOCAL STATIC FORWARD_ONLY READ_ONLY
FOR SELECT ID, amount
FROM #TBL
ORDER BY [id];
OPEN c;
FETCH NEXT FROM c INTO #id, #amount
WHILE ##FETCH_STATUS = 0 AND #CumSum + #amount < 300
BEGIN
SET #CumSum = #CumSum + #amount;
INSERT #CS (id, amount, total)
SELECT #id, #amount, #CumSum;
FETCH NEXT FROM c INTO #id, #amount
END
CLOSE c;
DEALLOCATE c;
SELECT id, amount, total
FROM #CS
ORDER BY id;
GO
id | amount | total
-: | -----: | ----:
1 | 100 | 100
2 | 100 | 200
3 | 60 | 260
dbfiddle here
Next answer can be used on SQL-SERVER 2012 and above
You can use a cumulative sum using a WINDOW function and SUM() ROWS UNBOUNDED PRECEDING.
Have a look at MS docs.
DECLARE #TBL TABLE(id int, amount int);
INSERT INTO #TBL VALUES
(1, 100), (2, 100), (3, 60), (4, 200), (5, 100);
Next query returns a cumulative sum:
SELECT ID,
SUM(amount) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING) AS CumTotal
FROM #TBL
;
ID | CumTotal
-: | -------:
1 | 100
2 | 200
3 | 260
4 | 460
5 | 560
The problem is that you cannot stop it, you need to calculate all records, and then you can apply a where clause just to filter the records.
WITH CSum As
(
SELECT ID,
SUM(amount) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING) AS CumTotal
FROM #TBL
)
SELECT ID, CumTotal
FROM CSum
WHERE CumTotal < 300
ORDER BY ID
;
This is the final result:
ID | CumTotal
-: | -------:
1 | 100
2 | 200
3 | 260
dbfiddle here

If any data duplicate then output show 1 if not then show 0 in SQL Server

Table: emp
id | name | sal
----------------
1 | abc | 100
2 | ha | 200
1 | abc | 100
1 | abc | 100
1 | abc | 100
2 | ha | 200
2 | ha | 200
3 | hai | 400
Based on this data I want give data duplicate or not in the table for that status i out show output.
I tried like this:
select
count(*) as status
from
[Test].[dbo].[emp]
group by
[id], [name], [sal]
having
count(*) >= 1
order by
count(*) desc
I get this output:
status
4
3
1
I do not want get output like above way.
I want show output like below
Status
1
when data comes unique in table that time status shows : 0 values.
1 means duplicate data and o means unique records.please tell me how to get singe status values to achive this issue.
Try This,
SELECT Id, Name, Sal, COUNT(*) ,
CASE WHEN COUNT(*) > 1 THEN 1
ELSE 0
END Status
FROM
(
SELECT 1 Id, 'abc' Name, 100 Sal
UNION ALL
SELECT 2, 'ha', 200
UNION ALL
SELECT 1, 'abc', 100
UNION ALL
SELECT 1, 'abc', 100
UNION ALL
SELECT 1, 'abc', 100
UNION ALL
SELECT 2, 'ha', 200
UNION ALL
SELECT 2, 'ha', 200
UNION ALL
SELECT 3, 'hai', 400
) A
GROUP BY Id, Name, Sal
select case when c>0 then 1 else 0 end as status from(
select count(*) as c from
(select count(*) as cout from loss
group by loss_claim,loss_key
having count(*)>1)as a) b

The highest value from list-distinct

Can anyone help me with query, I have table
vendorid, agreementid, sales
12001 1004 700
5291 1004 20576
7596 1004 1908
45 103 345
41 103 9087
what is the goal ?
when agreemtneid >1 then show me data when sales is the highest
vendorid agreementid sales
5291 1004 20576
41 103 9087
Any ideas ?
Thx
Well you could try using a CTE and ROW_NUMBER something like
;WITH Vals AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY AgreementID ORDER BY Sales DESC) RowID
FROM MyTable
WHERE AgreementID > 1
)
SELECT *
FROM Vals
WHERE RowID = 1
This will avoid you returning multiple records with the same sale.
If that was OK you could try something like
SELECT *
FROM MyTable mt INNER JOIN
(
SELECT AgreementID, MAX(Sales) MaxSales
FROM MyTable
WHERE AgreementID > 1
) MaxVals ON mt.AgreementID = MaxVals.AgreementID AND mt.Sales = MaxVals.MaxSales
SELECT TOP 1 WITH TIES *
FROM MyTable
ORDER BY DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC)
Explanation
We break table MyTable into partitions by agreementid.
For each partition we construct a ranking or its rows.
If agreementid is greater than 1 ranking will be equal to ORDER BY sales DESC.
Otherwise ranking for every single row in partition will be the same: ORDER BY 0 DESC.
See how it looks like:
SELECT *
, SIGN (SIGN (agreementid - 2) + 1) * sales AS x
, DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC) AS rnk
FROM MyTable
+----------+-------------+-------+-------+-----+
| vendorid | agreementid | sales | x | rnk |
+----------|-------------|-------+-------+-----+
| 0 | 0 | 3 | 0 | 1 |
| -1 | 0 | 7 | 0 | 1 |
| 0 | 1 | 3 | 0 | 1 |
| -1 | 1 | 7 | 0 | 1 |
| 41 | 103 | 9087 | 9087 | 1 |
| 45 | 103 | 345 | 345 | 2 |
| 5291 | 1004 | 20576 | 20576 | 1 |
| 7596 | 1004 | 1908 | 1908 | 2 |
| 12001 | 1004 | 700 | 700 | 3 |
+----------+-------------+-------+-------+-----+
Then using TOP 1 WITH TIES construction we leave only rows where rnk equals 1.
you can try like this.
SELECT TOP 1 sales FROM MyTable WHERE agreemtneid > 1 ORDER BY sales DESC
I really do not know the business logic behind agreement_id > 1. It looks to me you want the max sales (with ties) by agreement id regardless of vendor_id.
First, lets create a simple sample database.
-- Sample table
create table #sales
(
vendor_id int,
agreement_id int,
sales_amt money
);
-- Sample data
insert into #sales values
(12001, 1004, 700),
(5291, 1004, 20576),
(7596, 1004, 1908),
(45, 103, 345),
(41, 103, 9087);
Second, let's solve this problem using a common table expression to get a result set that has each row paired with the max sales by agreement id.
The select statement just applies the business logic to filter the data to get your answer.
-- CTE = max sales for each agreement id
;
with cte_sales as
(
select
vendor_id,
agreement_id,
sales_amt,
max(sales_amt) OVER(PARTITION BY agreement_id) AS max_sales
from
#sales
)
-- Filter by your business logic
select * from cte_sales where sales_amt = max_sales and agreement_id > 1;
The screen shot below shows the exact result you wanted.

Resources