SQL difference between Multiple Rows having the same ID - sql

SQL Sever 2012
Raw Data
ID VAL Time
+---+----+---------------------+
| 2 | 1 | 2015-05-09 12:54:39 |
| 3 | 10 | 2015-05-09 12:54:39 |
| 2 | 1 | 2015-05-09 12:56:39 |
| 3 | 10 | 2015-05-09 12:56:39 |
| 2 | 5 | 2015-05-09 13:48:30 |
| 3 | 16 | 2015-05-09 13:48:30 |
| 2 | 7 | 2015-05-09 15:01:09 |
| 3 | 20 | 2015-05-09 15:01:09 |
+---+----+---------------------+
I have a table where VAL is increasing forever in time. I want to manipulate the data to show how much VAL is increasing for each ID over time. So Val at Time2 - Val at Time1
Ideal Result:
ID VALI Time
+---+----+---------------------+
| 2 | 0 | 2015-05-09 12:56:39 |
| 3 | 0 | 2015-05-09 12:56:39 |
| 2 | 4 | 2015-05-09 13:48:30 |
| 3 | 6 | 2015-05-09 13:48:30 |
| 2 | 2 | 2015-05-09 15:01:09 |
| 3 | 4 | 2015-05-09 15:01:09 |
+---+----+---------------------+
Code so far:
select
t1.Time,t1.[ID],t2.[VAL]-t1.[VAL] AS [ValI]
from #tempTable t1
inner join #tempTable t2 ON t1.[ID]=t2.[ID]
AND t1.[Time]<t2.[Time]
I need to calculate the difference between the current timestamp and ONLY the Time right before current timestamp not all timestamps before the current timestamp. As of now I get a lot of repeating values when VAL did not change.

You can use this.
DECLARE #MyTable TABLE (ID INT, VAL INT, [Time] DATETIME)
INSERT INTO #MyTable VALUES
(2, 1 ,'2015-05-09 12:54:39'),
(3, 10 ,'2015-05-09 12:54:39'),
(2, 1 ,'2015-05-09 12:56:39'),
(3, 10 ,'2015-05-09 12:56:39'),
(2, 5 ,'2015-05-09 13:48:30'),
(3, 16 ,'2015-05-09 13:48:30'),
(2, 7 ,'2015-05-09 15:01:09'),
(3, 20 ,'2015-05-09 15:01:09')
;WITH CTE AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY [Time]) RN FROM #MyTable
)
SELECT T1.ID, T2.VAL - T1.VAL AS VALI, T2.Time FROM CTE T1
INNER JOIN CTE T2 ON T1.ID = T2.ID AND T1.RN = T2.RN - 1
ORDER BY T1.[Time], T1.ID
Result:
ID VALI Time
----------- ----------- -----------------------
2 0 2015-05-09 12:56:39.000
3 0 2015-05-09 12:56:39.000
2 4 2015-05-09 13:48:30.000
3 6 2015-05-09 13:48:30.000
2 2 2015-05-09 15:01:09.000
3 4 2015-05-09 15:01:09.000

