Merging records with consecutive dates - ms-access

I have the following problem in a Database using Access 2007 as front end and SQL2005 as back-end.
In Table A I have the following data structure
Table A
ID Date Supplier_ID
1 10/22/2009 1
2 10/23/2009 1
3 10/24/2009 2
4 10/25/2009 2
5 10/26/2009 1
I need to merge values that have consecutive dates and the same Supplier_ID, increment a new column (Days) for each of these consecutive records and them write the data into Table B, so that I have
Table B
ID Date Supplier_ID Days
1 10/22/2009 1 2
2 10/24/2009 2 2
3 10/26/2009 1 1
Only consecutive days should be merged. Hence ID 5 in table A has is added to table B as a new record. It's been some time since I've been working with Access VBA and wondered what the right approach to this would be.

Here's some ANSI-92 Query Mode syntax SQL DDL and DML to recreate the table and a possible solution:
CREATE TABLE TableA
(
ID INTEGER NOT NULL UNIQUE,
[Date] DATETIME NOT NULL,
Supplier_ID INTEGER NOT NULL
);
INSERT INTO TableA (ID, [Date], Supplier_ID)
SELECT DT1.ID, DT1.[Date], DT1.Supplier_ID
FROM (
SELECT DISTINCT 1 AS ID, '2009-10-22 00:00:00' AS [Date], 1 AS Supplier_ID FROM Customers
UNION ALL
SELECT DISTINCT 2, '2009-10-23 00:00:00', 1 FROM Customers
UNION ALL
SELECT DISTINCT 3, '2009-10-24 00:00:00', 2 FROM Customers
UNION ALL
SELECT DISTINCT 4, '2009-10-25 00:00:00', 2 FROM Customers
UNION ALL
SELECT DISTINCT 5, '2009-10-26 00:00:00', 1 FROM Customers
) AS DT1;
CREATE VIEW TableA_StartDates (Supplier_ID, start_date)
AS
SELECT T1.Supplier_ID, T1.[Date]
FROM TableA AS T1
WHERE NOT EXISTS (
SELECT *
FROM TableA AS T2
WHERE T2.Supplier_ID = T1.Supplier_ID
AND DATEADD('D', -1, T1.[Date]) = T2.[Date]
);
CREATE VIEW TableA_EndDates (Supplier_ID, end_date)
AS
SELECT T3.Supplier_ID, T3.[Date]
FROM TableA AS T3
WHERE NOT EXISTS (
SELECT *
FROM TableA AS T4
WHERE T4.Supplier_ID = T3.Supplier_ID
AND DATEADD('D', 1, T3.[Date]) = T4.[Date]
);
CREATE VIEW TableA_Periods (Supplier_ID, start_date, end_date)
AS
SELECT DISTINCT T5.Supplier_ID,
(
SELECT MAX(S1.start_date)
FROM TableA_StartDates AS S1
WHERE S1.Supplier_ID = T5.Supplier_ID
AND S1.start_date <= T5.[Date]
),
(
SELECT MIN(E1.end_date)
FROM TableA_EndDates AS E1
WHERE E1.Supplier_ID = T5.Supplier_ID
AND T5.[Date] <= E1.end_date
)
FROM TableA AS T5;
SELECT P1.Supplier_ID, P1.start_date, P1.end_date,
DATEDIFF('D', P1.start_date, P1.end_date) + 1 AS Days
FROM TableA_Periods AS P1;

You have a lot of business rules with some unique assumptions. Eg TableA is never empty, TableB is always empty before you run this
Regardless, the code below will work using your sample data:
Dim rs As Recordset
Dim dbs As Database, qdf As QueryDef, strSQL As String
Set dbs = CurrentDb
Dim Supplier_ID As Integer
Dim Days As Integer
Dim FirstDate As Date
Set qdf = dbs.CreateQueryDef("", "select [Date], Supplier_ID from tableA order by [date] ")
Set rs = qdf.OpenRecordset()
Supplier_ID = rs!Supplier_ID
FirstDate = rs!Date
While Not rs.EOF
If rs!Supplier_ID <> Supplier_ID Then
If Supplier_ID <> 0 Then
' we don't want to insert the first time we run through this, so we check if Supplier_ID is not zero
dbs.Execute ("insert into tableB ([Date], Supplier_ID, Days) select #" + Format(FirstDate, "dd-mmm-yyyy") + "#, " + Str(Supplier_ID) + ", " + Str(Days))
Supplier_ID = rs!Supplier_ID
FirstDate = rs!Date
Days = 0
End If
End If
Days = Days + 1
rs.MoveNext
Wend
dbs.Execute ("insert into tableB ([Date], Supplier_ID, Days) select #" + Format(FirstDate, "dd-mmm-yyyy") + "#, " + Str(Supplier_ID) + ", " + Str(Days))
While you can do it, Access (and generally SQL) isn't the best for doing row by row comparisons. Even though you can use cursors or code like the example above, maintenance can be a challenge!

