Selecting all rows until first occurrence of given value - sql

For following data:
date|value|check
2009 | 5 | 1
2008 | 5 | 1
2007 | 5 | 1
2006 | 5 | 0
2005 | 5 | 0
2004 | 5 | 1
2003 | 5 | 1
2002 | 5 | 1
I need to select all rows from 2009 back until first occurrence of 0 in check column:
date|value|check
2009 | 5 | 1
2008 | 5 | 1
2007 | 5 | 1
I tried with the lag function, but I was only able to check a month back.
I am working on Oracle 10g.
UPDATE:
All seems to work well, my test data set is too small to say anything about the performance differences.

SELECT * FROM mytable where date > (
SELECT max(date) FROM mytable where check = 0
)

SELECT *
FROM (
SELECT m.*,
MIN(CASE WHEN check = 0 THEN 0 ELSE 1 END) OVER (ORDER BY date DESC)) AS mn
FROM mytable
)
WHERE mn = 1
or even better:
SELECT *
FROM (
SELECT m.*, ROW_NUMBER() OVER (ORDER BY mydate DESC) AS rn
FROM mytable m
ORDER BY
mydate DESC
)
WHERE rownum = DECODE(check, 0, NULL, rn)
ORDER BY
mydate DESC
The latter query will actually stop scanning as soon as it encounters the first zero in check.