Here this should work:
select id, time, val-prevval val1 from (
select * , lag(val, 1, 0) over(partition by id order by val, time) prevVal from #Temp)A
order by time

You could first put a Rank on your #tempTable ordered by Time descending, and partitioned by ID.
Then your join becomes this:
select
t1.Time,
t1.[ID],
t1.[VAL] - t2.[VAL] AS [ValI]
from #tempTable t1
inner join #tempTable t2 ON t1.[ID] = t2.[ID]
AND t2.Rank = (t1.Rank + 1)

LAG() became available in SQL 2012. This allows you to take the current row's val and subtract the val from the previous row, grouped by the id and sorted by Time. That will return NULL for the first two rows, since they don't have a previous record to compare to. You can exclude them by placing the query in a sub-select then applying a WHERE valDiff IS NULL, or you can default the valDiff using the third argument of LAG() > LAG(Val,1,0) to default the first two rows to 0.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE t1 ( ID int, VAL int, [Time] datetime) ;
INSERT INTO t1 ( ID, Val, [Time] )
VALUES
( 2, 1 , '2015-05-09 12:54:39')
, ( 3, 10, '2015-05-09 12:54:39')
, ( 2, 1 , '2015-05-09 12:56:39')
, ( 3, 10, '2015-05-09 12:56:39')
, ( 2, 5 , '2015-05-09 13:48:30')
, ( 3, 16, '2015-05-09 13:48:30')
, ( 2, 7 , '2015-05-09 15:01:09')
, ( 3, 20, '2015-05-09 15:01:09')
;
Query 1:
SELECT s1.ID
, s1.ValDiff
, FORMAT(s1.[Time], 'yyyy-MM-dd hh:mm:ss') AS fTime
FROM (
SELECT ID
, Val - LAG(Val,1) OVER ( PARTITION BY ID ORDER BY [Time],ID ) AS ValDiff
, [Time]
FROM t1
) s1
WHERE s1.valDiff IS NOT NULL
ORDER BY s1.[Time],s1.ID
Results:
| ID | ValI | fTime |
|----|---------|---------------------|
| 2 | 0 | 2015-05-09 12:56:39 |
| 3 | 0 | 2015-05-09 12:56:39 |
| 2 | 4 | 2015-05-09 01:48:30 |
| 3 | 6 | 2015-05-09 01:48:30 |
| 2 | 2 | 2015-05-09 03:01:09 |
| 3 | 4 | 2015-05-09 03:01:09 |

If you have LAG
DEMO
SELECT
id
, val - LAG(val, 1) OVER (PARTITION BY id ORDER BY time ASC) AS VALI
, time
FROM #TempTable
ORDER BY time ASC, ID ASC

Related

How can I group and sum data by day using T-SQL?

I have a table like this
datex | countx |
---------------------
2022-12-04 | 1 |
2022-12-03 | 2 |
2022-12-02 | 1 |
2022-12-01 | 3 |
2022-11-30 | 1 |
2022-11-29 | 1 |
2022-11-28 | 1 |
2022-11-27 | 2 |
I want to get this output
datex | count_sum |
-------------------------
2022-12 | 4 |
2022-12-01 | 3 |
2022-11 | 5 |
So far I tried some group by clause but I didn't succeed.
Here is test code
declare #test table
(
datex date,
countx int
)
insert into #test
values ('2022-12-04', 1),
('2022-12-03', 2),
('2022-12-02', 1),
('2022-12-01', 3),
('2022-11-30', 1),
('2022-11-29', 1),
('2022-11-28', 1),
('2022-11-27', 2)
You may use a case expression to check if the date is the first day of the month then aggregate as the following:
with check_date as
(
select case
when Day([date])=1
Then Cast([date] as varchar(10))
else Format([date], 'yyyy-MM')
end As dt,
[count]
from table_name
)
select dt, sum([count]) as count_sum
from check_date
group by dt
order by dt desc
See demo
As I understood you want to "extract" year and month from your datex column and count it. I think you can use below SQL:
with cte as(
select
concat(year(datex), '-', month(datex)) as datex,
countx
from test
where not datex in ( '2022-12-01' )
)
select
datex,
count(1)
from cte
group by datex;
Result:
date | count_sum |
-------------------------
2022-12 | 3 |
2022-11 | 4 |
Here is Fiddle.

Replace value in column based on another column

I have the following table:
+----+--------+------------+----------------------+
| ID | Name | To_Replace | Replaced |
+----+--------+------------+----------------------+
| 1 | Fruits | 1 | Fruits |
| 2 | Apple | 1-2 | Fruits-Apple |
| 3 | Citrus | 1-3 | Fruits-Citrus |
| 4 | Orange | 1-3-4 | Fruits-Citrus-Orange |
| 5 | Empire | 1-2-5 | Fruits-Apple-Empire |
| 6 | Fuji | 1-2-6 | Fruits-Apple-Fuji |
+----+--------+------------+----------------------+
How can I create the column Replaced ? I thought of creating 10 maximum columns (I know there are no more than 10 nested levels) and fetch the ID from every substring split by '-', and then concatenating them if not null into Replaced, but I think there is a simpler solution.
While what you ask for is technically feasible (probably using a recursive query or a tally), I will take a different stance and suggest that you fix your data model instead.
You should not be storing multiple values as a delimited list in a single database column. This defeats the purpose of a relational database, and makes simple things both unnecessarily complicated and inefficient.
Instead, you should have a separate table to store that data, which each replacement id on a separate row, and possibly a column that indicates the sequence of each element in the list.
For your sample data, this would look like:
id replace_id seq
1 1 1
2 1 1
2 2 2
3 1 1
3 3 2
4 1 1
4 3 2
4 4 3
5 1 1
5 2 2
5 5 3
6 1 1
6 2 2
6 6 3
Now you can efficiently generate the expected result with either a join, a subquery, or a lateral join. Assuming that your table is called mytable and that the mapping table is mymapping, the lateral join solution would be:
select t.*, r.*
from mytable t
outer apply (
select string_agg(t1.name) within group(order by m.seq) replaced
from mymapping m
inner join mytable t1 on t1.id = m.replace_id
where m.id = t.id
) x
You can try something like this:
DECLARE #Data TABLE ( ID INT, [Name] VARCHAR(10), To_Replace VARCHAR(10) );
INSERT INTO #Data ( ID, [Name], To_Replace ) VALUES
( 1, 'Fruits', '1' ),
( 2, 'Apple', '1-2' ),
( 3, 'Citrus', '1-3' ),
( 4, 'Orange', '1-3-4' ),
( 5, 'Empire', '1-2-5' ),
( 6, 'Fuji', '1-2-6' );
SELECT
*
FROM #Data AS d
OUTER APPLY (
SELECT STRING_AGG ( [Name], '-' ) AS Replaced FROM #Data WHERE ID IN (
SELECT CAST ( [value] AS INT ) FROM STRING_SPLIT ( d.To_Replace, '-' )
)
) List
ORDER BY ID;
Returns
+----+--------+------------+----------------------+
| ID | Name | To_Replace | Replaced |
+----+--------+------------+----------------------+
| 1 | Fruits | 1 | Fruits |
| 2 | Apple | 1-2 | Fruits-Apple |
| 3 | Citrus | 1-3 | Fruits-Citrus |
| 4 | Orange | 1-3-4 | Fruits-Citrus-Orange |
| 5 | Empire | 1-2-5 | Fruits-Apple-Empire |
| 6 | Fuji | 1-2-6 | Fruits-Apple-Fuji |
+----+--------+------------+----------------------+
UPDATE
Ensure the id list order is maintained when aggregating names.
DECLARE #Data TABLE ( ID INT, [Name] VARCHAR(10), To_Replace VARCHAR(10) );
INSERT INTO #Data ( ID, [Name], To_Replace ) VALUES
( 1, 'Fruits', '1' ),
( 2, 'Apple', '1-2' ),
( 3, 'Citrus', '1-3' ),
( 4, 'Orange', '1-3-4' ),
( 5, 'Empire', '1-2-5' ),
( 6, 'Fuji', '1-2-6' ),
( 7, 'Test', '6-2-7' );
SELECT
*
FROM #Data AS d
OUTER APPLY (
SELECT STRING_AGG ( [Name], '-' ) AS Replaced FROM (
SELECT TOP 100 PERCENT
Names.[Name]
FROM ( SELECT CAST ( '<ids><id>' + REPLACE ( d.To_Replace, '-', '</id><id>' ) + '</id></ids>' AS XML ) AS id_list ) AS xIds
CROSS APPLY (
SELECT
x.f.value('.', 'INT' ) AS name_id,
ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ) ) AS row_id
FROM xIds.id_list.nodes('//ids/id') x(f)
) AS ids
INNER JOIN #Data AS Names ON Names.ID = ids.name_id
ORDER BY row_id
) AS x
) List
ORDER BY ID;
Returns
+----+--------+------------+----------------------+
| ID | Name | To_Replace | Replaced |
+----+--------+------------+----------------------+
| 1 | Fruits | 1 | Fruits |
| 2 | Apple | 1-2 | Fruits-Apple |
| 3 | Citrus | 1-3 | Fruits-Citrus |
| 4 | Orange | 1-3-4 | Fruits-Citrus-Orange |
| 5 | Empire | 1-2-5 | Fruits-Apple-Empire |
| 6 | Fuji | 1-2-6 | Fruits-Apple-Fuji |
| 7 | Test | 6-2-7 | Fuji-Apple-Test |
+----+--------+------------+----------------------+
I'm sure there's optimization that can be done here, but this solution seems to guarantee the list order is kept.

