Select MySQL Group By - mysql

I have a table chat in mysql, it this the record of conversations between users.
The fields are.
id, id_from, id_to, message
Example (My Data in DB):
1, 50, 10, 'Hello'
2, 10, 50 'Nice?'
3, 50, 10, 'Yeah, And you?'
4, 10, 50, 'Me too'
5, 1, 20, 'Hello'
6, 20, 1, 'Nice?'
7, 1, 20, 'Yeah, And you?'
8, 20, 1, 'Me too'
8, 50, 1, 'Hey .....'
9, 1, 50, "Enhancements ??? '
10, 50, 1 ':)'
11, 1, 50, 'LOL'
[User]
id
name
id = Paul 50
id = 1 Samuel
id = Donald 20
id = 10 Max
What I need is via SELECT, return the last conversation that took place between people.
For example, in my select, I want to know the last two conversations (showing only the last message of each conversation) that id had 50 (Paul).
How Poderi do this via MySQL?
The result would be like this:
4, 10, 50, 'Me too'
11, 1, 50, 'LOL'

You can get the ids of the messages using:
select least(id_from, id_to), greatest(id_from, id_to), max(id)
from messages m
where 50 in (id_from, id_to)
group by least(id_from, id_to), greatest(id_from, id_to)
To get the messages, you can use in or join. Here is one method:
select m.*
from messages m
where m.id in (select max(m2.id)
from messages m2
where 50 in (m2.id_from, m2.id_to)
group by least(m2.id_from, m2.id_to), greatest(m2.id_from, m2.id_to)
);

Related

join multiple subqueries that returns one or more rows in vertical table

I have large MySQL vertical table named 'profile_features' like this:
id
Profile_id
feature_id
value
1
1
1
Rick
2
1
2
Novak
3
5
3
5428
4
5
1
Joe
...
...
...
...
(above table is short part of all data)
how can I find specific Profile_ids that have ALL below conditions:
(meeting the 1st AND Second And Third condition).
and I want all profile_ids as result.
profile_id FROM profile_features WHERE( feature_id IN(2, 64, 90, 38, 73, 115) AND value ='Joe')
AND
profile_id FROM profile_features WHERE( feature_id IN(1, 55, 86, 23, 72, 114) AND value ='US')
AND
profile_id FROM profile_features WHERE( feature_id IN(4, 59, 98, 43, 78, 120) AND value ='54782')
Aggregate by profile and then use three separate assertions in the HAVING clause for your criteria:
SELECT profile_id
FROM profile_features
GROUP BY profile_id
HAVING SUM(feature_id IN (2, 64, 90, 38, 73, 115) AND value = 'Joe') > 0 AND
SUM(feature_id IN (1, 55, 86, 23, 72, 114) AND value = 'US') > 0 AND
SUM(feature_id IN (4, 59, 98, 43, 78, 120) AND value = '54782') > 0;

Count consecutive rows in mySQL 5.6

I'm trying to figure out counting the consecutive values row after row.
I have a mySQL / php website.
Based on Racing league results table, I have such example data:
(race id, season number, track number, first (p1), second (p2).... last (p2) filled with pilot ID).
id, season, track, p1, p2.... p20
1, 1, 1, 1, 4, .... 15
2, 1, 2, 3, 5, .... 15
3, 1, 3, 1, 4, .... 15
4, 1, 4, 1, 2, .... 15
5, 1, 5, 1, 4, .... 15
6, 1, 6, 2, 2, .... 15
7, 1, 7, 2, 4, .... 15
8, 1, 8, 1, 2, .... 15
As you can see, pilotID n.1 won 3 times consecutively, from race 3 to race 5, while pilotID n.2 won 2 times consecutively, from race 6 to race 7.
My goal output table would be:
count, pilot, fromRaceID, toRaceID
3, 1, 3, 5
2, 2, 6, 7
ordered by count.
I know it is complicated, and even mightn't be possible; I found major difficulties trying to do so, therefore your help would be extremely appreciated.
This is a type of gaps-and-islands. Subtract a sequence number and the "adjacent" values will be constant:
select p1, min(trackid), max(trackid), count(*)
from (select t.*,
row_number() over (partition by p1 order by track_number) as seqnum
from t
) t
group by p1, (track_number - seqnum)
having count(*) > 1;
EDIT:
In older versions of MySQL, you can use:
select p1, min(trackid), max(trackid), count(*)
from (select t.*,
(select count(*)
from t t2
where t2.p1 = t.p1 and t2.track_number <= t.track_number
) as seqnum
from t
) t
group by p1, (track_number - seqnum)
having count(*) > 1;