DECLARE #mytable TABLE (date integer, [value] integer, [check] integer)
INSERT INTO #mytable VALUES (2009, 5, 1)
INSERT INTO #mytable VALUES (2008, 5, 1)
INSERT INTO #mytable VALUES (2007, 5, 1)
INSERT INTO #mytable VALUES (2006, 5, 0)
INSERT INTO #mytable VALUES (2005, 5, 0)
INSERT INTO #mytable VALUES (2004, 5, 1)
INSERT INTO #mytable VALUES (2003, 5, 1)
INSERT INTO #mytable VALUES (2002, 5, 1)
SELECT *
FROM #mytable
WHERE date > (SELECT MAX(date) FROM #mytable WHERE [Check] = 0)

Related

T-SQL sequential updating with two columns

I have a table created by:
CREATE TABLE table1
(
id INT,
multiplier INT,
col1 DECIMAL(10,5)
)
INSERT INTO table1
VALUES (1, 2, 1.53), (2, 3, NULL), (3, 2, NULL),
(4, 2, NULL), (5, 3, NULL), (6, 1, NULL)
Which results in:
id multiplier col1
-----------------------
1 2 1.53000
2 3 NULL
3 2 NULL
4 2 NULL
5 3 NULL
6 1 NULL
I want to add a column col2 which is defined as multiplier * col1, however the next value of col1 then updates to take the previous calculated value of col2.
The resulting table should look like:
id multiplier col1 col2
---------------------------------------
1 2 1.53000 3.06000
2 3 3.06000 9.18000
3 2 9.18000 18.36000
4 2 18.36000 36.72000
5 3 36.72000 110.16000
6 1 110.16000 110.16000
Is this possible using T-SQL? I've tried a few different things such as joining id to id - 1 and have played around with a sequential update using UPDATE and setting variables but I can't get it to work.
A recursive CTE might be the best approach. Assuming your ids have no gaps:
with cte as (
select id, multiplier, convert(float, col1) as col1, convert(float, col1 * multiplier) as col2
from table1
where id = 1
union all
select t1.id, t1.multiplier, cte.col2 as col1, cte.col2 * t1.multiplier
from cte join
table1 t1
on t1.id = cte.id + 1
)
select *
from cte;
Here is a db<>fiddle.
Note that I converted the destination type to float, which is convenient for this sort of operation. You can convert back to decimal if you prefer that.
Basically, this would require an aggregate/window function that computes the product of column values. Such set function does not exists in SQL though. We can work around this with arithmetics:
select
id,
multiplier,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2
from table1
Demo on DB Fiddle:
id | multiplier | col1 | col2
-: | ---------: | -----: | -----:
1 | 2 | 1.53 | 3.06
2 | 3 | 3.06 | 9.18
3 | 2 | 9.18 | 18.36
4 | 2 | 18.36 | 36.72
5 | 3 | 36.72 | 110.16
6 | 1 | 110.16 | 110.16
This will fail if there are negative multipliers.
If you wanted an update statement:
with cte as (
select col1, col2,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1_new,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2_new
from table1
)
update cte set col1 = col1_new, col2 = col2_new

Select start date and end date form records with subsequent date field in SQL Server 2008 R2

I have a table in SQL Server 2008 R2 called ReserveLog. This is an existing table that stores the reserve date of each room in a complex.
It is like this:
RoomNumber ReserveDate
----------------------
1 2017-07-01
1 2017-07-02
1 2017-07-03
1 2017-07-06
1 2017-07-07
1 2017-07-08
2 2017-01-02
2 2017-01-03
2 2017-01-04
2 2017-01-09
2 2017-01-10
I want to query this table so that I get the following result:
RoomNumber ReserveStartDate ReserveEndDate
------------------------------------------
1 2017-07-01 2017-07-03
1 2017-07-06 2017-07-08
2 2017-07-02 2017-07-04
2 2017-07-09 2017-07-10
Is it possible? I can't make my mind how to do it. Any help is appreciated in advance
create table #reservs
(
roomnumber INT, ReserveDate DATE
)
INSERT INTO #reservs VALUES (1, '2017-07-01');
INSERT INTO #reservs VALUES (1, '2017-07-02');
INSERT INTO #reservs VALUES (1, '2017-07-03');
INSERT INTO #reservs VALUES (1, '2017-07-06');
INSERT INTO #reservs VALUES (1, '2017-07-07');
INSERT INTO #reservs VALUES (1, '2017-07-08');
INSERT INTO #reservs VALUES (2, '2017-01-02');
INSERT INTO #reservs VALUES (2, '2017-01-03');
INSERT INTO #reservs VALUES (2, '2017-01-04');
INSERT INTO #reservs VALUES (2, '2017-01-09');
INSERT INTO #reservs VALUES (2, '2017-01-10');
select roomnumber, MIN(reservedate) as mn, MAX(reservedate) as mx
FROM (
SELECT *
, DATEDIFF(day, ROW_NUMBER() OVER(partition by roomnumber order by reservedate) ,reservedate) as ind
FROM #reservs
) a
group by roomnumber, ind
order by 1, 2
Try this using common table expressions, comments in line and SQL Fiddle link:
SQL Fiddle
create table Reservelog
(
RoomNumber INT,
ReserveDate Date
)
INSERT INTO ReserveLog
VALUES
(1, '2017-07-01'),
(1, '2017-07-02'),
(1, '2017-07-03'),
(1, '2017-07-06'),
(1, '2017-07-07'),
(1, '2017-07-08'),
(2, '2017-01-02'),
(2, '2017-01-03'),
(2, '2017-01-04'),
(2, '2017-01-09'),
(2, '2017-01-10')
Query 1:
;WITH CTE
As
(
SELECT *,
(
-- Get Previous Reserve Date for this room
SELECT TOP 1 ReserveDate
FROM ReserveLog R2
WHERE R1.RoomNumber = R2.RoomNumber AND
R1.ReserveDate > R2.ReserveDate
ORDER BY ReserveDate DESC
) As PrevReserveDate,
(
-- Get NExt ReserveDate For this room
SELECT TOP 1 ReserveDate
FROM ReserveLog R2
WHERE R1.RoomNumber = R2.RoomNumber AND
R1.ReserveDate < R2.ReserveDate
ORDER BY ReserveDate
) As NextReserveDate
FROM ReserveLog R1
),
CTE2
AS
(
SELECT *,
CASE
WHEN PrevReserveDate IS NULL OR
DATEDIFF(D, PrevReserveDate, ReserveDate ) > 1
THEN 1 -- Flag as a StartDate
ELSE 0
END As DateStart,
CASE
WHEN NextReserveDate IS NULL OR
DATEDIFF(D, ReserveDate, NExtReserveDate) > 1
THEN 1 -- Flag as an end date
ELSE 0
END As DateEnd,
ROW_NUMBER() OVER
(PARTITION BY RoomNumber ORDER BY ReserveDate) AS RN
FROM CTE
-- only select rows which have no previous or next reservation or
-- ones where the difference between consecutive reservations > 1 day
WHERE PrevReserveDate IS NULL OR
NextReserveDate IS NULL OR
DATEDIFF(D, PrevReserveDate, ReserveDate ) > 1 OR
DATEDIFF(D, ReserveDate, NExtReserveDate) > 1
)
SELECT startRows.RoomNumber,
startRows.ReserveDate As ReserveStartDate,
endRows.ReserveDate As ReserveEndDate
FROM CTE2 startRows
INNER JOIN CTE2 endRows
ON startRows.RN + 1 = endRows.RN AND
startRows.RoomNumber = endRows.RoomNumber AND
endRows.DateEnd = 1
WHERE startRows.DateStart = 1
Results:
| RoomNumber | ReserveStartDate | ReserveEndDate |
|------------|------------------|----------------|
| 1 | 2017-07-01 | 2017-07-03 |
| 1 | 2017-07-06 | 2017-07-08 |
| 2 | 2017-01-02 | 2017-01-04 |
| 2 | 2017-01-09 | 2017-01-10 |
Use this query:
SELECT * FROM R2 WHERE ReserveDate between ('2017-07-01 ' AND '2017-07-03');

SQL speed issue

I am using PostgreSQL and want to use a query like this:
SELECT device, value, id
FROM myTable s
WHERE (SELECT COUNT(*) FROM myTable f WHERE f.id = s.id AND f.value >= s.value ) <= 2
This works but the problem is it takes minutes to execute over large data. Is there a faster way that can happen in seconds? What I am trying to do is take only two items from a row where both values are sorted in asc order.
id | device | value
1 123 40
1 456 30
1 789 45
2 12 10
2 11 9
The above is my table (I know ids are not unique, not my design, but it has a purpose) but within the id lets say id = 1, I want to select id, device and value of the smallest 2, so my result would be 1, 123, 30 and 1, 456, 40 and so on for other ids.
Also, if anyone knows, if you insert sorted data into a database is it a guarantee to read back out in the same order?
Try below query:
SELECT s.device,s.id,s.value
FROM myTable s
INNER JOIN myTable f ON s.id = f. id AND f.value >= s.value
GROUP BY s.device,s.id,s.value
HAVING COUNT(s.id) <= 2
This can be done using window functions:
select id, device, value
from (
select id, device, value,
row_number() over (partition by id order by value) as rn
from the_table
) t
where rn <= 2
order by id, device, value;
Example:
postgres> create table the_table (id integer, device integer, value integer);
CREATE TABLE
postgres> insert into the_table values
...> (1, 123, 40),
...> (1, 456, 30),
...> (1, 789, 45),
...> (2, 12 , 10),
...> (2, 11 , 9);
INSERT 0 5
postgres> select id, device, value
...> from (
...> select id, device, value,
...> row_number() over (partition by id order by value) as rn
...> from the_table
...> ) t
...> where rn <= 2;
id | device | value
----+--------+-------
1 | 123 | 40
1 | 456 | 30
2 | 11 | 9
2 | 12 | 10
(4 rows)

ordering query results with two columns

I have this table:
Reply_ID | Fk_Post_ID
10 | 5
9 | 6
8 | 5
7 | 9
6 | 5
5 | 9
4 | 7
I need a query retrieves records in the following order pattern. it searches for the record with the highest reply_ID then retrieves all records having the same Fk_Post_ID. something like this:
Reply_ID | Fk_Post_ID
10 | 5
8 | 5
6 | 5
9 | 6
7 | 9
5 | 9
4 | 7
CREATE TABLE #YourTable (
Reply_ID INT,
fk_Post_ID INT
)
INSERT INTO #YourTable VALUES (10, 5)
INSERT INTO #YourTable VALUES (9, 6)
INSERT INTO #YourTable VALUES (8, 5)
INSERT INTO #YourTable VALUES (7, 9)
INSERT INTO #YourTable VALUES (6, 5)
SELECT
t1.Reply_ID,
t1.fk_Post_ID
FROM
#YourTable t1 JOIN (
SELECT
MAX(Reply_ID) AS Max_Reply_ID,
fk_Post_ID
FROM #YourTable
GROUP BY fk_Post_ID
) t2 ON t2.fk_Post_ID = t1.fk_Post_ID
ORDER BY
t2.Max_Reply_ID DESC,
t1.Reply_ID DESC
Sql Fiddle Here
You could use a CASE in the ORDER BY:
....
ORDER BY CASE WHEN Fk_Post_ID=(
SELECT MIN(Fk_Post_ID)
FROM dbo.Table
WHERE Reply_ID=(SELECT MAX(Reply_ID)FROM dbo.Table)
) THEN 0 ELSE 1 END ASC
, Reply_ID DESC
, Fk_Post_ID ASC
Here's the fiddle: http://sqlfiddle.com/#!3/45f20/7/0
If your DBMS has window functions then this will also solve it:
Select
Reply_ID,
FK_Post_ID
From
yourTable a
Order By
Max(Reply_ID) Over (Partition By FK_Post_ID) Desc,
Reply_ID Desc
http://sqlfiddle.com/#!3/4be0c/9/0

