MySQl count grouping by 4 columns - mysql

Basically, this query returns me different values from counts()
Geographic Address(city),Office,Device type, Device unique type identifier, number case by device type
0001,1002,ORDENADOR,ORD1234,5 INCIDENCIAS
0001,1002,ORDENADOR,ORD3333,2 INCIDENCIAS
0001,1002,ORDENADOR,ORD2222,1 INCIDENCIAS
0001,1002,TECLADO,TECYYYY,2 INCIDENCIAS
0001,1002,TECLADO,TECXXXX,4 INCIDENCIAS
0001,1002,PANTALLA,PAN0000,1 INCIDENCIAS
Select
d.dt as 'Direccion Territorial',
t.centro as 'Oficina',
nombrelargo,
if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina) as 'Oficina2',
p.Tipo_Disp as 'Dispositivo',
count(p.Tipo_Disp) as 'Nº de partes/Etiqueta',
p.Etq_Amarilla as 'Etiqueta',
------------ count(TOTAL INC DE ESE DISPOSITIVO) ---------------------------,
------------ count(TOTAL INC DE ESA OFICINA) ---------------------------
from textcentro t,dtdz d,ppp p
where
t.jcentro03=d.dt and
t.organizativo='OFIC./AGEN./DELEG.' and
t.situacion='ABIERTO' and
t.sociedad='0900' and
(p.Estado != "Abierto" and p.Estado!= 'Planificado') and
(month(p.Fecha_y_hora_de_creacion) = 8 and year(Fecha_y_hora_de_creacion)=2013) and
t.centro=if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina)
GROUP BY d.dt,t.centro,p.Tipo_Disp,p.Etq_Amarilla
The grouping:
1 - d.dt ----> Postal code
2 - t.centro ----> Office code
3 - p.Tipo_Disp ----> Device Type
4 - d.Etq_Amarilla ----> Unique identifier for this device
The tables are :
1- textcentro ----> Specific information of the offices
2- dtdz ----> auxiliary table to find the Postal Code of the office
3- ppp ----> Table where we can find all the cases
So now, I want to sum the total number of cases by device type, should be this:
Postal Code,Office,Device type, Unique identifier for Device, total number of cases by unique identifier device, total number case by device type, total number case by office
0001,1002,ORDENADOR,ORD1234,5 INCIDENCIAS,8 INC,15
0001,1002,ORDENADOR,ORD3333,2 INCIDENCIAS,8 INC,15
0001,1002,ORDENADOR,ORD2222,1 INCIDENCIAS,8 INC,15
0001,1002,TECLADO,TECYYYY,2 INCIDENCIAS,6 INC,15
0001,1002,TECLADO,TECXXXX,4 INCIDENCIAS,6 INC,15
0001,1002,PANTALLA,PAN0000,1 INCIDENCIAS,1 INC,15
I'm trying with sums and counts functions but i dont reach it, i don't have any way to take the last two columns. I think that i can try to take this number by sub-query in the column but the performance will be down too much.
The example would be this... but even i get to finish the query and im waiting around 12-13 minutes.
Select
d.dt as 'Direccion Territorial',
t.centro as 'Oficina',
nombrelargo,
if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina) as 'Oficina2',
p.Tipo_Disp as 'Dispositivo',
count(p.Tipo_Disp) as 'Nº de partes/Etiqueta',
p.Etq_Amarilla as 'Etiqueta',
(Select count(*) from People_DB pp where pp.Oficina=p.Oficina and pp.Tipo_Disp=Dispositivo and (month(pp.Fecha_y_hora_de_creacion) = 8 and year(pp.Fecha_y_hora_de_creacion)=2013) and (pp.Estado != "Abierto" and pp.Estado!= 'Planificado') )
from textcentro t,dtdz d,ppp p
where
t.jcentro03=d.dt and
t.organizativo='OFIC./AGEN./DELEG.' and
t.situacion='ABIERTO' and
t.sociedad='0900' and
(p.Estado != "Abierto" and p.Estado!= 'Planificado') and
(month(p.Fecha_y_hora_de_creacion) = 8 and year(Fecha_y_hora_de_creacion)=2013) and
t.centro=if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina)
GROUP BY d.dt,t.centro,p.Tipo_Disp,p.Etq_Amarilla
Sorry for my poor english, maybe this post is unintelligible

