How to use WHERE (SELECT ....) - mysql

I'm doing a pretty complicated reporting function with many conditions, many panels,
I have a record in tbl_my_report
id param_filter
101 FIND_IN_SET(t.owner_department,'0620510200,0621510200,0623510200')
Query:
SELECT *
FROM tbl_abc t WHERE t.id = '1' AND
(SELECT mr.param_filter
FROM tbl_my_report mr WHERE mr.id = '101'
)
How to use it as a valid condition string?

First, you should normalize your data, and get rid of FIND_IN_SET. So, there should be some department table looking like this:
id | owner
'101' | '0620510200'
'101' | '0621510200'
'101' | '0623510200'
With this table in place, you may refactor your query to:
SELECT *
FROM tbl_abc t
WHERE
t.id = '1' AND
EXISTS (SELECT 1 FROM department WHERE id = '101' AND owner = t.owner_department);
In general, you should avoid storing CSV or other unnormalized data in your database tables, for the very reason that it can make querying difficult.

Use this:
SELECT * FROM tbl_abc t WHERE t.id = '1' IN (SELECT mr.param_filter
FROM tbl_my_report mr WHERE mr.id = '101' );

Related

Improving a SQL query when I need to select IN multiple fields combined together

Here is my SQL query
SELECT date(metrics_session.created_at) as day, COUNT(metrics_session.user_id) as total_logins,
sum(TIMESTAMPDIFF(MINUTE,metrics_session.created_at,metrics_session.completed_at)) as total_time_spent
FROM metrics_session
inner join metrics_training on metrics_training.id = metrics_session.training_id
inner join metrics_course on metrics_course.id = metrics_training.course_id
inner join metrics_user_training_cohort on metrics_training.id = metrics_user_training_cohort.training_id
inner join auth_user on auth_user.id = metrics_user_training_cohort.user_id
WHERE metrics_session.created_at >= '2021-01-15'
AND metrics_session.created_at <= '2022-10-15'
AND metrics_session.completed_at IS NOT NULL
AND metrics_session.user_id In (SELECT user_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
AND metrics_session.training_id In (SELECT training_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
#AND EXISTS(SELECT user_id,training_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
GROUP BY date(metrics_session.created_at) ORDER BY date(metrics_session.created_at)
the goal of this query is to select the sessions that were created by some user_id and are linked to some training_id , but only if in the table metrics_user_training_cohort I have that same user_id and training_id in the same row registered.
I managed to achieve that with the 2 last lines before GROUP BY:
AND metrics_session.user_id In (SELECT user_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
AND metrics_session.training_id In (SELECT training_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
however the repeated subquery used for the IN statement seems unnecessary to me and likely degrading performance, but I can't quite figure out a better way since the IN statement can only be used with 1 column.
The commented line is not the solution because it just checks for the existence of the row in the table in isolation without association to the sessions, but I left it there to give you a better idea what I'm trying to achieve.
It is usually better to use JOIN or EXISTS instead of IN ( SELECT ... )
These may help:
metrics_session: INDEX(created_at, completed_at, user_id, training_id)
metrics_user_training_cohort: INDEX(training_id, user_id)

Can't access variable in inner scope?

I'm trying to get this piece of SQL query to work. The problem is that I can't access core_user.id in the stuff in its parameters. I don't really know why.
SELECT
core_user.id AS target, (
SELECT
COUNT(permission_id) AS permissions
FROM (
SELECT
DISTINCT permission_id
FROM (
SELECT
id,
permission_id
FROM
core_user_user_permissions
WHERE
core_user_user_permissions.user_id = core_user.id
UNION
SELECT
id,
permission_id
FROM
auth_group_permissions
WHERE
auth_group_permissions.group_id IN (
SELECT
group_id
FROM
core_user_groups
WHERE
core_user_groups.user_id = core_user.id)) AS `permissions`) AS `derived`) AS `perms`
FROM
`core_user`
WHERE
`core_user`.`is_active` = TRUE
GROUP BY
`core_user`.`id`
ORDER BY
`perms` ASC
If i try target.id with core_user AS target it does not work either: Unknown column 'target.id' in 'where clause'
Expected Results:
+----------------------------------+-------+
| id | perms |
+----------------------------------+-------+
| ab7ec54bf9124dffb807fb89f9ea8036 | 0 |
| b54d4d3f97134dfcbc36ac193c0c1250 | 81 |
| c69ffa4d162b49129ff6a316da3caaa3 | 64 |
| f8ac73eee80044359c246f3b173aa631 | 0 |
+----------------------------------+-------+
Any idea how to fix this?
It's not easy to understand what one wants, if there is only an (invalid) query and no description of what it should retrieve. So this is just a best guess:
SELECT cu.id target,
count(x.permission_id) perms
FROM core_user cu
LEFT JOIN (SELECT cuup.permission_id,
cuup.user_id
FROM core_user_user_permissions cuup
WHERE cuup.permission_id IS NOT NULL
UNION
SELECT agp.permission_id,
cug.user_id
FROM auth_group_permissions agp
INNER JOIN core_user_groups cug
ON cug.group_id = agp.group_id
WHERE agp.permission_id IS NOT NULL) x
ON x.user_id = cu.id
WHERE cu.is_active = TRUE
GROUP BY cu.id
ORDER BY perms ASC;
Instead of getting the count in a subselect it left joins the permission IDs for each user. Grouping by the user ID than gets the (distinct, because the union already removed any duplicates and id is also unique in core_user (I assume)) count of the non null permission IDs of a user, which is what I believe you want.
(You may remove the WHERE <alias>.permission_id IS NOT NULL in the inner query, if there are not null constraints on the columns. The idea here is, that nulls aren't counted in the end anyway, so we want to discard them as early as possible.)
(Untested, as neither schema nor sample data was provided. May contain typos.)
I would rewrite it as:
SELECT core_user.id AS target,
(SELECT COUNT(permission_id) AS permissions
FROM (SELECT permission_id
FROM core_user_user_permissions
WHERE core_user_user_permissions.user_id = `core_user`.id
UNION
SELECT permission_id
FROM auth_group_permissions
WHEREauth_group_permissions.group_id IN (
SELECT group_id
FROM core_user_groups
WHERE core_user_groups.user_id = `core_user`.id)
) AS `derived`
) AS `perms`
FROM `core_user`
WHERE `core_user`.`is_active` = TRUE
GROUP BY `core_user`.`id`
ORDER BY `perms`;
I've removed one level of nested subquery in SELECT list.
Well I hope I could get your expected result by excluding group premissons:
SELECT core_user.id AS target,
(
SELECT COUNT(DISTINCT(permission_id)) AS permissions
FROM
(
SELECT cuup.id as id, cuup.permission_id as permission_id
FROM core_user_user_permissions cuup
-- WHERE cuup.user_id = cu.id
--VV-- this should be a join
JOIN core_user cu ON cu.id = cuup.user_id
)
) AS `perms`
FROM `core_user`
WHERE `core_user`.`is_active` = TRUE
GROUP BY `core_user`.`id`
ORDER BY `perms` ASC

SQL Statement for gouping messages

I have the following code and I'm trying to group the messages
Here is a picture of database table and how the groups should be
and here is the SQL statement
SELECT a.* FROM `user_messages` `a`
JOIN (
SELECT `sender`, MAX(`id`) `last_id` FROM `user_messages` WHERE `receiver` = '1' GROUP BY `sender`
) `b`
ON `a`.`sender` = `b`.`sender` AND `a`.`id` = `b`.`last_id`
WHERE `a`.`receiver` = '1'
ORDER BY `id` DESC
OUTPUT:
I want to get somehow the last record where "receiver" is not my id, but "sender" is and name receiver column as "id" or something.
...so what i want is following result:
id | msg
13852 123
48 Hello!
17 321
Here is a fiddle: http://sqlfiddle.com/#!9/e06d57/3/0
To map my generic answer to your particular use case (using example 1):
SELECT receiver AS id, msg
FROM user_messages outerTable
WHERE NOT EXISTS
( SELECT *
FROM user_messages innerTable
WHERE innerTable.sender = outerTable.sender
AND innerTable.receiver = outerTable.receiver
AND innerTable.added > outerTable.added
)
AND sender = 1
This is a very common use case. There are several ways to write this code. Depending on the SQL engine used, they will be of different speeds.
I will use fairly generic column names. Tweak as needed.
SELECT common_id, msg
FROM myTable outerTable
WHERE NOT EXISTS
( SELECT *
FROM myTable innerTable
WHERE innerTable.common_id = outerTable.common_id
AND innerTable.time > outerTable.time
)
Please note that if there are two rows with identical common_id and time columns, then both will show up in the output. You can replace the > with >= to hide both of those rows.
The other common approach is kind of difficult to make sense of, but here goes. Notice the similarities to the NOT EXISTS approach.
SELECT outerTable.common_id, outerTable.msg
FROM myTable outerTable
LEFT JOIN myTable innerTable
ON innerTable.common_id = outerTable.common_id
AND innerTable.time > outerTable.time
WHERE innerTable.common_id IS NULL
According to your description, you seem to want something like this:
select um.receiver as id, um.msg
from user_messages um
where um.sender = 1 and
um.id = (select max(um2.id)
from user_messages um2
where um2.msg = um.msg and um2.receiver <> 1 and um.sender = 1
);
It doesn't produce the desired output, but that is because the output is inconsistent with the text description.

mysql SELECT EXISTS on multiple tables

Have tables: person,person_ip
Both tables have pid column as a primary key, in table person there is column state_id, in table person_ip there is column ip.
Want to discover if specified IP address is assigned to person with state_id is not equal to 2. But always got result 1, even if state_id is 0, 1 or 2. Always got 0 only if ip address is not listed at all. What am I doing wrong?
SELECT EXISTS (
SELECT person_ip.PID
FROM person_ip,person
WHERE person.PID=person_ip.PID
AND person.state_id NOT IN (2)
AND person_ip.ip='10.11.12.13'
)
this seems like a simple join.. unless i'm missing something
select person.*
from person
inner join person_ip
on person.pid = person_ip.pid
where person.state_id <> 2
and person_ip.ip_address = '10.0.0.1'
If you want to exclude the ip_address if it has been assigned to any user with state = 2, even if it has also been assigned to a user without state = 2, then try:
select max(i)
from (
select *
from (
select 1 as i
from dual
where not exists (
select 1
from person p
inner join person_ip pi
on p.pid = pi.pid
where p.state_id = 2
and pi.ip_address = '10.0.0.1'
)
) q
union
select 0
) qq
(dual is a system table that can be used as a sort of stub table)
here's a fiddle showing both versions
update after some actual sleep
Okay, so the above query is a little.. out there. Back in the real world, this one is probably more appropriate:
select count(case when p1.state_id = 2 then 1 end)
from person p1
inner join person_ip pi1
on p1.pid = pi1.pid
where pi1.ip_address = '10.0.0.1'
group by pi1.ip_address;
This will return 1 or more if your ip_address has been used by someone with a state_id of 2, and 0 if it has never been used by someone with a state_id of 2.
It will return nothing if the ip has never been used.
this fiddle has all three of the above queries.
SELECT IF(COUNT(*)>0,1,0)
FROM person
INNER JOIN person_ip
ON person.pid = person_ip.pid
AND person_ip.ip_address = '10.0.0.1'
WHERE person.state_id <> 2

How to query mysql to select and group by multiple values

I'm trying to select and group by all the contentid values of the table below where the match criteria can be several different values.
the contentid values actually represent cars, so I need to select [and group by] all the contentis where the values are 'GMC' and the values are 'sedan' and the value is 'automatic.
i.e. I'm trying to select all the GMC sedans with an automatic transmission.
a query like this fails [obviously]:
select * from modx_site_tmplvar_contentvalues WHERE
`value` = 'gmc' and
`value` = 'tacoma'
group by contentid
I have no idea how to create a query like that. Any suggestions?
You need to "pivot" these data on "tmplvarid", but unfortunately for you MySQL doesn't have a PIVOT statement like other RDBMS. However, you can pivot it yourself by joining in the table multiple times for each variable you care about:
SELECT
contents.contentid,
transmission.value as transmission,
type.value as type,
make.value as make
FROM
(SELECT DISTINCT contentid FROM modx_site_tmplvar_contentvalues) AS contents
LEFT JOIN
modx_site_tmplvar_contentvalues AS transmission
ON contents.contentid = transmission.contentid
AND transmission.tmplvarid = 33 -- id for transmission
LEFT JOIN
modx_site_tmplvar_contentvalues AS make
ON contents.contentid = make.contentid
AND make.tmplvarid = 13 -- id for make
LEFT JOIN
modx_site_tmplvar_contentvalues AS type
ON contents.contentid = type.contentid
AND type.tmplvarid = 17 -- id for type
WHERE
type.value = 'sedan'
AND make.value = 'GMC'
AND transmission.value = 'automatic'
You can expand this with additional joins for other criteria such as year (id 15) or mileage (id 16).
If you need to use the value only, you could try:
SELECT DISTINCT
contents.contentid,
transmission.value as transmission,
type.value as type,
make.value as make
FROM
(SELECT DISTINCT contentid FROM modx_site_tmplvar_contentvalues) AS contents
INNER JOIN
modx_site_tmplvar_contentvalues AS transmission
ON contents.contentid = transmission.contentid
AND transmission.value = 'automatic'
INNER JOIN
modx_site_tmplvar_contentvalues AS make
ON contents.contentid = make.contentid
AND make.value = 'GMC'
INNER JOIN
modx_site_tmplvar_contentvalues AS type
ON contents.contentid = type.contentid
AND type.value = 'sedan'
In any case, make sure you have an index on the value column; these queries are going to get slow.
please try this:
SELECT *
FROM modx_site_tmplvar_contentvalues t1 INNER JOIN modx_site_tmplvar_contentvalues t2 ON t1.contentid = t2.content_id
WHERE
t1.`value` = 'gmc'
AND t2.`value` = 'tacoma';
You can do this with a group by. This is the most flexible in terms of expressing the conditions. In MySQL, multiple joins will often perform better:
select contentid
from modx_site_tmplvar_contentvalues
group by contentid
having sum(`value` = 'gmc') > 0 and
sum(`value` = 'tacoma') > 0;
This is always false:
`value` = 'gmc' and
`value` = 'tacoma'
Instead, use OR:
`value` = 'gmc' OR
`value` = 'tacoma'
In a condition "and" means "this and this is true at the same time". If you want all foos and all bars, then your condition is "foo OR bar".
EDIT:
To select groups containing your values, you can write subqueries:
SELECT DISTINCT name FROM table WHERE name IN (SELECT name FROM table WHERE value='value1') AND name IN (SELECT name FROM table WHERE value='value2')