double sorted selection from a single table

I have a table with an id as the primary key, and a description as another field.
I want to first select the records that have the id<=4, sorted by description, then I want all the other records (id>4), sorted by description. Can't get there!
select id, descr
from t
order by
case when id <= 4 then 0 else 1 end,
descr
select *, id<=4 as low from table order by low, description
You may want to use an id <= 4 expression in your ORDER BY clause:
SELECT * FROM your_table ORDER BY id <= 4 DESC, description;
Test case (using MySQL):
CREATE TABLE your_table (id int, description varchar(50));
INSERT INTO your_table VALUES (1, 'c');
INSERT INTO your_table VALUES (2, 'a');
INSERT INTO your_table VALUES (3, 'z');
INSERT INTO your_table VALUES (4, 'b');
INSERT INTO your_table VALUES (5, 'g');
INSERT INTO your_table VALUES (6, 'o');
INSERT INTO your_table VALUES (7, 'c');
INSERT INTO your_table VALUES (8, 'p');
Result:
+------+-------------+
| id | description |
+------+-------------+
| 2 | a |
| 4 | b |
| 1 | c |
| 3 | z |
| 7 | c |
| 5 | g |
| 6 | o |
| 8 | p |
+------+-------------+
8 rows in set (0.00 sec)
Related post:
Using MySql, can I sort a column but have 0 come last?
select id, description
from MyTable
order by case when id <= 4 then 0 else 1 end, description
You can use UNION
SELECT * FROM (SELECT * FROM table1 WHERE id <=4 ORDER by description)aaa
UNION
SELECT * FROM (SELECT * FROM table1 WHERE id >4 ORDER by description)bbb
OR
SELECT * FROM table1
ORDER BY
CASE WHEN id <=4 THEN 0
ELSE 1
END, description