SQL Server, assign key by date range - sql-server-2014

BILLS:
billdate billno
-------------------
2015-04-13 8090
2015-03-11 8089
2015-02-14 8088
2015-02-10 8087
TRANSACTIONS
transactiondate billno
-----------------------
2015-04-15 8090
2015-04-13 8090
2015-04-12 8090
2015-04-10 8090
2015-03-12 8090
2015-03-11 8089
2015-03-10 8089
2015-02-11 8088
2015-02-01 8087
I need to assign billno to transaction line items as described above. Transactions outside the bill range -- i.e. those beyond 2015-04-13 that don't match up within the date range window need to go with the last available bill. And vice-versa, transactions on the bottom end such as 2015-02-01 need to go with the last available bill.
Not quite sure how to go about this query.

You can use either a cursor or a recursive common table expression (CTE). I'd advise saying away from cursors, so here's an example of the CTE.
There's a lot going on in this query so I'll try to break it down in pieces.
If you perform a FULL JOIN against the two tables you'll come up with something that starts to look like the answer, but there are a lot of NULL values
for each transaction:
DECLARE #Bills TABLE (billdate DATE, billno INT)
DECLARE #Transactions TABLE (transactiondate DATE)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-04-13', 8090)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-03-11', 8089)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-02-14', 8088)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-02-10', 8087)
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-15')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-13')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-12')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-10')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-03-12')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-03-11')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-03-10')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-02-11')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-02-01')
SELECT
b.billdate,
b.billno,
t.transactiondate,
COALESCE(b.billdate, t.transactiondate) AS [eventdate]
FROM #Bills b
FULL JOIN #Transactions t
ON b.billdate = t.transactiondate
ORDER BY eventdate DESC
To arrive at the solution we need to pull the previous event's billno to the current event, if the current event's billno IS NULL.
To accomplish this we can use the recursive CTE. Recursive CTEs don't allow joins within (http://msdn.microsoft.com/en-us/library/ms175972.aspx) them so I had to refactor the FULL JOIN to a UNION ALL, aliased as the "AnchorSet". The
CTE is INNER JOINed against itself, with an offset to the rownumber/eventdate, while the billno IS NULL.
DECLARE #Bills TABLE (billdate DATE, billno INT)
DECLARE #Transactions TABLE (transactiondate DATE)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-04-13', 8090)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-03-11', 8089)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-02-14', 8088)
INSERT INTO #Bills (billdate, billno) VALUES ('2015-02-10', 8087)
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-15')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-13')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-12')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-04-10')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-03-12')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-03-11')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-03-10')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-02-11')
INSERT INTO #Transactions (transactiondate) VALUES ('2015-02-01')
SELECT
b.billdate,
b.billno,
t.transactiondate,
COALESCE(b.billdate, t.transactiondate) AS [eventdate]
FROM #Bills b
FULL JOIN #Transactions t
ON b.billdate = t.transactiondate
ORDER BY eventdate DESC
;WITH AnchorSet AS
(
SELECT
MIN(billdate) AS billdate,
MIN(billno) AS billno,
MIN(transactiondate) as transactiondate,
eventdate
FROM
(
SELECT
billdate,
billno,
transactiondate,
COALESCE(billdate, transactiondate) AS [eventdate]
FROM
(
SELECT
billdate,
billno,
NULL AS [transactiondate]
FROM #Bills b
UNION ALL
SELECT
NULL AS billdate,
NULL AS billno,
transactiondate
FROM #transactions t
)innerset
)innerset2
GROUP BY eventdate
)
, cte (billdate, billno, transactiondate, eventdate, rownumber, recursionlevel)
AS
(
SELECT
billdate,
billno,
transactiondate,
eventdate,
ROW_NUMBER() OVER (ORDER BY eventdate DESC) AS [rownumber],
0 AS [recursionlevel]
FROM
AnchorSet
WHERE
billno IS NULL
UNION ALL
SELECT
COALESCE(RecursiveSet.billdate, cte.billdate) AS [billdate],
COALESCE(RecursiveSet.billno, cte.billno) AS [billno],
COALESCE(RecursiveSet.transactiondate, cte.transactiondate) AS [transactiondate],
COALESCE(RecursiveSet.eventdate, cte.eventdate) AS [eventdate],
COALESCE(RecursiveSet.rownumber, cte.rownumber) AS [rownumber],
recursionlevel + 1 AS [recursionlevel]
FROM
(
SELECT
billdate,
billno,
transactiondate,
eventdate,
ROW_NUMBER() OVER (ORDER BY eventdate DESC) AS [rownumber]
FROM AnchorSet
)RecursiveSet
INNER JOIN cte ON cte.rownumber = RecursiveSet.rownumber - 1
)
SELECT
billno,
transactiondate
FROM
(
SELECT
billno,
transactiondate,
ROW_NUMBER() OVER (PARTITION BY transactiondate ORDER BY transactiondate DESC, rownumber DESC, recursionlevel DESC, billno DESC) AS [finalrownumber]
FROM cte
)SomeSet
WHERE finalrownumber = 1
ORDER BY transactiondate DESC
This will get you what you're looking for, with the exception that the final transaction will be null since it's after the last bill. You can create a query to get the latest billno and apply it to the remaining NULL values for the transaction's billnos. I'll leave that as an exercise for the reader.

