Can I replace this loop in a sql server query - sql-server-2008

I have a list of items, sorted by date descending, and it checks them like this:
counted = 0
DateToCheck = now
foreach(item)
{
if( abs(item.date - DateToCheck) > 14 days )
{
counted++
}
DateToCheck = item.date
}
The goal is to get a count of items on the list that did not occur within 14 days of the previous item.
The table is just a list of dates, like this:
index ItemDate
307000 2017-08-17
307001 2017-04-25
307002 2016-09-23
307003 2016-08-26
307004 2016-04-30
307005 2016-03-01
307006 2016-03-01
The result here should be a count of 6, the last one is ignored since it is within 14 days of the one before.

You can use this query.
DECLARE #item TABLE([index] int, [date] DATETIME)
INSERT INTO #item
VALUES( 307006 ,'2017-08-17'),
(307005 ,'2017-04-25'),
(307004 ,'2016-09-23'),
(307003 ,'2016-08-26'),
(307002 ,'2016-04-30'),
(307001 ,'2016-03-01'),
(307000 ,'2016-03-01')
SELECT
count(*)
FROM #item T1
OUTER APPLY (
SELECT TOP 1 *
FROM #item T2
WHERE T2.[index] < T1.[index]
ORDER BY T2.[index] DESC) T
WHERE DATEDIFF(DAY, T.[date], T1.[date]) > 14

You can use this query if you do not have a ID column. Use ID column directly if you have one.
;WITH TBL AS (
SELECT ROW_NUMBER() OVER(ORDER BY ItemDate ASC) Id, ItemDate FROM TABLE_NAME
)
SELECT COUNT(a.ItemDate) FROM TBL a INNER JOIN TBL b ON b.ID = a.ID + 1 WHERE DATEDIFF(d, a.CreatedOn, b.CreatedOn) > 14;
With ID column, query changes to
SELECT COUNT(a.ItemDate) FROM TABLE_NAME a INNER JOIN TABLE_NAME b ON b.ID = a.ID + 1 WHERE DATEDIFF(d, a.CreatedOn, b.CreatedOn) > 14;

Related

Mysql: Get next and previous in duplicated records

My database has two columns ID and Timestamp.
4 1597228600
8 1597228700
12 1597228700
11 1597228800
14 1597228800
9 1597228900
10 1597228900
1 1597228900
2 1597229000
I need to get next (previous) record of the given id and timestamp ordered by timestamp. If the timestamp has duplicates, the record with higher(lower) id should be returned.
In the example Next and Prev records of the 11(1597228800) are 14 and 12. Next and Prev records of the 14(1597228800) are 1 and 11.
I tried to use CASE condition with subquery, but this solution has issues
SELECT id
FROM tbl
WHERE timestamp_value >= '1597228800'
AND id > (case when ( SELECT min(id) min_id FROM tbl WHERE id > 11 AND timestamp_value = 1597228800) is null then 0 else 11 end)
ORDER BY timestamp_value
LIMIT 1
I think that this will do:
select t.*
from tablename t
cross join (select * from tablename where id = ?) i
where t.id in (
(
select id from tablename
where (id < i.id and timestamp = i.timestamp) or timestamp < i.timestamp
order by timestamp desc, id desc limit 1
),
(
select id from tablename
where (id > i.id and timestamp = i.timestamp) or timestamp > i.timestamp
order by timestamp, id limit 1
)
)
Replace ? with the id that you want to search for.
The 2 subqueries return the ids of the previous and the next id of ?.
See the demo.

How to select last and last but one records

I have a table with 3 columns id, type, value like in image below.
What I'm trying to do is to make a query to get the data in this format:
type previous current
month-1 666 999
month-2 200 15
month-3 0 12
I made this query but it gets just the last value
select *
from statistics
where id in (select max(id) from statistics group by type)
order
by type
EDIT: Live example http://sqlfiddle.com/#!9/af81da/1
Thanks!
I would write this as:
select s.*,
(select s2.value
from statistics s2
where s2.type = s.type
order by id desc
limit 1, 1
) value_prev
from statistics s
where id in (select max(id) from statistics s group by type) order by type;
This should be relatively efficient with an index on statistics(type, id).
select
type,
ifnull(max(case when seq = 2 then value end),0 ) previous,
max( case when seq = 1 then value end ) current
from
(
select *, (select count(*)
from statistics s
where s.type = statistics.type
and s.id >= statistics.id) seq
from statistics ) t
where seq <= 2
group by type