I don't think this can be done in a single SQL, so you'll have to write some code. My first idea would be to make some kind of dictionary (Key = SupplierID, Value = (FirstDate, LastDate)) and use an algorithm that iterates through the data in Table A and fills the dictionary, kind of like this (pseudo-code):
records = recordset: SELECT * FROM Table A ORDER BY Date
Do Until records.EOF
If dictionary contains records!SupplierID
If dictionaryEntry.LastDate = records!Date - 1
dictionaryEntry.LastDate = records!Date
Else
Make Table B Entry (Days = LastDate - FirstDate) and remove dictionary entry
End If
Else
Create dictionary entry for this Supplier with LastDate = FirstDate = records!Date
End If
records.MoveNext
Loop
records.Close
Make Table B Entries for all remaining entries in the dictionary
For the dictionary, you could use a Scripting.Dictionary with the value being a 2-Element-Array.

Related

How to delete only a single row from 2 duplicate rows?

I have 2 duplicate rows in the table,I want to delete only 1 from that and keep the other row.how can I do that?
The PostGres code might be a little different, but here's an example from TSQL that does it with a CTE:
; WITH duplicates
AS (
SELECT ServerName ,
ProcessName ,
DateCreated ,
RowRank = ROW_NUMBER() OVER(PARTITION BY ServerName, ProcessName, DateCreated ORDER BY 1)
FROM dbo.ErrorLog
)
DELETE e
FROM dbo.ErrorLog e
JOIN duplicates d
ON d.ServerName = e.ServerName
AND d.ProcessName = e.ProcessName
AND d.DateCreated = e.DateCreated
AND d.RowRank <> 1

Split logic in SQL

