SQL detect changes in log stored in database - sql

I have db log of items prices. Sometimes ( usually every month ) new State with prices stored into db.
Each item has uniqueid. It doen't change from month to month.
I've build sample database:
http://sqlfiddle.com/#!6/0bc28/3
I need to detect item price changes. For example
If item1 price on 2013-01-01 was 122, on 2013-02-01 was 122, on 2013-03-01 was 124
Select should show 1 row
itemname = item1, oldPrice = 122, newPrice = 124, dateCHanged = 2013-03-01

This should work:
with x as (
select i.*, s.datecreated, row_number() over (partition by uniqueid order by datecreated) as rn
from items i
inner join states s on i.stateid = s.id
)
select x1.uniqueId, x1.price as oldPrice, x2.price as newPrice, x2.dateCreated as dateChanged
from x x1
inner join x x2 on x1.uniqueid = x2.uniqueid
--and datediff(month, x1.datecreated, x2.datecreated) = 1
and x1.rn - x2.rn = 1
and x1.price <> x2.price

Related

ORACLE SQL - multiple JOINs from same table

I have data related material transactions in one table and log history header data related to materials is in another table and detailed log history data in third table. I'm trying to get different status update dates matched to material table but I get duplicate rows for one material transaction
Original material transaction table:
ORDER_NO
MATERIAL
QTY
0001
MAT01
2
0002
MAT02  
5
Original Log History Header transaction table:
ORDER_NO
LOG_ID
0001
1001
0001
1002
Status code 1 refers to Opened and code 2 to Closed
Detailed Log History table:
LOG_ID
STATUS_CODE
DATE
1001
1
11/12/2021
1002
2  
15/12/2021
With following SQL query:
SELECT
TO_CHAR (m.order_no) order_no,
m.material,
a.date opened_date,
ab.closed_date
FROM MATERIAL_TRANSACTIONS m
INNER JOIN HISTORY_LOG t
ON m.ORDER_NO = t.ORDER_NO
INNER JOIN HISTORY_LOG_DETAILED a
ON t.LOG_ID = a.LOG_ID
AND a.STATUS_CODE = '1'
INNER JOIN HISTORY_LOG_DETAILED ab
ON t.LOG_ID = ab.LOG_ID
AND ab.STATUS_CODE = '2'
I get following result:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
0001
MAT01  
2
15/12/2021
And I would like to get the status dates to the same row as below:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
15/12/2021
I would appreciate all the help I can get and am very sorry if there already is topic for similar issue.
Your problem occurs because you join the history table, which holds 2 records for the order. You could flatten this if you use 2 inline tables that hold exactly 1 record.
with opened_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '1'
), closed_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '2'
)
select to_char (m.order_no) order_no,
m.material,
o.date opened_date,
c.date closed_date
from material_transactions m
join opened_dates o on m.order_no = o.order_no
join closed_dates c on m.order_no = c.order_no
;
Just an idea :
I joined HISTORY_LOG and HISTORY_LOG_DETAILED tables to get dates for specific status, and set as OPENED_DATE and CLOSED_DATE (if status 1 , then opened date is DATE column, otherwise set it as 01.01.0001)
After that grouped those records by ORDER_NO and maxed the date values to get actual OPENED_DATE and CLOSED_DATE .
Finally joined this subquery with MATERIAL_TRANSACTIONS table :
SELECT
TO_CHAR (M.ORDER_NO) ORDER_NO,
M.MATERIAL,
QTY,
L_T.OPENED_DATE,
L_T.CLOSED_DATE
FROM MATERIAL_TRANSACTIONS M
INNER JOIN
(
SELECT L.ORDER_NO ,
MAX( CASE WHEN LD.STATUS_CODE = 1 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) OPENED_DATE
MAX( CASE WHEN LD.STATUS_CODE = 2 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) CLOSED_DATE
FROM
HISTORY_LOG L
INNER JOIN HISTORY_LOG_DETAILED LD ON LD.LOG_ID = L.LOG_ID
GROUP BY L.ORDER_NO
) L_T on L_T.ORDER_NO = M.ORDER_NO
Note: I didnt test it. So there can be small syntax errors. Please check it and for better help add a fiddle so i can test my query