Select from a select statement from a defined value

I've the following table structure:
id |name |date
1 a 2012-01-01
2 a 2011-01-01
3 a 2010-01-01
4 a 2014-01-01
5 a 2011-01-01
I'd like to perform a select order by date (desc), and after select the first 3 rows from the results by a condition which would be where id = 1. So the second part of the query would be "give me the first 3 rows starting from the row whose id equals to 1"
EDIT:
After the first "part" the result would be:
SELECT id, name, date FROM table ORDER BY date DESC
id |name |date
4 a 2014-01-01
1 a 2012-01-01
2 a 2011-01-01
5 a 2011-01-01
3 a 2010-01-01
After the second part it should look like this (so the first 3 after the row whose id is 1):
id |name |date
2 a 2011-01-01
5 a 2011-01-01
3 a 2010-01-01
I have no any idea how could I solve it, please help me.
EDIT:
This is the concrete code I'd like to re-write:
SELECT `id`, `questions`.`userid`, `categories`.`name`, `user`.`username`, `title`,
`details`, `date` FROM `questions`
LEFT JOIN `user`
ON `questions`.`userid` = `user`.`userid`
LEFT JOIN `categories`
ON `questions`.`categoryid` = `categories`.`categoryid`
ORDER BY `date` DESC LIMIT 10
SELECT *
FROM table
WHERE date < (SELECT date FROM table WHERE id = 1)
ORDER BY date DESC
LIMIT 3
This isn't pretty because MySQL doesn't support row_number() or common table expressions, but it should work. Basically, get the row number ordered by the date, then select those whose row number is greater than an arbitrary value (in this case 1). Finally use limit to select the number of records you want.
SELECT id, name, mydate
FROM (
SELECT id, name, mydate, #rn:=#rn+1 rn
FROM mytable, (select #rn:=0) t
ORDER BY mydate DESC
) t2
WHERE rn > (
select rn
from (
SELECT id, name, mydate, #rn:=#rn+1 rn
FROM mytable, (select #rn:=0) t
ORDER BY mydate DESC
) t2
where id = 1
)
LIMIT 3
SQL Fiddle Demo
This is what you want to do... if finds the first id thats equal to 4 and then selects those out. then limit the offset to go to the next row and pull out 3
SELECT id, name, m_date from(
SELECT id, name, m_date, #a := id, if(#a = 4, #b := 1, #b) AS join_id
FROM test
join(SELECT #a := 0, #b := 0) t
ORDER BY m_date DESC
) AS tt
WHERE join_id = 1
LIMIT 1,3
SELECT temp.`id`, temp.`userid`, `categories`.`name`, `user`.`username`, temp.`title`,
temp.`details`, temp.`date` FROM (
SELECT `id`, `categoryid`, `details`, `title`, `userid`, `date`, #a := id, if(#a = 11, #b := 1, #b) AS join_id
FROM `questions`
join(SELECT #a := 0, #b := 0) t
ORDER BY `date` DESC
) as temp
LEFT JOIN `user`
ON temp.`userid` = `user`.`userid`
LEFT JOIN `categories`
ON temp.`categoryid` = `categories`.`categoryid`
WHERE join_id = 1
LIMIT 1,10;
SEE FIDDLE for clarification

Insert N rows with date interval

I need to insert rows into a database, where every row is the same except a date column which should have its date incremented by 1 week for each new row. So, basically this:
for(n = 0; n<X; n++)
insert into events (date, title) values (start_date + 7*n, 'static title');
Any MySQL trick that can be used to do this?
You can use:
SELECT
'static_title' AS title,
DATE_ADD(#start_date, INTERVAL #i:=#i+1 WEEK) AS result_date
FROM
(SELECT
(two_1.id + two_2.id + two_4.id +
two_8.id + two_16.id) AS id
FROM
(SELECT 0 AS id UNION ALL SELECT 1 AS id) AS two_1
CROSS JOIN (SELECT 0 id UNION ALL SELECT 2 id) AS two_2
CROSS JOIN (SELECT 0 id UNION ALL SELECT 4 id) AS two_4
CROSS JOIN (SELECT 0 id UNION ALL SELECT 8 id) AS two_8
CROSS JOIN (SELECT 0 id UNION ALL SELECT 16 id) AS two_16
) AS sequence
CROSS JOIN
-- #i:=0 for not including current week
(SELECT #i:=-1, #start_date:=CURDATE()) AS init
WHERE
sequence.id<10;
-that will produce N rows (here N=10). To insert rows, just use INSERT .. SELECT syntax. Fiddle is here. Also in sample start_date is set to CURDATE() - but you can easily adjust that in query, of course.

MySql - How get value in previous row and value in next row? [duplicate]

This question already has answers here:
How to get next/previous record in MySQL?
(23 answers)
Closed 4 years ago.
I have the following table, named Example:
id(int 11) //not autoincriment
value (varchar 100)
It has the following rows of data:
0 100
2 150
3 200
6 250
7 300
Note that id values are not contiguous.
I've written this SQL so far:
SELECT * FROM Example WHERE id = 3
However, I don't know how to get the value of previous id and value of the next id...
Please help me get previous value and next value if id = 3 ?
P.S.: in my example it will be: previous - 150, next - 250.
Select the next row below:
SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1
Select the next row above:
SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1
Select both in one query, e.g. use UNION:
(SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1)
UNION
(SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1)
That what you mean?
A solution would be to use temporary variables:
select
#prev as previous,
e.id,
#prev := e.value as current
from
(
select
#prev := null
) as i,
example as e
order by
e.id
To get the "next" value, repeat the procedure. Here is an example:
select
id, previous, current, next
from
(
select
#next as next,
#next := current as current,
previous,
id
from
(
select #next := null
) as init,
(
select
#prev as previous,
#prev := e.value as current,
e.id
from
(
select #prev := null
) as init,
example as e
order by e.id
) as a
order by
a.id desc
) as b
order by
id
Check the example on SQL Fiddle
May be overkill, but it may help you
please try this sqlFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.value < e1.value
ORDER BY value DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.value > e1.value
ORDER BY value ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
Edit: OP mentioned to grab value of previous id and value of next id in one of the comments so the code is here SQLFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.id < e1.id
ORDER BY id DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.id > e1.id
ORDER BY id ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
SELECT *,
(SELECT value FROM example e1 WHERE e1.id < e.id ORDER BY id DESC LIMIT 1 OFFSET 0) as prev_value,
(SELECT value FROM example e2 WHERE e2.id > e.id ORDER BY id ASC LIMIT 1 OFFSET 0) as next_value
FROM example e
WHERE id=3;
And you can place your own offset after OFFSET keyword if you want to select records with higher offsets for next and previous values from the selected record.
Here's my solution may suit you:
SELECT * FROM Example
WHERE id IN (
(SELECT MIN(id) FROM Example WHERE id > 3),(SELECT MAX(id) FROM Example WHERE id < 3)
)
Demo: http://sqlfiddle.com/#!9/36c1d/2
A possible solution if you need it all in one row
SELECT t.id, t.value, prev_id, p.value prev_value, next_id, n.value next_value
FROM
(
SELECT t.id, t.value,
(
SELECT id
FROM table1
WHERE id < t.id
ORDER BY id DESC
LIMIT 1
) prev_id,
(
SELECT id
FROM table1
WHERE id > t.id
ORDER BY id
LIMIT 1
) next_id
FROM table1 t
WHERE t.id = 3
) t LEFT JOIN table1 p
ON t.prev_id = p.id LEFT JOIN table1 n
ON t.next_id = n.id
Sample output:
| ID | VALUE | PREV_ID | PREV_VALUE | NEXT_ID | NEXT_VALUE |
|----|-------|---------|------------|---------|------------|
| 3 | 200 | 2 | 150 | 4 | 250 |
Here is SQLFiddle demo
This query uses a user defined variable to calculate the distance from the target id, and a series of wrapper queries to get the results you want. Only one pass is made over the table, so it should perform well.
select * from (
select id, value from (
select *, (#x := ifnull(#x, 0) + if(id > 3, -1, 1)) row from (
select * from mytable order by id
) x
) y
order by row desc
limit 3
) z
order by id
See an SQLFiddle
If you don't care about the final row order you can omit the outer-most wrapper query.
If you do not have an ID this has worked for me.
Next:
SELECT * FROM table_name
WHERE column_name > current_column_data
ORDER BY column_name ASC
LIMIT 1
Previous:
SELECT * FROM table_name
WHERE column_name < current_column_data
ORDER BY column_name DESC
LIMIT 1
I use this for a membership list where the search is on the last name of the member. As long as you have the data from the current record it works fine.