May I make some suggestions:
First, your choice of tables looks like this:
from textcentro t,dtdz d,ppp p
For the sake of clarity I suggest you employ explicit JOIN statements instead. For example
FROM textcentro AS t
JOIN dtdx AS d ON t.jcentro03=d.dt
JOIN ppp AS p ON XXXXXXXXX
You may want to use LEFT JOIN in cases for example, where there might be no corresponding row in dtdx to go with a row in textcentro.
I cannot tell from your sample query what the ON constraint for the JOIN to ppp should be. I have shown that with XXXXXXXXX in my code above. I think your condition is this:
t.centro=if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina)
but that is a nasty expression to compute, and therefore very slow. It looks like your t.centro is a char column containing an integer with leading zeros, and your p.Oficina is the same but without the leading zeros. Instead of adding the leading zero to p.Oficina, try stripping it from the t.centro column.
CAST(t.centro AS INTEGER) = p.Oficina
Keep in mind that without a simple JOIN constraint you get a combinatorial explosion: m times n rows. This makes things slow and possibly wrong.
So, your table selection becomes:
FROM textcentro AS t
JOIN dtdx AS d ON t.jcentro03=d.dt
JOIN ppp AS p ON CAST(t.centro AS INTEGER) = p.Oficina
Second, your date/time search expressions are not built for speed. Try this:
p.Fecha_y_hora_de_creacion >= '2013-08-01'
AND p.Fecha_y_hora_de_creacion < '2013-08-01' + INTERVAL 1 MONTH
If you have an index on your p.Fecha... column, this will permit a range-scan search on that column.
Third, this item in your SELECT list is killing performance.
(Select count(*)
from People_DB pp
where pp.Oficina=p.Oficina
and pp.Tipo_Disp=Dispositivo
and (month(pp.Fecha_y_hora_de_creacion) = 8
and year(pp.Fecha_y_hora_de_creacion)=2013)
and (pp.Estado != "Abierto" and pp.Estado!= 'Planificado') )
Refactor this to be a virtual table in your JOIN list, as follows.
(SELECT COUNT(*) AS NumPersonas,
Oficina,
Tipo_Disp
FROM People_DB
WHERE Fecha_y_hora_de_creacion >= '2013-08-01'
AND Fecha_y_hora_de_creacion < '2013-08-01' + INTERVAL 1 MONTH
AND Estado != 'Abierto'
AND Estado != 'Planificado
GROUP BY Oficina, Tipo_Disp
) AS pp_summary ON ( pp_summary.Oficina=p.Oficina
AND pp_summary.Tipo_Disp=Dispositivo)
So, this is your final list of tables.
FROM textcentro AS t
JOIN dtdx AS d ON t.jcentro03=d.dt
JOIN ppp AS p ON CAST(t.centro AS INTEGER) = p.Oficina
JOIN (
SELECT COUNT(*) AS NumPersonas,
Oficina,
Tipo_Disp
FROM People_DB
WHERE Fecha_y_hora_de_creacion >= '2013-08-01'
AND Fecha_y_hora_de_creacion < '2013-08-01' + INTERVAL 1 MONTH
AND Estado != 'Abierto'
AND Estado != 'Planificado
GROUP BY Oficina, Tipo_Disp
) AS pp_summary ON ( pp_summary.Oficina=p.Oficina
AND pp_summary.Tipo_Disp=Dispositivo)
Three of these tables are "physical" tables, and the fourth is a "virtual" table, constructed as a summary of the physical table called People_DB.
You can include
pp_summary.NumPersonas
in your SELECT list.
Fourth, avoid the nonstandard extensions to MySQL GROUP BY functionality, and use standard SQL. Read this for more information.
http://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html
Fifth, add appropriate indexes to your tables.

Related

Aggregating row values in MySQl or Snowflake