How to show values that has a suitable pair in sql server

I have a table like this
---------------------------------------------
Id | TransactionId | Amount | Account| crdr |
---------------------------------------------
1 | 1 | 100 | 11111 | 1 |
2 | 2 | 130 | 13133 | 1 |
3 | 1 | 100 | 12111 | 2 |
4 | 2 | 130 | 13233 | 2 |
5 | 2 | 110 | 12122 | 1 |
What I need to display is, show these records as pairs (I have grouped them by transactionid, Amount).
SELECT TransactionId ,Amount , Account, CrDr
FROM Table1 ORDER BY TransactionId ASC,Amount ASC, CrDr ASC
But I want to ignore the records which dont have a pair, as a Example for this above records set result should be like this
---------------------------------------------
TransactionId | Amount | Account| crdr |
---------------------------------------------
1 | 100 | 11111 | 1 |
1 | 100 | 12111 | 2 |
2 | 130 | 13133 | 1 |
2 | 130 | 13233 | 2 |
Can someone suggest a solution for this.
You could use a correlated subquery with a NOT EXISTS condition to ensure that another record exists with the same TransactionId and Amount:
SELECT TransactionId ,Amount , Account, CrDr
FROM Table1 t
WHERE EXISTS (
SELECT 1
FROM Table1 t1
WHERE
t.id <> t1.id
AND t.TransactionId = t1.TransactionId
AND t.Amount = t1.Amount
)
ORDER BY TransactionId ASC,Amount ASC, CrDr ASC
Demo on DB Fiddle:
TransactionId | Amount | Account | CrDr
------------: | -----: | ------: | ---:
1 | 100 | 11111 | 1
1 | 100 | 12111 | 2
2 | 130 | 13133 | 1
2 | 130 | 13233 | 2
Try this:
DECLARE #DataSource TABLE
(
[Id] INT
,[TransactionId] INT
,[Amount] INT
,[Account] INT
,[crdr] INT
);
INSERT INTO #DataSource ([Id], [TransactionId], [Amount], [Account], [crdr])
VALUES (1, 1, 100, 11111, 1)
,(2, 2, 130, 13133, 1)
,(3, 1, 100, 12111, 2)
,(4, 2, 130, 13233, 2)
,(5, 2, 110, 12122, 1);
WITH DataSource AS
(
SELECT *
,COUNT(*) OVER (PARTITION BY [TransactionId], [Amount]) AS [Count]
FROM #DataSource
)
SELECT *
FROM DataSource
WHERE [Count] = 2
ORDER BY TransactionId ASC,Amount ASC, CrDr ASC;
Try this: This this simplest solution of this problem
;with cte
as
(
select TransactionId, Amount
from Table1
group by TransactionId, Amount
having count(*) > 1
)
select *
from Table1 t
inner join cte c on t.TransactionId = c.TransactionId and t.Amount = c.Amount

