Get previous and next row from rows selected with (WHERE) conditions - sql

For example I have this statement:
my name is Joseph and my father's name is Brian
This statement is splitted by word, like this table:
------------------------------
| ID | word |
------------------------------
| 1 | my |
| 2 | name |
| 3 | is |
| 4 | Joseph |
| 5 | and |
| 6 | my |
| 7 | father's |
| 8 | name |
| 9 | is |
| 10 | Brian |
------------------------------
I want to get previous and next word of each word
For example I want to get previous and next word of "name":
--------------------------
| my | name | is |
--------------------------
| father's | name | is |
--------------------------
How could I get this result?

you didn't specify your DBMS, so the following is ANSI SQL:
select prev_word, word, next_word
from (
select id,
lag(word) over (order by id) as prev_word,
word,
lead(word) over (order by id) as next_word
from words
) as t
where word = 'name';
SQLFiddle: http://sqlfiddle.com/#!12/7639e/1

Why did no-body give the simple answer?
SELECT LAG(word) OVER ( ORDER BY ID ) AS PreviousWord ,
word ,
LEAD(word) OVER ( ORDER BY ID ) AS NextWord
FROM words;

Without subqueries:
SELECT a.word
FROM my_table AS a
JOIN my_table AS b
ON b.word = 'name' AND abs(a.id - b.id) <= 1
ORDER BY a.id

Use Join to get the expected result for SQL Server 2005 plus.
create table words (id integer, word varchar(20));
insert into words
values
(1 ,'my'),
(2 ,'name'),
(3 ,'is'),
(4 ,'joseph'),
(5 ,'and'),
(6 ,'my'),
(7 ,'father'),
(8 ,'name'),
(9 ,'is'),
(10,'brian');
SELECT A.Id , C.word AS PrevName ,
A.word AS CurName ,
B.word AS NxtName
FROM words AS A
LEFT JOIN words AS B ON A.Id = B.Id - 1
LEFT JOIN words AS C ON A.Id = C.Id + 1
WHERE A.Word = 'name'
Result:
Fiddler Demo

Try this
SELECT *
FROM tablename a
WHERE ID IN(SELECT ID - 1
FROM tablename
WHERE word = 'name') -- will fetch previous rows of word `name`
OR ID IN(SELECT ID + 1
FROM tablename
WHERE word = 'name') -- will fetch next rows of word `name`
OR word = 'name' -- to fetch the rows where word = `name`

Here's a different approach, if you want the selects to be fast. It takes a bit of preparation work.
Create a new column (e.g. "phrase") in the database that will contain the words
you want. (i.e. the previous, the current and next).
Write a trigger that on insert appends the new word to the previous
row's phrase and prepends the previous row's word to the new row's word and fills
phrase.
If the individual words can change, you'll need a trigger on update to keep the phrase in sync.
Then just select the phrase. You get much better speed, but at the cost of extra storage and slower insert and harder maintainability. Obviously you have to update the phrase column for the existing records, but you have the SQL to do that in the other answers.

Related

Update statement to set a column based the maximum row of another table

I have a Family table:
SELECT * FROM Family;
id | Surname | Oldest | Oldest_Age
---+----------+--------+-------
1 | Byre | NULL | NULL
2 | Summers | NULL | NULL
3 | White | NULL | NULL
4 | Anders | NULL | NULL
The Family.Oldest column is not yet populated. There is another table of Children:
SELECT * FROM Children;
id | Name | Age | Family_FK
---+----------+------+--------
1 | Jake | 8 | 1
2 | Martin | 7 | 2
3 | Sarah | 10 | 1
4 | Tracy | 12 | 3
where many children (or no children) can be associated with one family. I would like to populate the Oldest column using an UPDATE ... SET ... statement that sets it to the Name and Oldest_Age of the oldest child in each family. Finding the name of each oldest child is a problem that is solved quite well here: How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL?
However, I don't know how to use the result of this in an UPDATE statement to update the column of an associated table using the h2 database.
The following is ANSI-SQL syntax that solves this problem:
update family
set oldest = (select name
from children c
where c.family_fk = f.id
order by age desc
fetch first 1 row only
)
In h2, I think you would use limit 1 instead of fetch first 1 row only.
EDIT:
For two columns -- alas -- the solution is two subqueries:
update family
set oldest = (select name
from children c
where c.family_fk = f.id
order by age desc
limit 1
),
oldest_age = (select age
from children c
where c.family_fk = f.id
order by age desc
limit 1
);
Some databases (such as SQL Server, Postgres, and Oracle) support lateral joins that can help with this. Also, row_number() can also help solve this problem. Unfortunately, H2 doesn't support this functionality.

Access query to grab +5 or more duplicates