Query to transform data into opening-closing pair

I have a table with 2 columns i.e. recordNum(int) and qty (float) and data in that table is like following:
and I am trying to come up with query which can transform it to following:
I am using SQL Server 2012.
We have to prepare a opening-closing record number pair. The logic is as follows:
1st row: 1001 record num is opening record with quantity = 100
2nd row: 50 out of 100 quantity is closed by record num 1002
3rd row: remaining 50 quantity of 1001 is closed by 1003(which is of quantity 60)
4th row: after closing 50, 1003 will still have -10 quantity available.
As per request, I am adding my attempt to description:
select
t.openRecordNum
,t.closingRecordNum
,t.qty
from
(
select *
, openRecordNum = case when t.openQ>0 then min(t.recordNumber) over( order by seqId)
else
max(t.recordNumber) over( order by seqId)
end
, closingRecordNum = Max(t.recordNumber) over( order by seqId)
from
(
select
*
,openQ = sum(qty) over( order by seqId)
from Table_1
) t
) t
But I am nowhere close to desired result. I am getting following:

How to query SQLite to find sum, latest record, grouped by id, in between dates?

Items (itemId, itemName)
Logs (logId, itemId, qtyAdded, qtyRemoved, availableStock, transactionDate)
Sample Data for Items:
itemId itemName
1 item 1
2 item 2
Sample Data for Logs:
logid itemId qtyAdded qtyRemoved avlStock transDateTime
1 2 5405 0 5405 June 1 (4PM)
2 2 1000 0 6405 June 2 (5PM)
3 2 0 6000 405 June 3 (11PM)
I need to see all items from Items table and their SUM(qtyAdded), SUM(qtyRemoved), latest availableStock (there's an option for choosing the range of transactionDate but default gets all records). Order of date in final result does not matter.
Preferred result: (without date range)
itemName qtyAddedSum qtyRemovedSum avlStock
item 1 6405 6000 405
item 2 <nothing here yet>
With date Range between June 2 (8AM) and June 3 (11:01PM)
itemName qtyAddedSum qtyRemovedSum avlStock
item 1 1000 6000 405
item 2 <no transaction yet>
So as you can see, final result is grouped which makes almost all my previous query correct except my availableStock is always wrong. If I focus in the availableStock, I can't get the two sums.
you could use group by sum, and between
select itemName, sum(qtyAdded), sum(qtyRemoved), sum(avlStock)
from Items
left join Logs on logs.itemId = items.itemId
where transDateTime between '2017-06-02 08:00:00' and '2017-06-03 23:00:00'
group by itemId
or
If you need the last avlStock
select itemName, sum(qtyAdded), sum(qtyRemoved), tt.avlStock
from Items
left join Logs on logs.itemId = items.itemId
INNER JOIN (
select logid,avlStock
from logs
inner join (
select itemId, max(transDateTime) max_trans
from Logs
group by itemId
) t1 on logs.itemId = t1.ItemId and logs.transDateTime = t1.max_trans
) tt on tt.logId = Logs.itemId
where transDateTime between '2017-06-02 08:00:00' and '2017-06-03 23:00:00'
group by itemId
Okay, I tried both of these and they worked, can anyone confirm if these are already efficient or if there are some more efficient answers there.
SELECT * FROM Items LEFT JOIN
(
SELECT * FROM Logs LEFT JOIN
(
SELECT SUM(qtyAdd) AS QtyAdded, SUM(qtySub) AS QtyRemoved, availableStock AS Stock
FROM Logs WHERE transactionDate BETWEEN julianday('2017-07-18 21:10:40')
AND julianday('2017-07-18 21:12:00') GROUP BY itemId
)
ORDER BY transactionDate DESC
)
USING (itemId) GROUP BY itemName;
SELECT * FROM Items LEFT JOIN
(
SELECT * FROM Logs LEFT JOIN
(
SELECT SUM(qtyAdd) AS QtyAdded, SUM(qtySub) AS QtyRemoved, availableStock AS Stock
FROM Logs GROUP BY itemId
)
ORDER BY transactionDate DESC
)
USING (itemId) GROUP BY itemName;

How to select or skip to the next row if meets certain criteria

I really am not even sure which direction to go with this...
I'm trying to select a list of customers based on the following rules:
Select all rows from Customer where Ranking = 1,
OR if Ranking = 1 AND Type = Store then Rank 1 and return the row with Rank 2.
OR if the customer only has 1 row, return it even if the type = Store.
The Ranking is not assigned with a Rank statement in the query. Rather it is an actual column in the Customer table (populated by a stored proc that does the ranking).
Using the example below I'd want rows 1, 4, 6, and 10 returned.
Customer Table
RowID CustID Type Ranking
----- ------ ---- -------
1 9 Web 1
2 9 Catalog 2
3 9 Store 3
4 10 Store 1
5 11 Store 1
6 11 Web 2
7 12 Store 1
8 12 Web 2
9 12 Catalog 3
10 13 Web 1
I feel like this task is more difficult BECAUSE the Ranking is already done when the table is created! Any suggestions are most welcome!
You could try something like this (I haven't tested it!):
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE (c.Ranking = 1 AND c.Type != 'Store')
OR (c.Type = 'Store' AND Ranking = 2)
OR (c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2))
If the customer table is large, you might find that the query is a bit slow and something like this would be faster:
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Ranking = 1 AND c.Type != 'Store'
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 2
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2)
As with the other answer, I haven't done a lot of thorough testing, but here's what I'd look at. The idea here is to build a row_number over the data set prioritizing type:store to the top, and then using rank as the secondary sort condition.
select *
from (
select
rid = row_number() over (partition by CustID, order by case when type = 'Store' then 0 else 1 end, Rank desc),
rowid,
CustID,
Type,
Ranking
from customer)
where RID = 1
Try:
SELECT *
FROM Customer c
WHERE
-- There's only one row for this customer
(
SELECT COUNT(*)
FROM Customer
WHERE CustID = c.CustID
) = 1
-- There's a row with Ranking = 1 and Type = 'Store', so select Ranking = 2
OR (Ranking = 2 AND EXISTS (SELECT 1 FROM Customer WHERE CustID = c.CustID AND Ranking = 1 AND Type = 'Store'))
-- There's a row with Ranking = 1 that's not Type = 'Store'
OR (Ranking = 1 AND Type <> 'Store')