SQL Server : Making 2 rows value into 1 column and joining it, while using a where clause

I am using an SQL Server database and have these following tables
Table "Data"
------------------
| Id | data_name |
------------------
| 1 |Data 1 |
| 2 |Data 2 |
| 3 |Data 3 |
| 4 |Data 4 |
| 5 |Data 5 |
------------------
and Table "Value_data"
--------------------------------------------------------------------------------------------------------------
| Id | data_id | date | col_1_type | col_1_name | col_1_value | col_2_type | col_2_name | col_2_value |
--------------------------------------------------------------------------------------------------------------
| 1 | 1 | 2017-01-01 | A | Alpha | 12 | B | Beta | 23 |
| 2 | 1 | 2017-02-01 | A | Alpha | 32 | B | Beta | 42 |
---------------------------------------------------------------------------------------------------------------
And i want to make result like so
-----------------------------------------------------------------
|value_id | data_id | data_name | date | A-Alpha | B-Beta |
-----------------------------------------------------------------
|1 | 1 | Data 1 | 2017-01-01 | 12 | 23 |
|2 | 1 | Data 1 | 2017-02-01 | 32 | 42 |
-----------------------------------------------------------------
I've search multiple times for solutions, i've tried using Pivot for example, but it wont work well with the data that i'm using with the joining tables, anyone had a solution with the same case?
You can use this.
DECLARE #Data TABLE ( Id INT, data_name VARCHAR(10) )
INSERT INTO #Data VALUES
( 1 ,'Data 1'),
( 2 ,'Data 2'),
( 3 ,'Data 3'),
( 4 ,'Data 4'),
( 5 ,'Data 5')
DECLARE #Value_data TABLE (Id INT, data_id INT, [date] DATE, col_1_type VARCHAR(10), col_1_name VARCHAR(10), col_1_value INT, col_2_type VARCHAR(10), col_2_name VARCHAR(10), col_2_value INT)
INSERT INTO #Value_data VALUES
( 1, 1, '2017-01-01','A','Alpha','12','B','Beta','23'),
( 2, 1, '2017-02-01','A','Alpha','32','B','Beta','42')
;WITH CTE AS (
select vd.Id value_id
, vd.data_id
, d.data_name
, vd.[date]
, vd.col_1_type + '-' +vd.col_1_name Col1
, vd.col_1_value
, vd.col_2_type + '-' +vd.col_2_name Col2
, vd.col_2_value
from #Value_data vd
inner join #Data d on vd.data_id = d.Id
)
SELECT * FROM CTE
PIVOT( MAX(col_1_value) FOR Col1 IN ([A-Alpha])) PVT_A
PIVOT( MAX(col_2_value) FOR Col2 IN ([B-Beta])) PVT_B
Result:
value_id data_id data_name date A-Alpha B-Beta
----------- ----------- ---------- ---------- ----------- -----------
1 1 Data 1 2017-01-01 12 23
2 1 Data 1 2017-02-01 32 42
This looks like a basic left join
create table data( Id int, data_name varchar(20))
insert into data values
( 1 ,'Data 1'),
( 2 ,'Data 2'),
( 3 ,'Data 3'),
( 4 ,'Data 4'),
( 5 ,'Data 5')
create table Value_data( Id int, data_id int, dt smalldatetime, col_1_type varchar(1), col_1_name varchar(5), col_1_value int, col_2_type varchar(1), col_2_name varchar(5), col_2_value int)
insert into value_data values
( 1 , 1 , '2017-01-01' , 'A' , 'Alpha' , 12 , 'B' , 'Beta' , 23 ),
( 2 , 1 , '2017-02-01' , 'A' , 'Alpha' , 32 , 'B' , 'Beta' , 42 )
select d.id,vd.id,d.data_name,vd.dt,
vd.col_1_value as 'Alpha',vd.col_2_value as 'Beta'
from data d
left join value_data vd on d.id = vd.data_id
id id data_name dt Alpha Beta
----------- ----------- -------------------- ----------------------- ----------- -----------
1 1 Data 1 2017-01-01 00:00:00 12 23
1 2 Data 1 2017-02-01 00:00:00 32 42
2 NULL Data 2 NULL NULL NULL
3 NULL Data 3 NULL NULL NULL
4 NULL Data 4 NULL NULL NULL
5 NULL Data 5 NULL NULL NULL
(6 row(s) affected)
SELECT
a.id,
a.data_id,
b.data_name,
a.date1,
a.col_1_value AS alpha,
a.col_2_value AS beta
FROM table1 a WITH (NOLOCK)
INNER JOIN table2 b WITH (NOLOCK)
ON a.data_id = b.id