i have a little problem with an Access query ( dont ask me why but i cannot use a true SGBD but Access )
i have a huge table with like 920k records
i have to loop through all those data and grab the ref that occur more than 5 time on the same date
table = myTable
--------------------------------------------------------------
| id | ref | date | C_ERR_ANO |
--------------------------------------------|-----------------
| 1 | A12345678 | 2012/02/24 | A 4565 |
| 2 | D52245708 | 2011/05/02 | E 5246 |
| ... | ......... | ..../../.. | . .... |
--------------------------------------------------------------
so to resume it a bit, i have like 900000+ records
there is duplicates on the SAME DATE ( oh by the way there is another collumn i forgot to add that have C_ERR_ANO as name)
so i have to loop through all those row, grab each ref based on date AND errorNumber
and if there is MORE than 5 time with the same errorNumber i have to grab them and display it in the result
i ended up using this query:
SELECT DISTINCT Centre.REFERENCE, Centre.DATESE, Centre.C_ERR_ANO
FROM Centre INNER JOIN (SELECT
Centre.[REFERENCE],
COUNT(*) AS `toto`,
Centre.DATESE
FROM Centre
GROUP BY REFERENCE
HAVING COUNT(*) > 5) AS Centre_1
ON Centre.REFERENCE = Centre_1.REFERENCE
AND Centre.DATESE <> Centre_1.DATESE;
but this query isent good
i tried then
SELECT DATESE, REFERENCE, C_ERR_ANO, COUNT(REFERENCE) AS TOTAL
FROM (
SELECT *
FROM Centre
WHERE (((Centre.[REFERENCE]) NOT IN (SELECT [REFERENCE]
FROM [Centre] AS Tmp
GROUP BY [REFERENCE],[DATESE],[C_ERR_ANO]
HAVING Count(*)>1 AND [DATESE] = [Centre].[DATESE]
AND [C_ERR_ANO] = [Centre].[C_ERR_ANO]
AND [LIBELLE] = [Centre].[LIBELLE])))
ORDER BY Centre.[REFERENCE], Centre.[DATESE], Centre.[C_ERR_ANO])
GROUP BY REFERENCE, DATESE, C_ERR_ANO
still , not working
i'm struggeling
Your group by clause needs to include all of the items in your select. Why not use:
select Centre.DATESE, Centre.C_ERR_ANO, Count (*)
Group by Centre.DATESE, Centre.C_ERR_ANO
HAVING COUNT (*) > 5
If you need other fields then you can add them, as long as you ensure the same fields appear in the select as the group by.
No idea what is going on with the formatting here!

Getting a distinct code-name pair but sorting on other columns