SQL gaps in dates

I am trying to find gaps in the a table based on a state code the tables look like this.
StateTable:
StateID (PK) | Code
--------------------
1 | AK
2 | AL
3 | AR
StateModel Table:
StateModelID | StateID | EfftiveDate | ExpirationDate
-------------------------------------------------------------------------
1 | 1 | 2012-06-28 00:00:00.000| 2012-08-02 23:59:59.000
2 | 1 | 2012-08-03 00:00:00.000| 2050-12-31 23:59:59.000
3 | 1 | 2055-01-01 00:00:00.000| 2075-12-31 23:59:59.000
The query I am using is the following:
Declare #gapMessage varchar(250)
SET #gapMessage = ''
select
#gapMessage = #gapMessage +
(Select StateTable.Code FROM StateTable where t1.StateID = StateTable.StateID)
+ ' Row ' +CAST(t1.StateModelID as varchar(6))+' has a gap with '+
CAST(t2.StateModelID as varchar(6))+ CHAR(10)
from StateModel t1
inner join StateModel t2
on
t1.StateID = t2.StateID
and DATEADD(ss, 1,t1.ExpirationDate) < t2.EffectiveDate
and t1.EffectiveDate < t2.EffectiveDate
if(#gapMessage != '')
begin
Print 'States with a gap problem'
PRINT #gapMessage
end
else
begin
PRINT 'No States with a gap problem'
end
But with the above table example I get the following output:
States with a gap problem
AK Row 1 has a gap with 3
AK Row 2 has a gap with 3
Is there anyway to restructure my query so that the gap between 1 and 3 does not display because there is not a gap between 1 and 2?
I am using MS sql server 2008
Thanks
WITH
sequenced AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY StateID ORDER BY EfftiveDate) AS SequenceID,
*
FROM
StateModel
)
SELECT
*
FROM
sequenced AS a
INNER JOIN
sequenced AS b
ON a.StateID = b.StateID
AND a.SequenceID = b.SequenceID - 1
WHERE
a.ExpirationDate < DATEADD(second, -1, b.EfftiveDate)
To make this as effective as possible, also add an index on (StateID, EfftiveDate)
I wanted to just give credit to MatBailie, but don't have the points to do it yet, so I thought I would help out anyone else looking for a similar solution that may want to take it a step further like I needed to. I have changed my application of his code (which involves member enrollment) to the same language as the example here.
In my case, I needed these things:
I have two similar tables that I need to develop into one total table. In this example, let's make the tables like this: SomeStates + OtherStates = UpdatedTable. These are UNIONED in the AS clause.
I didn't want to remove any rows due to gaps, but I wanted to flag them on the StateID level. This is added as an additional column 'StateID_GapFlag'.
I also wanted to add a column to hold the oldest or MIN(EffectiveDate). This would be used in later calculations of SUM(period) to get a total duration, excluding gaps. This is the column 'MIN_EffectiveDate'.
;WITH sequenced
( SequenceID
,EffectiveDate
,ExpirationDate)
AS
(select
ROW_NUMBER() OVER (PARTITION BY StateID ORDER by EffectiveDate) as SequenceID,
* from (select EffectiveDate, ExpirationDate from SomeStates
UNION ALL
(select EffectiveDate, ExpirationDate from OtherStates)
) StateModel
where
EffectiveDate > 'filter'
)
Select DISTINCT
IJ1.[MIN_EffectiveDate]
,coalesce(IJ2.GapFlag,'') as [MemberEnrollmentGapFlag]
,EffectiveDate
,ExpirationDate
into UpdatedTable
from sequenced seq
inner join
(select StateID, min(EffectiveDate) as 'MIN_EffectiveDate'
from sequenced
group by StateID
) IJ1
on seq.member# = IJ1.member
left join
(select a.member#, 'GAP' as 'StateID_GapFlag'
from sequenced a
inner join
sequenced b
on a.StateID = b.StateID
and a.SequenceID = (b.sequenceID - 1)
where a.ExpirationDate < DATEADD(day, -1, b.EffectiveDate)
) LJ2
on seq.StateID = LJ2.StateID
You could use ROW_NUMBER to provide an ordering of stateModel's for each state, then check that the second difference for consecutive rows doesn't exceed 1. Something like:
;WITH Models (StateModelID, StateID, Effective, Expiration, RowOrder) AS (
SELECT StateModelID, StateID, EffectiveDate, ExpirationDate,
ROW_NUMBER() OVER (PARTITION BY StateID, ORDER BY EffectiveDate)
FROM StateModel
)
SELECT F.StateModelId, S.StateModelId
FROM Models F
CROSS APPLY (
SELECT M.StateModelId
FROM Models M
WHERE M.RowOrder = F.RowOrder + 1
AND M.StateId = F.StateId
AND DATEDIFF(SECOND, F.Expiration, M.Effective) > 1
) S
This will get you the state model IDs of the rows with gaps, which you can format how you wish.