I would like to calculate the std dev. min and max of the mer_data array into 3 other fields called std_dev,min_mer and max_mer grouped by mac and timestamp.
This needs to be done without flattening the data as each mer_data row consists of 4000 float values and multiplying that with 700k rows gives a very high dimensional table.
The mer_data field is currently saved as varchar(30000) and maybe Json format might help, I'm not sure.
Input:
Output:
This can be done in Snowflake or MySQL.
Also, the query needs to be optimized so that it does not take much computation time.
While you don't want to split the data up, you will need to if you want to do it in pure SQL. Snowflake has no problems with such aggregations.
WITH fake_data(mac, mer_data) AS (
SELECT * FROM VALUES
('abc','43,44.25,44.5,42.75,44,44.25,42.75,43'),
('def','32.75,33.25,34.25,34.5,32.75,34,34.25,32.75,43')
)
SELECT f.mac,
avg(d.value::float) as avg_dev,
stddev(d.value::float) as std_dev,
MIN(d.value::float) as MIN_MER,
Max(d.value::float) as Max_MER
FROM fake_data f, table(split_to_table(f.mer_data,',')) d
GROUP BY 1
ORDER BY 1;
I would however discourage the use of strings in the grouping process, so would break it apart like so:
WITH fake_data(mac, mer_data, timestamp) AS (
SELECT * FROM VALUES
('abc','43,44.25,44.5,42.75,44,44.25,42.75,43', '01-01-22'),
('def','32.75,33.25,34.25,34.5,32.75,34,34.25,32.75,43', '02-01-22')
), boost_data AS (
SELECT seq8() as seq, *
FROM fake_data
), math_step AS (
SELECT f.seq,
avg(d.value::float) as avg_dev,
stddev(d.value::float) as std_dev,
MIN(d.value::float) as MIN_MER,
Max(d.value::float) as Max_MER
FROM boost_data f, table(split_to_table(f.mer_data,',')) d
GROUP BY 1
)
SELECT b.mac,
m.avg_dev,
m.std_dev,
m.MIN_MER,
m.Max_MER,
b.timestamp
FROM boost_data b
JOIN math_step m
ON b.seq = m.seq
ORDER BY 1;
MAC
AVG_DEV
STD_DEV
MIN_MER
MAX_MER
TIMESTAMP
abc
43.5625
0.7529703087
42.75
44.5
01-01-22
def
34.611111111
3.226141056
32.75
43
02-01-22
performance testing:
so using this SQL to make 70K rows of 4000 values each:
create table fake_data_tab AS
WITH cte_a AS (
SELECT SEQ8() as s
FROM TABLE(GENERATOR(ROWCOUNT =>70000))
), cte_b AS (
SELECT a.s, uniform(20::float, 50::float, random()) as v
FROM TABLE(GENERATOR(ROWCOUNT =>4000))
CROSS JOIN cte_a a
)
SELECT s::text as mac
,LISTAGG(v,',') AS mer_data
,dateadd(day,s,'2020-01-01')::date as timestamp
FROM cte_b
GROUP BY 1,3;
takes 79 seconds on a XTRA_SMALL,
now with that we can test the two solutions:
The second set of code (group by numbers, with a join):
WITH boost_data AS (
SELECT seq8() as seq, *
FROM fake_data_tab
), math_step AS (
SELECT f.seq,
avg(d.value::float) as avg_dev,
stddev(d.value::float) as std_dev,
MIN(d.value::float) as MIN_MER,
Max(d.value::float) as Max_MER
FROM boost_data f, table(split_to_table(f.mer_data,',')) d
GROUP BY 1
)
SELECT b.mac,
m.avg_dev,
m.std_dev,
m.MIN_MER,
m.Max_MER,
b.timestamp
FROM boost_data b
JOIN math_step m
ON b.seq = m.seq
ORDER BY 1;
takes 1m47s
the original group by strings/dates
SELECT f.mac,
avg(d.value::float) as avg_dev,
stddev(d.value::float) as std_dev,
MIN(d.value::float) as MIN_MER,
Max(d.value::float) as Max_MER,
f.timestamp
FROM fake_data_tab f, table(split_to_table(f.mer_data,',')) d
GROUP BY 1,6
ORDER BY 1;
takes 1m46s
Hmm, so leaving the "mac" as a number made the code very fast (~3s), and dealing with strings in ether way changed the data processed from 1.5GB for strings and 150MB for numbers.
If the numbers were in rows, not packed together like that, we can discuss how to do it in SQL.
In rows, GROUP_CONCAT(...) can construct a commalist like you show, and MIN(), STDDEV(), etc can do the other stuff.
If you continue to have the commalist, the do the rest of work in you app programming language. (It is very ugly to have SQL pick apart an array.)

How select count distinct (unique truckers) without group by function and maybe without using Having (not sure about last)