How to limit results of SQLite per specific group of results?

I have the following problem at work. I have a large table with different columns and few 100 000s of rows. I'll only post the ones im interested in.
Assume the following data set
Device ID, Feature Id, Feature Status
1, 1, 0
1, 2, 0
1, 3, 1
1, 4, 1
1, 5, 1
2, 1, 1
2, 2, 0
2, 3, 0
2, 4, 1
2, 5, 0
3, 1, 1
3, 2, 1
3, 3, 1
3, 4, 1
3, 5, 1
4, 1, 0
4, 2, 0
4, 3, 1
4, 4, 0
4, 5, 0
I need to select rows with Feature Status = 1 but only the first 2 from each Device Id.
The results of the query should be:
1,3,1
1,4,1
2,1,1
2,4,1
3,1,1
3,2,1
4,3,1
I tried something like this:
SELECT brdsurfid,featureidx,FeatStatus FROM Features F1 WHERE FeatStatus = 1 AND
(SELECT COUNT(*) FROM Features F2
WHERE F2.FeatureIdx <= F1.FeatureIdx AND F2.FeatStatus = 1) < 2
ORDER BY BrdSurfId,FeatureIdx;
which I found in another response but it didnt quite work.
I know I need to use a mix of LIMIT or COunt(*) and some nested selects but I can't figure it out. Thanks
This probably not a very efficient way to do this, but I don't think there is a better solution for sqlite (that involves a single query):
SELECT *
FROM t t0
WHERE FeatureStatus AND
(SELECT count(*)
FROM t t1
WHERE t0.DeviceID=t1.DeviceID
AND FeatureStatus
AND t1.FeatureId<t0.FeatureId
)<2;
I assume that the table is called t. The idea is to find all features where the status is 1 and then for each feature to count the previous features with that status for the same product. If that count is more than 2, then reject the row.
Not sure if this will work with sqlite but for what its worth...
;with result as
(
SELECT
brdsurfid,
featureidx,
FeatStatus ,
ROW_NUMBER() OVER(PARTITION BY brdsurfid ORDER BY fieldWhatever) AS 'someName1',
ROW_NUMBER() OVER(PARTITION BY featureidx ORDER BY fieldWhatever) AS 'someName2',
ROW_NUMBER() OVER(PARTITION BY FeatStatus ORDER BY fieldWhatever) AS 'someName3'
FROM
Features
)
SELECT *
FROM
result
WHERE
FeatStatus = 1 AND
someName1 <= 2 AND
someName2 <= 2 AND
someName3 <= 2

Count up on a positive change, and down on a negative change