I am looking for an sql query which would take the following table data as input
ID Start date end date
ID1 01.01.2016 31.12.2016
ID2 01.02.2016 30.06.2016
ID3 01.10.2016 31.10.2016
ID4 01.02.2016 31.07.2016
and gives the following output
ID1 01.01.2016 31.01.2016
ID4 01.02.2016 31.07.2016
ID1 01.08.2016 30.09.2016
ID3 01.10.2016 31.10.2016
ID1 01.11.2016 31.12.2016
please see that ID1 is split for only one month as ID2,ID3 and ID4 has an overlap with ID1.
The idea is the latest date range gets the preference. if you see the output ID2 is completly rejected as it is overwritten by ID4.
please can you post the hints of the query.
I have thought up some logic for doing this. I can give the syntax for sql server (but you would have to find the syntax for mysql). Replace [Table Name] with the name of your table in the queries that follow.
Get the maximum end date from your table and store in a variable:
declare #max_end_date datetime
set #max_end_date = (select max([end date]) from [Table Name])
Collect possible start dates into a temporary table, adding in a row number and two blank columns (' ' as id, 0 as dupe):
select
row_number()over(order by start_date) as row,
start_date,
'' as id,
0 as dupe
into #temp
from
(select [start date] as start_date
from [Table Name]
union
select dateadd(day,1,[end date])
from [Table Name]
where [end date] <> #max_end_date )
group by start_date
Update each row of the temporary table with the plan effective on that start date:
update #temp
set #temp.id = c.id
from #temp
left outer join
(select a.start_date, max(b.id) as id
from #temp a
inner join [Table Name] b
on a.start_date between b.[start date] and b.[end date]
group by a.start_date) c
on #temp.start_date = c.start_date
Update the temporary table with a flag to mark out 'duplicate' rows:
update #temp
set #temp.dupe = 1
from #temp
inner join #temp b
on a.row = b.row + 1
and a.id = b.id
Delete the duplicate rows:
delete from #temp where dupe = 1
Update the row column:
update #temp
set row = row_number()over(order by start_date)
Join this table to itself to create the table you are after:
select
a.id as id,
a.start_date as start_date,
isnull(dateadd(day,-1,b.start_date),#max_end_date) as end_date
from #temp a
left outer join #temp b
on a.row = b.row - 1

MySQL rows with maximum and specific values

I have to write a query to retrieve data from a table, using the selected date and selected category. I have written a query and it does not give correct data as expected. It should query only rows with maximum actiondate, if the action column has the value 'AD'.
INSERT INTO goldstockvaluation SELECT sh.stockid, sh.description, sh.branch, sh.grossweight, sh.pureweight, sh.purity, sh.goldcarat, sh.mcpergram, sh.goldpergram, sh.fixgold, CURDATE( )
FROM stock_history sh
JOIN (
SELECT stockid, branch,ACTION , MAX( actiondate ) AS MaxDateTime
FROM stock_history
GROUP BY stockid,branch,ACTION
)groupedsh ON sh.stockid = groupedsh.stockid
AND sh.actiondate = groupedsh.MaxDateTime
AND sh.branch = groupedsh.branch
AND sh.action = groupedsh.action
AND sh.branch = '8'
AND sh.categoryid = 'G'
AND sh.action = 'AD'
AND sh.actiondate <= '2016-03-28 23:59:59'
This is to query out the rows that have action as 'AD' and have the max(actiondate).
To query with max(action_date), you need to use sub query, e.g.:
select field1, field2
from table
where
sh.action = 'AD'
and sh.actiondate = (
select max(sh.actiondate)
from table
where sh.action = 'AD'
);

Unrolling periodical events in MySQL

I've got a database with two tables, that I want to combine. One of the tables contains "incidental events", which just occur once. Next to this, I also have "periodical events". Now I want to combine these two in a view.
The incidental one simply has two columns, one called changes, the other one called date. The periodical one has three columns, changes, startDate and endDate. The difference between these two can be a maximum of 50 years, so manually typing out one case for every day is not going to work. Both views also have an AI ID. In this view I want to have a column date and a column changes.
To achieve this I want to unroll the periodical changes table, so that it shows one entry for every day in between the startDate and endDate. For instance:
incidental changes:
date | change
09/08/2015 | 5
11/08/2015 | 10
periodical changes:
startDate | endDate | change
09/08/2015 | 12/08/2015 | 7
These two I want combined into:
changes view:
date | change
09/08/2015 | 5
09/08/2015 | 7
10/08/2015 | 7
11/08/2015 | 10
11/08/2015 | 7
12/08/2015 | 7
My idea is to use something like this:
SELECT * FROM incidental_changes,(
SET #id = (SELECT min(ID) AS min FROM periodical_changes WHERE 1)
SET #maxID = (SELECT max(ID) AS max FROM periodical_changes WHERE 1)
WHILE (#id <= #maxID) DO
SET #firstDate = (SELECT startDate FROM periodical_changes WHERE id = #id)
SET #lastDate = (SELECT endDate FROM periodical_changes WHERE id = #id)
WHILE (#firstDate <= #lastDate) DO
SELECT #firstDate AS date, change FROM periodical_changes WHERE id = #id
#firstDate = #firstDate + INTERVAL 1 DAY
END
#id = #id + 1
END
) WHERE 1
This gives me an error,
CREATE ALGORITHM = UNDEFINED VIEW all_periodicals AS SELECT * FROM
incidental_changes,( SET #id = (SELECT min(ID) AS min FROM
periodical_changes WHERE 1) SET #maxID = (SELECT max(ID) AS max FROM
periodical_changes WHERE 1) WHILE (#id <= #maxID) DO SET #firstDate =
(SELECT startDate FROM periodical_changes WHERE id = #id) SET
#lastDate = (SELECT endDate FROM periodical_changes WHERE id = #id)
WHILE (#firstDate <= #lastDate) DO SELECT #firstDate AS date, change
FROM periodical_changes WHERE id = #id #firstDate = #firstDate +
INTERVAL 1 DAY END #id = #id + 1 END ) WHERE 1
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near 'SET #id = (SELECT min(ID) AS min FROM periodical_changes WHERE
1) SET #' at line 5
and I'm guessing that if I'd manage to fix this error there'd be more. So, is there any way to do this the way I want, or do I have to look for a different approach?
EDIT:
Okay, so far I have not found a way to do this in a view or so. So instead I am now using a routine. This routine has one parameter, account INT. The definition I am using so far is as followed:
BEGIN
DECLARE periodicalID int;
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE periodicalCursor CURSOR
FOR SELECT periodicals.periodicalID FROM periodicals WHERE periodicals.accountID = account;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
CREATE TEMPORARY TABLE results LIKE incidentials;
ALTER TABLE results DROP INDEX date;
SET #periodicalID = -1;
OPEN periodicalCursor;
allPeriodicals: LOOP
FETCH periodicalCursor INTO periodicalID;
IF (v_finished) THEN
LEAVE allPeriodicals;
END IF;
SELECT periodicals.startDate,periodicals.numberOfPeriods,periodicals.period,periodicals.endDate,periodicals.money FROM periodicals WHERE periodicals.periodicalID = periodicalID AND periodicals.accountID = account INTO #startDate, #numberOfPeriods, #period,#endDate,#money;
SET #intervalStatement = "SELECT ? + INTERVAL ? ";
SET #intervalStatement = CONCAT(#intervalStatement,#period," INTO #res");
PREPARE intervalStatement FROM #intervalStatement;
WHILE #startDate <= #endDate DO
EXECUTE intervalStatement USING #startDate,#numberOfPeriods;
SET #startDate = #res;
INSERT INTO results(accountID,date,money) VALUES (account,#startDate,#money);
END WHILE;
END LOOP allPeriodicals;
INSERT INTO results(accountID,date,money) SELECT accountID,date, money FROM incidentials WHERE incidentials.accountID = account;
SELECT * FROM results ORDER BY date;
END
This poses the problem of performance though. With only one periodical entry spread over a year this query already takes about 16 seconds. So even though this approach works, I either did something wrong causing it to take this long or this is not the right way to go.
Let me presume you have a numbers table. Then you can do:
select i.date, i.change
from incidental
union all
select date_add(p.startDate, interval n.n - 1 day), p.change
from periodic p join
numbers n
on date_add(p.startDate, interval n.n - 1 day) <= p.endDate;
For a select query, you can generate the numbers using a subquery, if you know the maximum length. Something like:
select i.date, i.change
from incidental
union all
select date_add(p.startDate, interval n.n - 1 day), p.change
from periodic p join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) n
on date_add(p.startDate, interval n.n - 1 day) <= p.endDate;
This doesn't work in a view, however. For that, you really do need a numbers table.

MySQL insert if select output <> Value

I'm writing a Logging System for Items where i track the Quantity and Type of various Objects.
And i need to write a Insert Query where it only imports if the Quantity (qty) has changed since the last time.
This is the Query to get the last inserted Quantity:
SELECT qty FROM `qty` WHERE object='object_name' AND type='type' ORDER BY UNIX_TIMESTAMP(timestamp) DESC LIMIT 1
But now how do i say: Import only if quantity given by Programm is not the Quantity given by the Query above
Edit:
Here is the Normal insert:
INSERT INTO `qty` (qty, object, type) VALUES ("quantity", "object_name", "type")
Edit:
I got it working now!
thanks everybody for the response! you guys are awesome :)
INSERT INTO qty (qty, object, type)
SELECT * FROM (SELECT 'qty-value', 'object-value', 'type-value') AS tmp
WHERE NOT EXISTS (
SELECT * FROM (SELECT qty FROM `qty` WHERE object = 'object-value' AND type = 'type-value' ORDER BY UNIX_TIMESTAMP( timestamp ) DESC LIMIT 1) as lastQTY WHERE qty = "qty-value"
) LIMIT 1;
If you want to insert new values, try matching the new values to the old values. If there is a match, then filter out the rows. I think the key is using insert . . . select rather than insert . . . values.
The following gives the idea:
INSERT INTO qty(qty, object, type)
select #quantity, #object_name", #type
from (select #quantity as quantity, #object_name as object_name, #type as type
) as newrow left outer join
(SELECT qty.*
FROM qty
WHERE object = #object_name AND type = #type
ORDER BY UNIX_TIMESTAMP(timestamp) DESC
LIMIT 1
) oldrow
on newrow.quantity = oldrow.quantity and
newrow.object_name = oldrow.object_name and
newrow.type = oldrow.type
where oldrow is null;
Think this would do it.
This takes your input values, joins that against a sub query to get the latest timestamp for the object and type, and then joins that against the qty table to get the value of the column qty for the latest timestamp and that the qty is the same as the new qty.
The WHERE clause is then checking that the value of the latest qty is NULL (ie, assuming the qty can not legitimatly be NULL there is no record found )
INSERT INTO `qty_test` (qty, object, type)
SELECT a.qty, a.object, a.type
FROM
(
SELECT 1 AS qty, 1 AS object, 1 AS type
) a
LEFT OUTER JOIN
(
SELECT object, type, MAX(timestamp) AS max_timestamp
FROM qty_test
GROUP BY object, type
) b
ON a.object = b.object
AND a.type = b.type
LEFT OUTER JOIN qty_test c
ON a.object = c.object
AND a.type = c.type
AND a.qty = c.qty
AND b.max_timestamp = c.timestamp
WHERE c.qty IS NULL