I have a task, but couldn't solve it:
There are truckers and they have to travel between cities.
We have data of these travels in our database in 2 tables:
trucker_traffic
tt_id (key)
date
starting_point_coordinate
destination_coordinate
traveller_id
event_type ('travel', 'accident')
parent_event_id (For 'accident' event type it's tt_id of the original travel. There might be few accidents within one travel.)
trucker_places
coordinate (key)
country
city
I need SQL query to pull the number of all unique truckers who travelled more than once from or to London city in June 2020.
In the same query pull the number of these travels who got into an accident.
Example of my tries
SELECT
count(distinct(tt.traveller_id)),
FROM trucker_traffic tt
JOIN trucker_places tp
ON tt.starting_point_coordinate = tp.coordinate
OR tt.destination_coordinate = tp.coordinate
WHERE
tp.city = 'London'
AND month(tt.date) = 6
AND year(tt.date) = 2020
GROUP BY tt.traveller_id
HAVING count(tt.tt_id) > 1
But it's select count distinct truckers with grouping and works only if I had one tracker in db
For second part of task (where I have select number of travels with accident - I think that good to use function like this
SUM(if(count(tt_id = parent_event_id),1,0))
But I'm not sure
This is rather complicated, so make sure you do this step by step. WITH clauses help with this.
Steps
Find travels from and to London in June 2020. You can use IN or EXISTS in order to see whether a travel had accidents.
Group the London travels by traveller, count travels and accident travels and only keep those travellers with more than one travel.
Take this result set to count the travellers and sum up their travels.
Query
with london_travels as
(
select
traveller_id,
case when tt_id in
(select parent_event_id from trucker_traffic where event_type = 'accident')
then 1 else 0 end as accident
from trucker_traffic tt
where event_type = 'travel'
and month(tt.date) = 6
and year(tt.date) = 2020
and exists
(
select
from trucker_places tp
where tp.coordinate in (tt.starting_point_coordinate, tt.destination_coordinate)
and tp.city = 'London'
)
)
, london_travellers as
(
select
traveller_id,
count(*) as travels,
sum(accident) as accident_travels
from london_travels
group by traveller_id
having count(*) > 1;
)
select
count(*) as total_travellers,
sum(travels) as total_travels,
sum(accident_travels) as total_accident_travels
from london_travellers;
If your MySQL version doesn't support WITH clauses, you can of course just nest the queries. I.e.
with a as (...), b as (... from a) select * from b;
becomes
select * from (... from (...) a) b;
You say in the request title that you don't want GROUP BY in the query. This is possible, but makes the query more complicated. If you want to do this I leave this as a task for you. Hint: You can select travellers and count in subqueries per traveller.

How to select sql with if else use column

I'm beginner for mysql and this my graduation project please help me.
//AMOUNT_PEOPLE is variable in nodejs
//place_id is variable in nodejs recieve from front end.
SELECT
(IF AMOUNT_PEOPLE >= 10
RETURN COLUMN package_tb.price_group*(AMOUNT_PEOPLE-1)
ELSE IF AMOUNT_PEOPLE >= 6
RETURN COLUMN package_tb.price_group*(AMOUNT_PEOPLE-1) - (SELECT option_tb.price_group FROM option_tb WHERE obtion_tb.place_id = place_id)
ElSE
RETURN COLUMN price_normal*AMOUNT_PEOPLE
END IF) AS price,name,detail
FROM package_tb WHERE package_tb.place_id = place_id
This ticket booking program
Logic is
Check number of tourist
if tourist >= 10 must use group_price and free 1 person include food for free person option
but if tourist >= 6 must use group_price
and free 1 person but not include food for free person option
finally tourist 0-5 must use normal_price
Such customer tell me "I want ticket for 10 tourist" the system will check as above explain.
package_tb
-package_id
-place_id
-name
-detail
-price_group
-price_normal
option_tb
-option_id
-place_id
-name
-price_group
-price_normal
place_tb
-place_id
-name
If tourist use price group option have to use price group only
But tourist use price normal option have to use price normal only.
Sorry for my bad english.
There exists a CASE function.
Which is a standard SQL function for switch logic.
Based on the updated question I'm assuming that there can be multiple options per place.
So with node.js variables:
SET #place_id = ${PLACE_ID};
SET #amount_people = ${AMOUNT_PEOPLE};
SELECT
CASE
WHEN #amount_people >= 10
THEN (p.price_group * (#amount_people - 1))
WHEN #amount_people >= 6
THEN (p.price_group * (#amount_people - 1)) - SUM(o.price_group)
ELSE p.price_normal
END AS price,
p.name,
p.detail
FROM package_tb p
LEFT JOIN option_tb o ON o.place_id = p.place_id
WHERE p.place_id = #place_id
GROUP BY p.package_id, p.place_id, p.price_normal, p.price_group, p.name, p.detail;
A test on rextester here
-- From which table you want use price_normal? tb or o ???
SELECT
CASE WHEN p.AMOUNT_PEOPLE >= 4
THEN p.price_group
WHEN p.AMOUNT_PEOPLE >= 3
THEN p.price_group - o.price_food
ELSE tb.price_normal
END AS price,
p.name, p.detail
FROM package_tb p JOIN option_tb o ON o.package_id = p.package_id;
There are two main control-flow functions in MySQL IF() and CASE
Since you are not comparing the value of AMOUNT_PEOPLE directly, CASE is a bit of overkill, which can be simplified slightly by using IF.
The syntax for IF is IF(<expr>, <true_result>, <false_result>). This allows you to perform else if by chaining another IF() as the false_result
IF(<expr>, ..., IF(<expr>, ..., ...))
Instead of using else if, you only need to remove the option_tb.price_group when AMOUNT_PEOPLE is fewer than 10 to get your desired pricing.
/* groups with 6 or more use group price and one person free */
IF(AMOUNT_PEOPLE >= 6,
/* groups with fewer than 10 people remove option */
p.price_group*(AMOUNT_PEOPLE-1) - IF(AMOUNT_PEOPLE < 10,
o.price_group,
0
),
p.price_normal*AMOUNT_PEOPLE
) AS price
This reduces the amount of code slightly, to determine when to subtract a person.
Instead of using a nested sub-query, which would be executed for each row returned. If the option_tb.place_id is unique, a JOIN would be more preferable.
If option_tb.place_id is not unique, you would need to use a GROUP BY. One approach is to JOIN using a sub-query, to avoid false matching on the join table groupings.
To ensure results are not excluded when a row in the option_tb table fails to match a place_id, you would use a LEFT JOIN that returns NULL instead of excluding the row.
Then you can use COALESE(<column>, 0) to retrieve the value from the column or 0 if the column value is NULL.
In NodeJS you can use ${var} to inject a variable into a string.
For example:
var place_id = 1;
var query = 'SELECT ${place_id};';
console.log(query);
Results in
SELECT 1;
Putting it all together.
Example: db-fiddle
SELECT
IF(${AMOUNT_PEOPLE} >= 6,
p.price_group*(${AMOUNT_PEOPLE}-1) - IF(${AMOUNT_PEOPLE} < 10, COALESCE(o.price_group, 0), 0),
p.price_normal*${AMOUNT_PEOPLE}
) AS price,
p.name,
p.detail
FROM package_tb AS p
LEFT JOIN (
SELECT
place_id,
SUM(price_group) AS price_group
FROM option_tb
GROUP BY place_id
) AS o
ON o.place_id = p.place_id
WHERE p.place_id = ${place_id};

UNION in MySQL 5.7.2

I'm using MySQL 5.7.
I am getting bad results by a UNION of COUNT(*).
SELECT
COUNT(*) AS Piezas
, ''Motor
from parque
where `parque`.`CausasParalizacion` = 2
UNION
SELECT
''Piezas
, COUNT(*) AS Motor
from parque
where `parque`.`CausasParalizacion` = 3
The result should be 30 and 12, and I am getting 3330 and 3132.
Can anyone help?
I don't think MySQL is returning a "bad" result. The results returned by MySQL are per the specification.
Given no GROUP BY each of the SELECT statements will return one row. We can verify by running each SELECT statement separately. We'd expect the UNION result of the two SELECT to be something like
Piezas Motor
------ -----
mmm
ppp
You say the results should be '30' and '12'
My guess is that MySQL is returning the characters '30' and '12'.
But we should be very suspicious, and note the hex representation of the ASCII encoding of those characters
x'30' -> '0'
x'31' -> '1'
x'32' -> '2'
x'33' -> '3'
As a demonstration
SELECT HEX('30'), HEX('12')
returns
HEX('30') HEX('12')
--------- ---------
3330 3132
I don't think MySQL is returning "bad" results. I suspect that the column metadata for the columns is confusing the client. (We do note that both of the columns is a mix of two different datatypes being UNION'd. On one row, the datatype is string/varchar (an empty string), and the other row is integer/numeric (result of COUNT() aggregate.)
And I'm not sure what the resultset metadata for the columns ends up as.
I suspect that the issue with the client interpretation the resultset metadata, determining the datatype of the columns. And the client is deciding that the most appropriate way to display the values is as a hex representation of the raw bytes.
Personally, I would avoid returning a UNION result of different/incompatible datatypes. I'd prefer the datatypes be consistent.
If I had to do the UNION of incompatible datatypes, I would include an explicit conversion into compatible/appropriate datatypes.
But once I am at that point, I have to question why I need any of that rigmarole with the mismatched datatypes, why we need to return two separate rows, when we could just return a single row (probably more efficiently to boot)
SELECT SUM( p.`CausasParalizacion` = 2 ) AS Piezas
, SUM( p.`CausasParalizacion` = 3 ) AS Motor
FROM parque p
WHERE p.`CausasParalizacion` IN (2,3)
To avoid the aggregate functions returning NULL,
we can wrap the aggregate expressions in an IFNULL (or ANSI-standard COALESCE) function..
SELECT IFNULL(SUM( p.`CausasParalizacion` = 2 ),0) AS Piezas
, IFNULL(SUM( p.`CausasParalizacion` = 3 ),0) AS Motor
FROM parque p
WHERE p.`CausasParalizacion` IN (2,3)
-or-
we could use a COUNT() of an expression that is either NULL or non-NULL
SELECT COUNT(IF( p.`CausasParalizacion` = 2 ,1,NULL) AS Piezas
, COUNT(IF( p.`CausasParalizacion` = 3 ,1,NULL) AS Motor
FROM parque p
WHERE p.`CausasParalizacion` IN (2,3)
If, for some reason it turns out it is faster to run two separate SELECT statements, we could still combine the results into a single row. For example:
SELECT s.Piezas
, t.Motor
FROM ( SELECT COUNT(*) AS Piezas
FROM parque p
WHERE p.`CausasParalizacion` = 2
) s
CROSS
JOIN ( SELECT COUNT(*) AS Motor
FROM parque q
WHERE q.`CausasParalizacion` = 3
) t
Spencer, I think that the problem was about encoding. Ej. When I execute the consult in console, the result was the expected, the otherwise in the phpmyadmin.
However, I must say that your first solution works perfectly, Thanks a lot bro.

how to search for a given sequence of rows within a table in SQL Server 2008

The problem:
We have a number of entries within a table but we are only interested in the ones that appear in a given sequence. For example we are looking for three specific "GFTitle" entries ('Pearson Grafton','Woolworths (P and O)','QRX - Brisbane'), however they have to appear in a particular order to be considered a valid route. (See image below)
RowNum GFTitle
------------------------------
1 Pearson Grafton
2 Woolworths (P and O)
3 QRX - Brisbane
4 Pearson Grafton
5 Woolworths (P and O)
6 Pearson Grafton
7 QRX - Brisbane
8 Pearson Grafton
9 Pearson Grafton
So rows (1,2,3) satisfy this rule but rows (4,5,6) don't even though the first two entries (4,5) do.
I am sure there is a way to do this via CTE's but some help would be great.
Cheers
This is very simple using even good old tools :-) Try this quick-and-dirty solution, assuming your table name is GFTitles and RowNumber values are sequential:
SELECT a.[RowNum]
,a.[GFTitle]
,b.[GFTitle]
,c.[GFTitle]
FROM [dbo].[GFTitles] as a
join [dbo].[GFTitles] as b on b.RowNumber = a.RowNumber + 1
join [dbo].[GFTitles] as c on c.RowNumber = a.RowNumber + 2
WHERE a.[GFTitle] = 'Pearson Grafton' and
b.[GFTitle] = 'Woolworths (P and O)' and
c.[GFTitle] = 'QRX - Brisbane'
Assuming RowNum has neither duplicates nor gaps, you could try the following method.
Assign row numbers to the sought sequence's items and join the row set to your table on GFTitle.
For every match, calculate the difference between your table's row number and that of the sequence. If there's a matching sequence in your table, the corresponding rows' RowNum differences will be identical.
Count the rows per difference and return only those where the count matches the number of sequence items.
Here's a query that implements the above logic:
WITH SoughtSequence AS (
SELECT *
FROM (
VALUES
(1, 'Pearson Grafton'),
(2, 'Woolworths (P and O)'),
(3, 'QRX - Brisbane')
) x (RowNum, GFTitle)
)
, joined AS (
SELECT
t.*,
SequenceLength = COUNT(*) OVER (PARTITION BY t.RowNum - ss.RowNum)
FROM atable t
INNER JOIN SoughtSequence ss
ON t.GFTitle = ss.GFTitle
)
SELECT
RowNum,
GFTitle
FROM joined
WHERE SequenceLength = (SELECT COUNT(*) FROM SoughtSequence)
;
You can try it at SQL Fiddle too.