I have a column that changes values.
I want to count by adding at each change up and subtracting at each change down. Assuming x[] are my values, Delta is the sign of change in x's elements, and y[] is my targeted results or counts.
We count up until the next delta -1 at which we start counting down, then we resume counting up when delta changes back to +1. In summary we add normally until we have a delta of -1 at that time we start subtracting, then resume adding up at the next +1 delta.
x: 1, 3, 4, 4, 4, 5, 5, 3, 3, 4, 5, 5, 6, 5, 4, 4, 4, 3, 4, 5, 6, 7, 8
Delta: 0, 1, 1, 0, 0, 1, 0, -1, 0, 1, 1, 0, 1, -1, -1, 0, 0, -1, 1, 1, 1, 1, 1
y: 1, 2, 3, 4, 5, 6, 7, 6, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 6, 7, 8, 9
The length of my array is in the millions of rows, and efficiency is important. Not sure if such operation should be done in SQL or whether I would be better off retrieving the data from the database and performing such calculation outside.
You could use this query in SQL-Server, presuming a PK-column for the ordering:
WITH CTE AS
(
SELECT t.ID, t.Value,
LastValue = Prev.Value,
Delta = CASE WHEN Prev.Value IS NULL
OR t.Value > Prev.Value THEN 1
WHEN t.Value = Prev.Value THEN 0
WHEN t.Value < Prev.Value THEN -1 END
FROM dbo.TableName t
OUTER APPLY (SELECT TOP 1 t2.ID, t2.Value
FROM dbo.TableName t2
WHERE t2.ID < t.ID
ORDER BY t2.ID DESC) Prev
)
, Changes AS
(
SELECT CTE.ID, CTE.Value, CTE.LastValue, CTE.Delta,
Change = CASE WHEN CTE.Delta <> 0 THEN CTE.Delta
ELSE (SELECT TOP 1 CTE2.Delta
FROM CTE CTE2
WHERE CTE2.ID < CTE.ID
AND CTE2.Delta <> 0
ORDER BY CTE2.ID DESC) END
FROM CTE
)
SELECT SUM(Change) FROM Changes c
The result is 9 as expected:
complete result set
only Sum
The OUTER APPLY links the current with the previous record, the previous record is the one with the highest ID < current.ID. It works similar to a LEFT OUTER JOIN.
The main challenge was the sub-query in the last CTE. That is necessary to find the last delta that is <> 0 to determine if the current delta is positive or negative.
You can also use LAG and SUM with OVER (Assuming you have SQL Server 2012 or above) like this.
Sample Data
DECLARE #Table1 TABLE (ID int identity(1,1), [x] int);
INSERT INTO #Table1([x])
VALUES (1),(3),(4),(4),(4),(5),(5),(3),(3),(4),(5),(5),(6),(5),(4),(4),(4),(3),(4),(5),(6),(7),(8);
Query
;WITH T1 as
(
SELECT ID,x,ISNULL(LAG(x) OVER(ORDER BY ID ASC),x - 1) as PrevVal
FROM #Table1
), T2 as
(
SELECT ID,x,PrevVal,CASE WHEN x > PrevVal THEN 1 WHEN x < PrevVal THEN -1 ELSE 0 END as delta
FROM T1
)
SELECT ID,x,SUM(COALESCE(NULLIF(T2.delta,0),TI.delta,0))OVER(ORDER BY ID) as Ordered
FROM T2 OUTER APPLY (SELECT TOP 1 delta from T2 TI WHERE TI.ID < T2.ID AND TI.x = T2.x AND TI.delta <> 0 ORDER BY ID DESC) as TI
ORDER BY ID
Output
ID x Ordered
1 1 1
2 3 2
3 4 3
4 4 4
5 4 5
6 5 6
7 5 7
8 3 6
9 3 5
10 4 6
11 5 7
12 5 8
13 6 9
14 5 8
15 4 7
16 4 6
17 4 5
18 3 4
19 4 5
20 5 6
21 6 7
22 7 8
23 8 9
You use sql-server and mysql tag. If this can be done within SQL-Server you should have a look on the OVER-clause: https://msdn.microsoft.com/en-us/library/ms189461.aspx
Assuming there's an ordering criteria it is possible to state a ROW-clause and use the value of a preceeding row. Many SQL-functions allow the usage of OVER.
You could define a computed column which does the calculation on insert...
Good luck!

mysql join result if only have all features

I have a checkbox with all features. A users select some features.
How can I do to list all persons with all the features selected.
person
- id
- name
features
- id
- name
person_feature
- person_fk
- feature_fk
For example only the persons that have features 1,5 and 9
Not persons with feature 1 and person with feature 5... only with this features at the same time
Confirm... Exclusively what provided, or a MINIMUM OF. Ex: if user wants features 1, 5, 9 but some people have
(1, 2, 5, 8, 9),
(1, 5, 6, 7, 9),
(1, 5, 9, 12)...
these would all be considered ok as they have the MINIMUM of the 1, 5 and 9 that you are looking for.
select
pf.person_fk,
p.name
from
person_feature pf
join person p
on pf.person_fk = p.id
group by
pf.person_fk,
p.name
having
sum( case when pf.feature_fk in ( 1, 5, 9 ) then 1 else 0 end ) >= 3
If you ONLY want those with exactly 3, just change >= to just =