The behaviour I want to replicate is like grep with -A and -B flags .
eg grep -A 2 -B 2 "hello" myfile.txt will give me all the lines which have "hello" in them, but also 2 lines before and 2 lines after it.
Lets assume this table schema :
+--------+-------------------------+
| id | message |
+--------+-------------------------+
| 1 | One tow three |
| 2 | No error in this |
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
| 8 | social game |
| 9 | stackoverflow |
|10 | stackexchange |
+------------+---------------------+
Now a query like :
Select * from theTable where message like '%hello%' will result in :
5 | hello world
How can I put another parameter "N" which selects N rows before, and N rows after the matched record i.e. for N = 2, the result should be :
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
For simplicity assume 'like %TERM%' matches only 1 row .
Here the result is supposed to be sorted on auto-increment id field.
Right, this works for me:
SELECT child.*
FROM stack as child,
(SELECT idstack FROM stack WHERE message LIKE '%hello%') as parent
WHERE child.idstack BETWEEN parent.idstack-2 AND parent.idstack+2;
Don't know if this is at all valid MySQL but how about
SELECT t.*
FROM theTable t
INNER JOIN (
SELECT id FROM theTable where message like '%hello%'
) id ON id.id <= t.id
ORDER BY
ID DESC
LIMIT 3
UNION ALL
SELECT t.*
FROM theTable t
INNER JOIN (
SELECT id FROM theTable where message like '%hello%'
) id ON id.id > t.id
ORDER BY
ID
LIMIT 2
Try this simple one (edited) -
CREATE TABLE messages(
id INT(11) DEFAULT NULL,
message VARCHAR(255) DEFAULT NULL
);
INSERT INTO messages VALUES
(1, 'One tow three'),
(2, 'No error in this'),
(3, 'My testing message'),
(4, 'php module test'),
(5, 'hello world'),
(6, 'team spirit'),
(7, 'puzzle game'),
(8, 'social game'),
(9, 'stackoverflow'),
(10, 'stackexchange');
SET #text = 'hello world';
SELECT id, message FROM (
SELECT m.*, #n1:=#n1 + 1 num, #n2:=IF(message = #text, #n1, #n2) pos
FROM messages m, (SELECT #n1:=0, #n2:=0) n ORDER BY m.id
) t
WHERE #n2 >= num - 2 AND #n2 <= num + 2;
+------+--------------------+
| id | message |
+------+--------------------+
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
+------+--------------------+
N value can be specified as user variable; currently it is - '2'.
This query works with row numbers, and this guarantees that the nearest records will be returned.
Try
Select * from theTable
Where id >=
(Select id - variableHere from theTable where message like '%hello%')
Order by id
Limit (variableHere * 2) + 1
(MS SQL Server only)
The most reliable way would be to use the row_number function that way it doesn't matter if there are gaps in the id. This will also work if there are multiple occurances of the search result and properly return two above and below each result.
WITH
srt AS (
SELECT ROW_NUMBER() OVER (ORDER BY id) AS int_row, [id]
FROM theTable
),
result AS (
SELECT int_row - 2 AS int_bottom, int_row + 2 AS int_top
FROM theTable
INNER JOIN srt
ON theTable.id = srt.id
WHERE ([message] like '%hello%')
)
SELECT theTable.[id], theTable.[message]
FROM theTable
INNER JOIN srt
ON theTable.id = srt.id
INNER JOIN result
ON srt.int_row >= result.int_bottom
AND srt.int_row <= result.int_top
ORDER BY srt.int_row
Adding an answer using date instead of an id.
The use-case here is an on-call rotation table with one record pr week.
Due to edits the id might be out of order for the purpose intended.
Any use-case having several records pr week, pr date or other will of course have to be mended.
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| startdate| datetime | NO | | NULL | |
| person | int(11) | YES | MUL | NULL | |
+----------+--------------+------+-----+---------+----------------+
The query:
SELECT child.*
FROM rota-table as child,
(SELECT startdate
FROM rota-table
WHERE YEARWEEK(startdate, 3) = YEARWEEK(now(), 3) ) as parent
WHERE
YEARWEEK(child.startdate, 3) >= YEARWEEK(NOW() - INTERVAL 25 WEEK, 3)
AND YEARWEEK(child.startdate, 3) <= YEARWEEK(NOW() + INTERVAL 25 WEEK, 3)
Related
I'm trying to get records from the stats table and if there is no data for the specific day and reference get the latest known value for a given ref.
stats table:
| ref | date | views |
| --- | -------- | ----- |
| 1 |2022-01-01|1 |
| 2 |2022-01-01|1 |
| 1 |2022-01-02|2 |
| 2 |2022-01-02|1 |
| 1 |2022-01-03|2 |
| 1 |2022-01-04|3 |
| 2 |2022-01-04|3 |
As you see above there the record for ref 2 at 2022-01-03 is missing.
Now I want to sum views for those records and group them by the ref column and since there is one missing record for ref 2 the value for the summation should be taken from the latest record (2022-01-02).
I have also the posts table:
| id | title |
| --- | --------- |
| 1 |title no. 1|
| 2 |title no. 2|
Also, I have to create a timeline from the oldest stat to the current date.
What do I have is:
WITH RECURSIVE timeline (
date
) AS (
SELECT
MIN(date)
FROM
stats
UNION ALL
SELECT
DATE_ADD(date, INTERVAL 1 day)
FROM
timeline
WHERE (timeline.date < CURRENT_DATE),
posts_days AS (
SELECT timeline.date, posts.id
FROM
posts
CROSS JOIN timeline
),
view_stats AS (
SELECT posts_days.date, posts_days.id, stats.views
FROM
posts_days
LEFT JOIN stats ON (stats.ref = posts_days.id and stats.date = posts_days.date)
)
SELECT
view_stats.date, SUM(view_stats.views) AS views
-- ,SUM(prev_stats.views) AS prev_views,
FROM view_stats
LEFT JOIN (
SELECT id, (view_stats.views) as views, date FROM view_stats GROUP BY id, date
) as prev_stats on prev_stats.date = (
SELECT date FROM view_stats s1
WHERE s1.date < view_stats.date and s1.id = view_stats.id ORDER BY date desc limit 1
) and prev_stats.id = view_stats.id
GROUP BY date
ORDER BY date
But it obviously behaves in the wrong way.
I would be appreciated any tips on how to solve this one.
Assessments table:
+---------------+-----------+---------------------+
| assessment_id | device_id | created_at |
+---------------+-----------+---------------------+
| 1 | 1 | 2022-07-15 20:03:03 |
| 2 | 2 | 2022-07-15 21:03:03 |
| 3 | 1 | 2022-07-15 22:03:03 |
| 4 | 2 | 2022-07-15 23:03:03 |
| 5 | 2 | 2022-07-15 23:03:03 |
+---------------+-----------+---------------------+
Results table:
+---------------+---------+--------+
| assessment_id | test | result |
+---------------+---------+--------+
| 1 | A | PASS |
| 2 | B | FAIL |
| 3 | A | FAIL |
| 4 | B | PASS |
| 5 | B | PASS |
+---------------+---------+--------+
Objective
I would like to return a row for each time the result of a test_id changes.
For example, Device 1 has Assessments 1 and 3. Test A in Assessment 1 was PASS, and Test A in Assessment 3 was FAIL, so we want to return this change as a row.
Device 2 has Assessments 2, 4, and 5. There was a test result change in Assessments 2 and 4 (Test B changed from FAIL to PASS), we want to return a row for this.
We do not want to return a row for Assessment 5 because Test B was PASS, and it was also PASS in Assessment 4. No change.
The resulting table would look like this:
+-----------+---------+------------------------+----------------+----------------------+--------------------+------------+----------------------+
| device_id | test_id | previous_assessment_id | previous_value | previous_value_date | next_assessment_id | next_value | next_value_date |
+-----------+---------+------------------------+----------------+----------------------+--------------------+------------+----------------------+
| 1 | A | 1 | PASS | 15/07/2022 20:03:03 | 3 | FAIL | 15/07/2022 22:03:03 |
| 2 | B | 2 | FAIL | 15/07/2022 21:03:03 | 4 | PASS | 15/07/2022 23:03:03 |
+-----------+---------+------------------------+----------------+----------------------+--------------------+------------+----------------------+
I've tried playing around with a couple of queries found here on SO, but they either took a long time and returned the wrong data, or didn't work at all. I don't think this is a duplicate because I'm using multiple tables, and every other question I've seen refers to a single table.
I've also looked at this SO question but could not get the helpful answer to apply to my situation.
I'm having some weird issue getting SQL Fiddle to work, but here is the test schema I've been tinkering with:
CREATE TABLE `assessments` (
`id` int,
`device_id` int,
`created_at` datetime
);
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (1, 1, '2022-07-09 22:56:00');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (2, 2, '2022-07-10 22:56:06');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (3, 1, '2022-07-11 22:56:11');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (4, 2, '2022-07-12 22:56:17');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (5, 2, '2022-07-13 22:56:24');
CREATE TABLE `results` (
`assessment_id` int,
`test` enum('A','B'),
`result` enum('PASS','FAIL')
);
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (1, 'A', 'PASS');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (2, 'B', 'FAIL');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (3, 'A', 'FAIL');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (4, 'B', 'PASS');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (5, 'B', 'PASS');
If you are using MySQL 8, Window functions can help. https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
You can partition your results by device and test, and add a column that is the previous value of the result, then use the last row where the result differs from the previous value.
The following query creates a new column in your results with previous_value
SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`,
LAG (assessment_id) over w as `previous_assessment_id`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY device_id, test ORDER BY assessment_id)
Yields the result:
+---------------+-----------+------+--------+----------------+------------------------+
| assessment_id | device_id | test | result | previous_value | previous_assessment_id |
+---------------+-----------+------+--------+----------------+------------------------+
| 1 | 1 | A | PASS | NULL | NULL |
| 3 | 1 | A | FAIL | PASS | 1 |
| 2 | 2 | B | FAIL | NULL | NULL |
| 4 | 2 | B | PASS | FAIL | 2 |
| 5 | 2 | B | PASS | PASS | 4 |
+---------------+-----------+------+--------+----------------+------------------------+
Which is a big part of the battle. Now we need to take that result and find the row for each device/test pair with the highest assessment_id, where result != previous_value.
The window is calculated after GROUP BY, ORDER BY, and even HAVING, so there's not much more that can be done in that query (that I have thought of) to narrow it down to the only the most recent entries for each device/test pair. So the above will have to be a subquery to get the final result.
Note: I am going to assume that if the result never changes, you want to show the first time that result was recorded. In other words, you want to count results with previous_value = NULL as a transition.
Here's a query that lists all the times the test result from a device/test pair changes:
SELECT * FROM
(SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY `device_id`, `test` ORDER BY `assessment_id`)
) AS t
WHERE result != `previous_value` OR `previous_value` IS NULL
gets the result (I left out previous_assesssment_id and the others for space):
+---------------+-----------+------+--------+----------------+
| assessment_id | device_id | test | result | previous_value |
+---------------+-----------+------+--------+----------------+
| 1 | 1 | A | PASS | NULL |
| 3 | 1 | A | FAIL | PASS |
| 2 | 2 | B | FAIL | NULL |
| 4 | 2 | B | PASS | FAIL |
+---------------+-----------+------+--------+----------------+
EDIT
That's the answer to the question. If the first time the value is set is not of interest, just delete the OR part of the WHERE clause. There rest of this answer is because I convinced myself the problem was to get the MOST RECENT time the value flipped. I'm leaving it here, but only for interest.
Carrying On
This is all the times the outcome was different than previous, plus the first time a result was recorded. Almost there.
It would be tempting at this point to add another window in the outer query to aggregate the rows from above and identify the correct rows. But at least in MySQL 8, nested windows are not supported.
But given that result, we can create a query using MAX() and GROUP BY that gives the assessment_ids of all the rows we ultimately want:
SELECT MAX(assessment_id)
FROM (
SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`,
LAG (assessment_id) over w as `previous_assessment_id`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY device_id, test ORDER BY assessment_id)
) AS t
where result != previous_value OR previous_value IS NULL
GROUP BY device_id, test
Which yields:
+--------------------+
| MAX(assessment_id) |
+--------------------+
| 3 |
| 4 |
+--------------------+
Now we know exactly which rows we need; but we built all that data about the previous values, and now we need a way to join the result of that query with the result of the subquery.
Happily, MySQL 8 has a way to stash a query and use it multiple times, called Common Table Expressions, that use the WITH clause docs here. So we can create the table with all the fun data, then use it as a subquery to get the id's we ultimately want, and then join that right back with the results we just created:
WITH
transitions AS (SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`,
LAG (assessment_id) over w as `previous_assessment_id`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY device_id, test ORDER BY assessment_id)
)
SELECT transitions.*
FROM transitions
JOIN (
SELECT MAX(assessment_id) as assessment_id
FROM transitions
WHERE result != previous_value OR previous_value IS NULL
GROUP BY device_id, test
) AS t2 using (assessment_id)
Giving us the final answer (with the other columns you can fill in):
+---------------+-----------+------+--------+----------------+------------------------+
| assessment_id | device_id | test | result | previous_value | previous_assessment_id |
+---------------+-----------+------+--------+----------------+------------------------+
| 3 | 1 | A | FAIL | PASS | 1 |
| 4 | 2 | B | PASS | FAIL | 2 |
+---------------+-----------+------+--------+----------------+------------------------+
The first part creates a data set that includes all the information about what came before each test. Then we write a query that gets the id's of the interesting rows in that query, then we join back to the original data set to fill in all the columns.
You can use windows functions to peek at values of previous or successive rows according to an ordering and grouping.
For example:
select *
from (
select
a.device_id,
r.test,
a.id as prev_assessment_id,
r.result as prev_result,
a.created_at as prev_value_date,
lead(a.id) over(partition by a.device_id
order by a.created_at) as next_assessment_id,
lead(r.result) over(partition by a.device_id
order by a.created_at) as next_result,
lead(a.created_at) over(partition by a.device_id
order by a.created_at) as next_value_date
from assessments a
join results r on r.assessment_id = a.id
) x
where prev_result <> next_result
Result:
device_id test prev_assessment_id prev_result prev_value_date next_assessment_id next_result next_value_date
---------- ----- ------------------- ------------ -------------------- ------------------- ------------ -------------------
1 A 1 PASS 2022-07-09 22:56:00 3 FAIL 2022-07-11 22:56:11
2 B 2 FAIL 2022-07-10 22:56:06 4 PASS 2022-07-12 22:56:17
See running example at db<>fiddle.
Note: Your setup queries (that I used) include different data, compared to your expected result.
I have a problem here trying to get one of my CASE WHEN statement to query each row for something called is_op as it's returning the same number for all rows. Here is the code:
SELECT `mid`, `message`, `created_at`,
CASE WHEN (SELECT `uid` FROM `bulletin_message` WHERE `bid` = 1 ORDER BY `mid` ASC LIMIT 1) = 5 THEN 1 ELSE 0 END AS `is_op`,
CASE WHEN `bulletin_message`.`uid` = 5 THEN 1 ELSE 0 END AS `is_me`
FROM`bulletin_message`
WHERE `bid` = 1
GROUP BY `mid`
ORDER BY `mid` ASC
As you can see I'm trying to select messages with the condition bid must equal to 1 and uid must equal to 5. While is_me returns the correct value for each row, is_op isn't reflecting the correct value for all the rows at all. It displays 1 at the result of the statement, rather than showing if a user is an OP or not based on the oldest value of mid or created_at. I don't think I am correctly querying each row like is_me statement.
This is all the data of the table:
mid = message uid; bid = bulletin/thread uid; uid = user uid
| mid | bid | uid | message | created_at |
---------------------------------------------------
| 3 | 1 | 5 | ... | ... |
| 5 | 1 | 6 | ... | ... |
| 6 | 2 | 7 | ... | ... |
| 9 | 1 | 5 | ... | ... |
| 10 | 1 | 7 | ... | ... |
| 11 | 1 | 6 | ... | ... |
What can be done to improve this line of code so that it can query each row? Thank you!
Edit: OP is Original Poster, sorry for not clarifying that! It's usually the person who post the first in each bid.
The problem is your subquery is based on a fixed predicate, ``bid= 1, so it is bound to return the same value for all rows.
Something like this would make more sense:
SELECT `mid`, `message`, `created_at`,
CASE WHEN (SELECT `uid`
FROM `bulletin_message` AS t2
WHERE t1.`bid` = t2.`bid`
ORDER BY `mid` ASC LIMIT 1) = t1.`uid`
THEN 1
ELSE 0
END AS `is_op`
FROM`bulletin_message` AS t1
ORDER BY `mid` ASC
The subquery is correlated using bid field: it returns the OP of the current thread.
This is a problem for which I have a working query, but it feels horribly inefficient to me and I'd like some help constructing a better one. This is going into a live production environment, and the number of queries the db handles each day is incredibly high, so the more efficient this can be, the better. I have a table structured something like this (stripped to just the relevant parts):
id | type | datecolumn
1 | A | 2014-01-01
1 | B | 0000-00-00
2 | A | 2014-01-02
2 | B | 2014-01-10
3 | A | 2014-01-01
3 | B | 0000-00-00
There will always be two rows for each id, one of type A and one of type B. A will always have a valid date, and B will either have a date >= that of A, or all 0s. What I want is a query that will produce output similar to this:
id | date for A | date for B
1 | 2014-01-01 | None
2 | 2014-01-02 | 2014-01-10
3 | 2014-01-01 | None
The way I'm doing this now is as follows:
SELECT
id,
IF(MIN(datecolumn) > 0, MIN(datecolumn), MAX(datecolumn)) AS 'date for A',
IF(MIN(datecolumn) > 0, MAX(datecolumn), 'None') AS 'date for B'
GROUP BY id
But it really feels like I should be able to pluck the datecolumn value on a by-type basis somehow. I know the simplest solution should be to change the table structure so that each id only uses one row, but I'm afraid that is not possible in this case; there has to be two rows. Is there a way to leverage the type column properly in this query?
Edit: Also, this is on a table that will have upwards of 10,000,000 rows. So again, efficiency is key.
I'd stick with what you've go, but maybe write it this way...
CREATE TABLE my_table
(id INT NOT NULL
,type CHAR(1) NOT NULL
,datecolumn DATE NOT NULL DEFAULT '0000-00-00'
,PRIMARY KEY(id,type)
);
INSERT INTO my_table VALUES
(1 ,'A','2014-01-01'),
(1 ,'B','0000-00-00'),
(2 ,'A','2014-01-02'),
(2 ,'B','2014-01-10'),
(3 ,'A','2014-01-01'),
(3 ,'B','0000-00-00');
SELECT id
, MAX(CASE WHEN type = 'A' THEN datecolumn END) a
, MAX(REPLACE(CASE WHEN type='B' THEN datecolumn END,'0000-00-00','none')) b
FROM my_table
GROUP
BY id;
+----+------------+------------+
| id | a | b |
+----+------------+------------+
| 1 | 2014-01-01 | none |
| 2 | 2014-01-02 | 2014-01-10 |
| 3 | 2014-01-01 | none |
+----+------------+------------+
Make sure you have an index that covers both the id and type columns (e.g ALTER TABLE tbl ADD INDEX (type,id)), then do:
SELECT
table_a.id,
table_a.datecolumn AS 'date for A',
IF(table_b.datecolumn > 0, table_b.datecolumn, 'None') AS 'date for B'
FROM tbl AS table_a
JOIN tbl AS table_b ON table_a.id = table_b.id AND table_b.type = 'B'
WHERE table_a.type = 'A';
Firstly, pardon the incredibly vague/long question, I'm really not sure how to summarise my query without the full explanation.
Ok, I have a single MySQL table with the format like so
some_table
user_id
some_key
some_value
If you imagine that, for each user, there are multiple rows, for example:
1 | skill | html
1 | skill | php
1 | foo | bar
2 | skill | html
3 | skill | php
4 | foo | bar
If I want to find all the users who have listed HTML as a skill I can simply do:
SELECT user_id
FROM some_table
WHERE some_key = 'skill' AND some_value='html'
GROUP BY user_id
Easy enough. This would give me user ID's 1 and 2.
If I want to find all users who have listed HTML or PHP as a skill then I can do:
SELECT user_id
FROM some_table
WHERE (some_key = 'skill' AND some_value='html') OR (some_key = 'skill' AND some_value='php')
GROUP BY user_id
This would give me use ID's 1, 2 and 3.
Now, what I'm struggling to work out is how I can query the same table but this time say "give me all the users who have listed both HTML and PHP as a skill", i.e: just user ID 1.
Any advice, guidance or links to docs massively appreciated.
Thanks.
Here's one way:
SELECT user_id
FROM some_table
WHERE user_id IN (SELECT user_id FROM some_table where (some_key = 'skill' AND some_value='html'))
AND user_id IN (SELECT user_id FROM some_table where (some_key = 'skill' AND some_value='php'))
you need to use a nested query (or a self join, which is different)
I set up the following table.
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| type | char(10) | YES | | NULL | |
| value | char(10) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
inserted the following values
+------+-------+-------+
| id | type | value |
+------+-------+-------+
| 1 | skill | html |
| 1 | skill | php |
| 2 | skill | html |
| 3 | skill | php |
| 2 | skill | php |
+------+-------+-------+
ran this query
select id
from test
where type = 'skill'
and value = 'html'
and id in (
select id
from test
where type = 'skill'
and value = 'php');
and got
+------+
| id |
+------+
| 1 |
| 2 |
+------+
a self join would be as follows
select e1.id
from test e1, test e2
where e1.id = e2.id
and e2.type = 'skill'
and e2.value = 'html'
and e1.type = 'skill'
and e1.value = 'php'
;
and produce the same result.
so there you have two ways to try it in your code.
I don't know if this is valid for mysql, but should be (works for other db engines):
SELECT php.user_id
FROM some_table php, some_table html
WHERE php.user_id = html.user_id
AND php.some_key = 'skill'
AND html.some_key = 'skill'
AND php.some_value = 'php'
AND html.some_value = 'html';
And alternative, by using HAVING statement:
SELECT user_id, count(*)
FROM some_table
WHERE some_key = 'skill'
AND some_value in ('php','html')
GROUP BY user_id
HAVING count(*) = 2;
And a third option is to use inner selects. A slight alternative approach to David's approach:
SELECT user_id FROM some_table
WHERE
some_key = 'skill' AND
some_value = 'html' AND
user_id IN (
SELECT user_id FROM some_table
WHERE
some_key = 'skill' AND
some_value = 'php' AND
user_id IN (
SELECT user_id FROM some_table
WHERE
some_key = 'skill' AND
some_value = 'js' -- AND user_id IN ... for next level, etc.
)
);
... idea is that you can "pipe" the inner selects. With each new property you add new inner select to the most inner one.