Select N Rows With Mixed Values

I have a table with columns like
insertTimeStamp, port, data
1 , 20 , 'aaa'
2 , 20 , 'aba'
3 , 20 , '3aa'
4 , 20 , 'aab'
2 , 21 , 'aza'
5 , 21 , 'aha'
8 , 21 , 'aaa'
15 , 22 , '2aa'
Now I need N Rows (Say 4) from that table, ordered asc by insertTimeStamp.
But if possible, I want to get them from different ports.
So the result should be:
1 , 20 , 'aaa'
2 , 20 , 'aba'
2 , 21 , 'aza'
15 , 22 , '2aa'
If there are not enough different values in port I would like select the remaining ones with the lowest insertTimeStamp.
SQL Fiddle Demo
As you can see I create a group_id so group_id = 1 will be the smaller TimeStamp for each port
The second field is time_id so in the ORDER BY after I select all the 1 bring all the 2,3,4 for any port.
SELECT *
FROM (
SELECT *,
row_number() over (partition by "port" order by "insertTimeStamp") group_id,
row_number() over (order by "insertTimeStamp") time_id
FROM Table1 T
) as T
ORDER BY CASE
WHEN group_id = 1 THEN group_id
ELSE time_id
END
LIMIT 4
OUTPUT
| insertTimeStamp | port | data | group_id | time_id |
|-----------------|------|------|----------|---------|
| 1 | 20 | aaa | 1 | 1 |
| 2 | 21 | aza | 1 | 3 |
| 15 | 22 | 2aa | 1 | 8 |
| 2 | 20 | aba | 2 | 2 |
Use row_number():
select *
from (
select insertTimeStamp, port, data
from (
select *, row_number() over (partition by port order by insertTimeStamp) rn
from a_table
) alias
order by rn, insertTimeStamp
limit 4
) alias
order by 1, 2;
inserttimestamp | port | data
-----------------+------+------
1 | 20 | aaa
2 | 20 | aba
2 | 21 | aza
15 | 22 | 2aa
(4 rows)
SqlFiddle