Related

Mysql, select many rows and assign different values to each row transactionally

I have a simple MySQL 8 table like this:
CREATE TABLE IF NOT EXISTS `test_codes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`segment_id` int(11) unsigned NOT NULL,
`code_id` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
);
The table is populated with some random values
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('2');
INSERT INTO `test_codes` (`segment_id`) VALUES ('2');
INSERT INTO `test_codes` (`segment_id`) VALUES ('2');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
INSERT INTO `test_codes` (`segment_id`) VALUES ('1');
My applications has the following task: A request comes with an array of codes eg [1,200,10,18] and I need to get 4 rows (equal to array size) from the database where the code = 0 and update the code_id at each row with the values 1,200, 10, 18 transitionally.
A concurrent request that wants to update the code from another running thread, should not access the selected rows of the first thread.
How can I do this?
After the update the first selected row will have code_id 1, the second selected row code_id 200, the third 10 and the last one 18. In other words the task must find rows with unassigned codes (code_id=0) and set a value to each row.
Link: http://sqlfiddle.com/#!9/60e555/1
You can do it with a single statement which you may build in the app
update test_codes
join
(
select id, row_number() over (order by id) rn
from test_codes
where code_id = 0
) t on t.id = test_codes.id
join (
-- a table of new values with their positions
select 1 rn, 1 val union all
select 2, 200 union all
select 3, 10 union all
select 4, 18
) v on v.rn = t.rn
set code_id = v.val
db<>fiddle
MySQL doesn't support arrays. I would suggest that you first load the array into a table. This is just a convenience, but it is handy.
Then you can use a complex update with join to handle this:
update test_codes tc join
(select tc2.*,
row_number() over (order by rand()) as seqnum
from test_codes tc2
) tc2
on tc2.id = tc.id join
(select nc.*,
row_number() over (order by code_id) as seqnum
from new_codes nc
) nc
on tc2.seqnum = nc.seqnum
set tc.code_id = nc.code_id;
EDIT:
You can construct the query directly from the codes:
update test_codes tc join
(select tc2.*,
row_number() over (order by rand()) as seqnum
from test_codes tc2
) tc2
on tc2.id = tc.id join
(select ? as code, 1 as seqnum union all
select ? as code, 2 union all
. . .
) nc
on tc2.seqnum = nc.seqnum
set tc.code_id = nc.code_id;

How to find cumulative sum between two dates in MySQL?

How to find cumulative sum between two dates taking into account the previous state?
Putting WHERE condition
WHERE date BETWEEN '2021-02-19 12:00:00'AND '2021-02-21 12:00:00';
doesn't do the job because the sum starts from the first condition's date, and not from the first record. I would like to select only part of the whole query (between two dates), but to calculate cumulative sum from the first (initial) state.
I prepared Fiddle
CREATE TABLE `table1` (
`id` int(11) NOT NULL,
`date` datetime NOT NULL DEFAULT current_timestamp(),
`payment` double NOT NULL
);
INSERT INTO `table1` (`id`, `date`, `payment`) VALUES
(1, '2021-02-16 12:00:00', 100),
(2, '2021-02-17 12:00:00', 200),
(3, '2021-02-18 12:00:00', 300),
(4, '2021-02-19 12:00:00', 400),
(5, '2021-02-20 12:00:00', 500),
(6, '2021-02-21 12:00:00', 600),
(7, '2021-02-22 12:00:00', 700);
version();
SELECT DATE_FORMAT(date, "%Y-%m-%d") AS date,
payment, SUM(payment) OVER(ORDER BY id) AS balance
FROM table1
WHERE date BETWEEN '2021-02-19 12:00:00'AND '2021-02-21 12:00:00';
You must filter the table after you get the cumulative sums:
SELECT *
FROM (
SELECT DATE(date) AS date,
payment,
SUM(payment) OVER(ORDER BY id) AS balance
FROM table1
) t
WHERE date BETWEEN '2021-02-19'AND '2021-02-21';
or:
SELECT *
FROM (
SELECT DATE(date) AS date,
payment,
SUM(payment) OVER(ORDER BY id) AS balance
FROM table1
WHERE DATE(date) <= '2021-02-21'
) t
WHERE date >= '2021-02-19';
See the demo.
Results:
date
payment
balance
2021-02-19
400
1000
2021-02-20
500
1500
2021-02-21
600
2100

How to get missing dates from date column in a table based on two dates

I have a table name as 'records' field is 'dates', how to get missing dates in a table based on passing two dates like 2018-03-23,2018-03-30 using MySQL query
records :
id dates
------------
1 2018-03-23
2 2018-03-24
3 2018-03-27
4 2018-03-28
5 2018-03-30
Expected Result is missing dates: 25,26,29
Here's a SQL Server Solution
declare #date datetime=(Select min(date) from table)
declare #maxdate datetime = (Select max(date) from table)
while #date<=#maxdate
begin
if #date not in (Select date from table)
select #date
select #date=dateadd(dd, 1, #date)
end
Please check the example below:
CREATE TABLE #tmp (
my_date DATETIME
)
INSERT INTO #tmp (my_date) VALUES ('2016-01-01')
INSERT INTO #tmp (my_date) VALUES ('2016-01-02')
INSERT INTO #tmp (my_date) VALUES ('2016-01-04')
INSERT INTO #tmp (my_date) VALUES ('2016-01-05')
INSERT INTO #tmp (my_date) VALUES ('2016-01-07')
INSERT INTO #tmp (my_date) VALUES ('2016-01-08')
INSERT INTO #tmp (my_date) VALUES ('2016-01-10')
INSERT INTO #tmp (my_date) VALUES ('2016-01-11')
DECLARE #max_date DATETIME
SELECT #max_date = max(my_date) FROM #tmp
SELECT DATEADD(day,1,t1.my_date) as miss_date
FROM #tmp t1
LEFT JOIN #tmp t2 ON t1.my_date=DATEADD(day,-1,t2.my_date)
WHERE t2.my_date is null
AND t1.my_date<>#max_date
Also, please see: MySQL: Find Missing Dates Between a Date Range

Count results for the current date using fields of type datetime

I am trying to count the entries for the current day and sum the total. Currently, I have a query that counts the entries per day. I am using the datetime field to achieve my end goal. What would be the best approach to count the entries for the current day and sum the total?
CREATE TABLE product_entry
(`id` int, `entry_time` datetime, `product` varchar(55))
;
INSERT INTO product_entry
(`id`, `entry_time`, `product`)
VALUES
(1, '2015-09-03 15:16:52', 'dud1'),
(2, '2015-09-03 15:25:00', 'dud2'),
(3, '2015-09-04 16:00:12', 'dud3'),
(4, '2015-09-04 17:23:29', 'dud4')
;
SQLFIDDLE
Query
SELECT entry_time, count(*)
FROM product_entry
GROUP BY hour( entry_time ) , day( entry_time )
Schema
CREATE TABLE product_entry
(`id` int, `entry_time` datetime, `product` varchar(55))
;
INSERT INTO product_entry
(`id`, `entry_time`, `product`)
VALUES
(1, '2015-09-03 15:16:52', 'dud1'),
(2, '2015-09-03 15:25:00', 'dud2'),
(3, '2015-09-04 16:00:12', 'dud3'),
(4, '2015-09-04 17:23:29', 'dud4')
;
The title of your question says Count results for the current date ..., but the query you have tried suggests you want to show result counts for every distinct date. I am not sure which one you need. If the former is the case, you could simply use:
SELECT COUNT(`id`) FROM `product_entry` WHERE DATE(`entry_time`) = CURDATE()
To get count for today:
SELECT COUNT(`id`) FROM `product_entry` WHERE DATE(`entry_time`) = CURRENT_DATE
To get count for yesterday (needed when You want to get entries at end of the day):
SELECT COUNT(`id`) FROM `product_entry` WHERE DATE(`entry_time`) = SUBDATE(CURRENT_DATE, 1)
For all time grouped by date and formated:
SELECT DATE_FORMAT(entry_time,'%Y-%m-%d'), count(*)
FROM product_entry
GROUP BY date(entry_time)
this is MSSQL Code maybe your help
SELECT day([product_entry].[entry_time])as input, count(*) as Miktar
FROM [product_entry]
GROUP BY day([entry_time])

Insert INTO MySQL FROM another table

INSERT INTO campaign_ledger (`campaign_id`, `description`, amount, balance, timestamp)
VALUES (SELECT id as campaign_id, 'Ported from campaigns' as description, budget_remaining as amount, budget_remaining as balance, NOW() as timestamp FROM campaigns)
That's my syntax, but I get an error saying:
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 'SELECT id as campaign_id, 'Ported from campaigns' as
description, budget_remaini' at line 2
What am I doing wrong?
Since you are selecting from a table then you will want to use an INSERT INTO SELECT FROM query:
INSERT INTO campaign_ledger
(
`campaign_id`
, `description`
, amount
, balance
, timestamp
)
SELECT
id as campaign_id
, 'Ported from campaigns' as description
, budget_remaining as amount
, budget_remaining as balance
, NOW() as timestamp
FROM campaigns
Only use INSERT INTO VALUES when you are using specific values and not selecting from a table. If you wanted to use INSERT INTO VALUES then your query would be like this:
INSERT INTO campaign_ledger
(
`campaign_id`
, `description`
, amount
, balance
, timestamp
)
VALUES
(
1
, 'test'
, 100.00
, 1000.00
, NOW()
)
INSERT INTO campaign_ledger (`campaign_id`, `description`, amount, balance, timestamp)
SELECT id as campaign_id, 'Ported from campaigns' as description,
budget_remaining as amount,budget_remaining as balance,
NOW() as timestamp FROM campaigns;
The VALUES part of the query is not necessary. For example:
INSERT INTO campaign_ledger (`campaign_id`, `description`, amount, balance, timestamp)
SELECT id as campaign_id, 'Ported from campaigns' as description,
budget_remaining as amount, budget_remaining as balance,
NOW() as timestamp
FROM campaigns;