I'm a bit stumped on writing a query (SQL not my strong point).
Say I have the following TABLE1:
CODE NAME SCOPE1 SCOPE2 SEQ
------------------------------------
A a Here 1
B b Here 2
C c Here 3
C    c            Room      1
A aa Room 2
B bbb Room 3
The business key is CODE + SCOPE1 + SCOPE2, where SCOPE1 and SCOPE2 are always mutually exclusive.
How can I get a distinct result of CODE and NAME given that I need sort by SCOPE1, SCOPE2, and SEQ?
That is, given SCOPE1 = 'Here' and SCOPE2 = 'Room', I would like to get this result:
CODE NAME
---------
A a
B b
C c
A aa
B bbb
Note: C c from Room is not wanted as it's a duplicate to C c from Here.
I do realise the limitation of using DISTINCT with ORDER BY and the best I could come up with was the following:
select distinct CODE, NAME from
(
select CODE, NAME from MYTABLE
where (SCOPE1='Here' or SCOPE2='Room')
order by SCOPE1, SCOPE2, SEQ
);
The above produces the correct pairs but in the wrong sequence. I tried messing around with GROUP BY, but I guess I didn't know enough.
I have to stick with standard SQL (that is, no product-specific SQL constructs, unless it's Oracle, maybe), and I guess with this particular query, it's probably impossible to avoid subselects.
I would be very grateful for any pointers. Thanks in advance.
UPDATE: I've updated the data set, and based on peterm's answer, here's what I have so far: sqlfiddle. The MIN/MAX trick doesn't work well when I start tweaking the sequences.
The assumption is that I will always search for one specific SCOPE1 paired with one specific SCOPE2. But I need all SCOPE1 records to appear before SCOPE2. The idea is that I don't care whether CODE + NAME comes from SCOPE1 or SCOPE2 - I just want unique pairs that are sorted by SCOPE1, SCOPE2, and SEQ.
UPDATE Based on your updated requirements for Oracle
SELECT CODE, NAME
FROM
(
SELECT CODE, NAME,
ROW_NUMBER() OVER (ORDER BY SCOPE1, SCOPE2, SEQ) rnum
FROM Table1
WHERE SCOPE1='Here'
OR SCOPE2='Room'
) q
GROUP BY CODE, NAME
ORDER BY MIN(rnum)
Here is SQLFiddle
To make it work the same way in SQL Server
SELECT CODE, NAME
FROM
(
SELECT CODE, NAME,
ROW_NUMBER() OVER (ORDER BY CASE WHEN SCOPE1 IS NULL
THEN 1 ELSE 0 END, SCOPE1,
CASE WHEN SCOPE2 IS NULL
THEN 2 ELSE 3 END, SCOPE2, SEQ) rnum
FROM Table1
WHERE SCOPE1='Here'
OR SCOPE2='Room'
) q
GROUP BY CODE, NAME
ORDER BY MIN(rnum)
Here is SQLFiddle
Output:
| CODE | NAME |
---------------
| A | a |
| B | b |
| C | c |
| A | aa |
| B | bbb |
Original answer: The only thing I could think of based on your description of requirements
SELECT CODE, NAME
FROM Table1
WHERE SCOPE1='Here'
OR SCOPE2='Room'
GROUP BY CODE, NAME
ORDER BY MIN(SCOPE1), MIN(SCOPE2), MIN(SEQ)
Here is SQLFiddle demo (MySql)
Here is SQLFiddle demo (SQL Server)
Here is SQLFiddle demo (Oracle)
Now in MySql and SQL Server NULLs go first by default therefore you'll get
| CODE | NAME |
---------------
| B | bbb |
| A | a |
| B | b |
| C | c |
In Oracle NULLs go last by default therefore you'll get
| CODE | NAME |
---------------
| A | a |
| B | b |
| C | c |
| B | bbb |

SQL Query Count and Label if first

I'm using SQL Server management Studio 2010.
I need my query to populate a column if the record contains the first occurrence of a value.
The table my query returns is huge so I'll just use pretend columns to get my point across. My query currently returns a table that looks like this
| ROW | ItemNumber | DateOpen | Status |
| 1 | 10045 | 5/5/2005 | Open |
| 2 | 10045 | 5/5/2005 | Open |
| 3 | 10046 | 5/5/2005 | Open |
| 4 | 10046 | 5/5/2005 | Open |
| 5 | 10046 | 5/5/2005 | Open |
I've already added the row indicator in to the query thinking it would help identify the first occurrence of an ItemNumber. I need to have a new column that marks an X if the record is the first occurrence.
I have this so far
DECLARE #ItemData Table(itemRow BIGINT, itemNumber BIGINT, DateOpen VARCHAR(15), status VARCHAR(15))
INSERT INTO #ItemData (itemRow, ItemNumber, DateOpen, Status)
SELECT Row_Number() OVER(ORDER BY Cm_ItemNumber) AS 'ROW'
,Cm_ItemNumber AS ItemNumber
,Dates_DateOpen AS DateOpen
,St_Status AS Status
FROM db_Items
JOIN db_Dates ON Dates_Item = Cm_ItemID
JOIN db_Status ON St_ID = Cm_StatusID
Select * from #ItemData
The reason its thrown into a table then selected seperately is because there's actually a union and a lot more stuff in the query and when I realized I needed to do a unique checker and add it to a column, I figured the easiest way would be an "after the fact" type thing and it would go into the Select * from #ItemData portion.
I haven't tested this, but it's along the lines I'd be playing with...
using your existing query as the first of 2 CTEs :
With AllData as
(
SELECT Row_Number() OVER(ORDER BY Cm_ItemNumber) AS 'ROW'
,Cm_ItemNumber AS ItemNumber
,Dates_DateOpen AS DateOpen
,St_Status AS Status
FROM db_Items
JOIN db_Dates ON Dates_Item = Cm_ItemID
JOIN db_Status ON St_ID = Cm_StatusID
),
FirstRows as
(
SELECT Min(ROW) as Row, ItemNumber
FROM AllData
GROUP BY ItemNumber
)
SELECT
ad.*,
Case When fr.Row IS NULL then '' else 'X' end as X_Col
FROM AllData ad LEFT JOIN FirstRows fr
ON ad.ROW=fr.Row

Deleting similar columns in SQL

In PostgreSQL 8.3, let's say I have a table called widgets with the following:
id | type | count
--------------------
1 | A | 21
2 | A | 29
3 | C | 4
4 | B | 1
5 | C | 4
6 | C | 3
7 | B | 14
I want to remove duplicates based upon the type column, leaving only those with the highest count column value in the table. The final data would look like this:
id | type | count
--------------------
2 | A | 29
3 | C | 4 /* `id` for this record might be '5' depending on your query */
7 | B | 14
I feel like I'm close, but I can't seem to wrap my head around a query that works to get rid of the duplicate columns.
count is a sql reserve word so it'll have to be escaped somehow. I can't remember the syntax for doing that in Postgres off the top of my head so I just surrounded it with square braces (change it if that isn't correct). In any case, the following should theoretically work (but I didn't actually test it):
delete from widgets where id not in (
select max(w2.id) from widgets as w2 inner join
(select max(w1.[count]) as [count], type from widgets as w1 group by w1.type) as sq
on sq.[count]=w2.[count] and sq.type=w2.type group by w2.[count]
);
There is a slightly simpler answer than Asaph's, with EXISTS SQL operator :
DELETE FROM widgets AS a
WHERE EXISTS
(SELECT * FROM widgets AS b
WHERE (a.type = b.type AND b.count > a.count)
OR (b.id > a.id AND a.type = b.type AND b.count = a.count))
EXISTS operator returns TRUE if the following SQL statement returns at least one record.
According to your requirements, seems to me that this should work:
DELETE
FROM widgets
WHERE type NOT IN
(
SELECT type, MAX(count)
FROM widgets
GROUP BY type
)