MySQL Request with IN clause, with array of couple identifier - mysql

I have a user table with a couple as identifier : id and type, like this :
id | type | name
----------------
15 | 1 | AAA
16 | 1 | BBB
15 | 2 | CCC
I would like to get a list, matching both id and type.
I currently use a concat system, which works :
SELECT u.id,
u.type,
u.name
FROM user u
WHERE CONCAT(u.id, '-', u.type) IN ('15-1', '16-1', '17-1', '10-2', '15-2')
But, I have the feeling it could be better, what would be the proper way to do it ?
Thank you !

You may use the following approach in mysql
with dat as
(
select 17 id, 1 type, 'AAA' t
union all
select 16 id, 1 type, 'BBB' t
union all
select 17 id, 2 type, 'CCC' t
)
-- end of testing data
select *
from dat
where (id, type) in (
-- query data
(17, 1), (16, 1)
)

IN can operate on "tuples" of values, like this (a, b) IN ((c,d), (e,f), ....). Using this method is (should be) faster as you are not doing a concat operation on "a" and "b" and then comparing strings; instead you are comparing pairs of values, unprocessed and with an appropriate comparison operation (i.e. not always string compares).
Additionally, if "a" and/or "b" are string values themselves using the concat technique risks ambiguous results. ("1-2","3") and ("1","2-3") pairs concat to the same result "1-2-3"

You can separate them out. Not sure if it's more efficient but at least you would save the concat part :
SELECT u.id,
u.type,
u.name
FROM user u
WHERE (u.id = 15 AND u.type = 1)
OR (u.id = 16 AND u.type = 1)
OR (u.id = 17 AND u.type = 1)
OR (u.id = 10 AND u.type = 2)
OR (u.id = 15 AND u.type = 2)

I think it depends a lot on how you obtain the values for id and type that you use for filtering
If they are results of another computation they can be saved in a temporary table and used in a join
create TEMPORARY TABLE criteria as
select 15 as id, 1 as type
UNION
select 16 as id, 1 as type
UNION
select 17 as id, 1 as type
UNION
select 10 as id, 2 as type
UNION
select 15 as id, 2 as type
SELECT u.id,
u.type,
u.name
FROM user u
inner join criteria c on u.type = c.type and u.id = c.id
The other option is an inner query and then a join or a WITH clause (which is rather late addition to Mysql arsenal of tricks)

Related

Query: I have 4 rows, need to add the results from 3 rows into one, and leave the last row untouched

I have a kind of tricky question for this query. First the code:
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
What Am I doing?
For example, I am counting police reports of robbery, made from different kind of users. In my example, "admin" users reported 6 incidents of code "2" (robbery) and so on, as is showed in 'where' clause (incident must be robbery, also code 2).
this brings the following result:
+-----------------------+----------+
| user_type_description | Quantity |
+-----------------------+----------+
| Admin | 6 |
| Moderator | 8 |
| Fully_registered_user | 8 |
| anonymous_user | 9 |
+-----------------------+----------+
Basically Admin,Moderator and Fully_registered_user are appropriately registered users. I need to add them in a result where it shows like:
+--------------+------------+
| Proper_users | Anonymous |
+--------------+------------+
| 22 | 9 |
+--------------+------------+
I am not good with sql. Any help is appreciated. Thanks.
You can try to use condition aggregate function base on your current result set.
SUM with CASE WHEN expression.
SELECT SUM(CASE WHEN user_type_description IN ('Admin','Moderator','Fully_registered_user') THEN Quantity END) Proper_users,
SUM(CASE WHEN user_type_description = 'anonymous_user' THEN Quantity END) Anonymous
FROM (
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
) t1
You just need conditional aggregation:
SELECT SUM( ut.user_type_description IN ('Admin', 'Moderator', 'Fully_registered_user') ) as Proper_users,
SUM( ut.user_type_description IN ('anonymous_user') as anonymous
FROM incident i INNER JOIN
user u
ON i.user_id = u.user_id INNER JOIN
user_type ut
ON u.user_type = ut.user_type
WHERE i.code = 2;
Notes:
Table aliases make the query easier to write and to read.
This uses a MySQL shortcut for adding values -- just just adding the booelean expressions.
I would solve it with a CTE, but it would be better to have this association in a table.
WITH
user_type_categories
AS
(
SELECT 'Admin' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Moderator' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Fully_registered_user' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'anonymous_user' AS [user_type_description] , 'Anonymous' AS [user_type_category]
)
SELECT
CASE WHEN utc.[user_type_category] = 'Proper_users' THEN
SUM(incident.user_id)
END AS [Proper_Users_Quantity]
, CASE WHEN utc.[user_type_category] = 'Anonymous' THEN
SUM(incident.user_id)
END AS [Anonymous_Quantity]
FROM
[incident]
INNER JOIN [user] ON [incident].[user_id] = [user].[user_id]
INNER JOIN [user_type] ON [user].[user_type] = [user_type].[user_type]
LEFT JOIN user_type_categories AS utc ON utc.[user_type_description] = [user_type].[user_type_description]
WHERE
[incident].[code] = 2

How can I optimise mySQL to use JOINs instead of nested IN queries?

I have a query which combines a user's balance at a number of locations and uses a nested subquery to combine data from the customer_balance table and the merchant_groups table. There is a second piece of data required from the customer_balance table that is unique to each merchant.
I'd like to optimise my query to return a sum and a unique value i.e. the order of results is important.
For instance, there may be three merchants in a merchant_group:
id | group_id | group_member_id
1 12 36
2 12 70
3 12 106
The user may have a balance at 2 locations but not all in the customer_balance table:
id | group_member_id | user_id | balance | personal_note
1 36 420 1.00 "Likes chocolate"
2 70 420 20.00 null
Notice there isn't a 3rd row in the balance table.
What I'd like to end up with is the ability to pull the sum of the balance as well as the most appropriate personal_note.
So far I have this working in all situations with the following query:
SELECT sum(c.cash_balance) as cash_balance,n.customer_note FROM customer_balance AS c
LEFT JOIN (SELECT customer_note, user_id FROM customer_balance
WHERE user_id = 420 AND group_member_id = 36) AS n on c.user_id = n.user_id
WHERE c.user_id = 420 AND c.group_id IN (SELECT group_member_id FROM merchant_group WHERE group_id = 12)
I can change out the group_member_id appropriately and I will always get the combined balance as expected and the appropriate note. i.e. what I'm looking for is:
balance: 21.00
customer_note: "Likes Chocolate" OR null (depending on the group_member_id)
Is it possible to optimise this query without using resource heavy nested queries e.g. using a JOIN? (or some other method).
I have tried a number of options, but cannot get it working in all situations. The following is the closest I have gotten, except this doesn't return the correct note:
SELECT sum(cb.balance), cb.personal_note FROM customer_balance AS cb
LEFT JOIN merchant_group AS mg on mg.group_member_id = cb.group_member_id
WHERE cb.user_id = 420 && mg.group_id = 12
ORDER BY (mg.group_member_id = 106)
I also tried another option (but since lost the query) that works, but not when the group_member_id = 106 - because there was no record in one table (but this is a valid use case that I'd like to cater for).
Thanks!
This should be equivalent but without subselect
SELECT
sum(c.cash_balance) as cash_balance
, n.customer_note
FROM customer_balance AS c
LEFT JOIN customer_balance as n on ( c.user_id = n.user_id AND n.group_member_id = 36 AND n.user_id = 420 )
INNER JOIN merchant_group as mg on ( c.group_id = mg.group_member_id AND mg.group_id = 12)
WHERE c.user_id = 420

Nested queries and Join

As a beginner with SQL, I’m ok to do simple tasks but I’m struggling right now with multiple nested queries.
My problem is that I have 3 tables like this:
a Case table:
id nd date username
--------------------------------------------
1 596 2016-02-09 16:50:03 UserA
2 967 2015-10-09 21:12:23 UserB
3 967 2015-10-09 22:35:40 UserA
4 967 2015-10-09 23:50:31 UserB
5 580 2017-02-09 10:19:43 UserA
a Value table:
case_id labelValue_id Value Type
-------------------------------------------------
1 3633 2731858342 X
1 124 ["864","862"] X
1 8981 -2.103 X
1 27 443 X
... ... ... ...
2 7890 232478 X
2 765 0.2334 X
... ... ... ...
and a Label table:
id label
----------------------
3633 Value of W
124 Value of X
8981 Value of Y
27 Value of Z
Obviously, I want to join these tables. So I can do something like this:
SELECT *
from Case, Value, Label
where Case.id= Value.case_id
and Label.id = Value.labelValue_id
but I get pretty much everything whereas I would like to be more specific.
What I want is to do some filtering on the Case table and then use the resulting id's to join the two other tables. I'd like to:
Filter the Case.nd's such that if there is serveral instances of the same nd, take the oldest one,
Limit the number of nd's in the query. For example, I want to be able to join the tables for just 2, 3, 4 etc... different nd.
Use this query to make a join on the Value and Label table.
For example, the output of the queries 1 and 2 would be:
id nd date username
--------------------------------------------
1 596 2016-02-09 16:50:03 UserA
2 967 2015-10-09 21:12:23 UserB
if I ask for 2 different nd. The nd 967 appears several times but we take the oldest one.
In fact, I think I found out how to do all these things but I can't/don't know how to merge them.
To select the oldest nd, I can do someting like:
select min((date)), nd,id
from Case
group by nd
Then, to limit the number of nd in the output, I found this (based on this and that) :
select *,
#num := if(#type <> t.nd, #num + 1, 1) as row_number,
#type := t.nd as dummy
from(
select min((date)), nd,id
from Case
group by nd
) as t
group by t.nd
having row_number <= 2 -- number of output
It works but I feel it's getting slow.
Finally, when I try to make a join with this subquery and with the two other tables, the processing keeps going on for ever.
During my research, I could find answers for every part of the problem but I can't merge them. Also, for the "counting" problem, where I want to limit the number of nd, I feel it's kind of far-fetch.
I realize this is a long question but I think I miss something and I wanted to give details as much as possible.
to filter the case table to eliminate all but oldest nds,
select * from [case] c
where date = (Select min(date) from case
where nd = c.nd)
then just join this to the other tables:
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
to limit it to a certain number of records, there is a mysql specific command, I think it called Limit
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
Limit 4 -- <=== will limit return result set to 4 rows
if you only want records for the top N values of nd, then the Limit goes on a subquery restricting what values of nd to retrieve:
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
and nd In (select distinct nd from [case]
order by nd desc Limit N)
So finally, here is what worked well for me:
select *
from (
select *
from Case
join (
select nd as T_ND, date as T_date
from Case
where nd in (select distinct nd from Case)
group by T_ND Limit 5 -- <========= Limit of nd's
) as t
on Case.nd = t.T_ND
where date = (select min(date)
from Case
where nd = t.T_ND)
) as subquery
join Value
on Value.context_id = subquery.id
join Label
on Label.id = Value.labelValue_id
Thank you #charlesbretana for leading me on the right track :).

MYSQL counting occurrences separately from 2 tables

I got 3 tables in my MYSQL bases and I have to compare how many time there are each user_ID in each of the 2 first table (table 1 and table 2)
here is my table 1:
user_ID
A
B
A
D
...
here is my table 2 :
user_ID
A
C
A
...
here is my table 3 (with link between user_ID and nickname) :
user_ID // nickname
A // Bob
B // Joe
C // Tom
...
I would like to get a result like this:
Nickname // count occurrences from Table 1 // count occurrences from table 2
Bob // 1 // 2
Joe // 4 // 0
Tom // 0 // 2
I did not succeed for instant to count separately from each table, I got a global result for each nickname :(
Could you help me to find the right MYSQL request ?
- ...
This type of query is a little tricky, because some names may not be in the first table and others may not be in the second. To really solve this type of problem, you need to pre-aggregate the results for each query. To get all the names, you need a left outer join:
select t3.name, coalesce(cnt1, 0) as cnt1, coalesce(cnt2, 0) as cnt2
from table3 t3 left outer join
(select name, count(*) as cnt1
from table1
group by name
) t1n
on t3.name = t1n.name left outer join
(select name, count(*) as cnt2
from table2
group by name
) t2n
on t3.name = t1n.name;

Complex SQL Query: Select like tree traversal

Consider the following (1:N) relationship:
[entity: user] <------ rid key ------> [entity: rid].
consider the data in both tables as:
select * from user;
user-id rid-key
a-basa a
b-basa b
a.a-basa a.a
a.b-basa a.b
a.a.a-basa a.a.a
a.a.b-basa a.a.b
a.b.a-basa a.b.a
a.b.b-basa a.b.b
a.b.b.a-basa a.b.b.a
a.b.b.b-basa a.b.b.b
select * from rid;
rid-key parent-rid enabled
a null true
b null true
a.a a true
a.b a false
a.a.a a.a true
a.b.a a.b true
a.b.b a.b true
a.b.b.a a.b.b true
......
n rows
I need to design a single query (not stored procedure) which will input a user-id, and the following facts are considered:
If an user is given access to a rid, then it can also access the parent rid of the rid given - the rid itself is enabled (enabled = true).
This should continue till we reach the root rid, ie. parent rid property is null.
In above example, the list of accessible rid for the user 'a.b.b.a-basa' will be:
a.b.b.a
a.b.b
a.b
and for a.a.a-basa:
a.a.a
a.a
a
can we get this list using a single query? Any sql vendor is fine.
There are several models for Hierarchical data. Most models (like the Adjacency List) require some sort of recursion for some queries. With your design that uses the Materialized Path model, what you want is possible without a recursive query.
Tested in MySQL (that has no recursive queries), at SQL-fiddle test-mysql. It can be easily converted for other DBMS, if you modify the string concatenation part:
SELECT
COUNT(*)-1 AS steps_up,
rid2.rid_key AS ancestor_rid_key
FROM
u2
JOIN
rid
ON u2.rid_key = rid.rid_key
OR u2.rid_key LIKE CONCAT(rid.rid_key, '.%')
JOIN
rid AS rid2
ON rid.rid_key = rid2.rid_key
OR rid.rid_key LIKE CONCAT(rid2.rid_key, '.%')
WHERE
u2.userid = 'basa'
AND
u2.rid_key = 'a.b.b.a'
GROUP BY
rid2.rid_key, rid2.enabled
HAVING
COUNT(*) + (rid2.enabled = 'true')
= SUM(rid.enabled = 'true') + 1 ;
It uses this view, which is not strictly needed but it shows that the user.user_id is storing data that you already have in the rid_key column.
CREATE VIEW u2 AS
SELECT
SUBSTRING_INDEX(user_id, '-', -1) AS userid
, rid_key
FROM user ;
One more note is that the above query does not use the parent_rid column at all. And that I'm sure it can be further improved.
In Oracle, you can achieve this using a hierarhical query. Search for CONNECT BY or have a look at this article.
This should get the ball rolling for you.
The answer works on SQL Server 2005 onwards
DECLARE #UsersRIDkey VARCHAR(10) = 'a.a.a'
;WITH UserCTE (userid, ridkey) AS
(
SELECT 'a-basa', 'a' UNION ALL
SELECT 'b-basa', 'b' UNION ALL
SELECT 'a.a-basa', 'a.a' UNION ALL
SELECT 'a.b-basa', 'a.b' UNION ALL
SELECT 'a.a.a-basa', 'a.a.a' UNION ALL
SELECT 'a.a.b-basa', 'a.a.b' UNION ALL
SELECT 'a.b.a-basa', 'a.b.a' UNION ALL
SELECT 'a.b.b-basa', 'a.b.b' UNION ALL
SELECT 'a.b.b.a-basa', 'a.b.b.a' UNION ALL
SELECT 'a.b.b.b-basa', 'a.b.b.b'
)
,RidCTE (ridkey, parentrid, isenabled) AS
(
SELECT 'a', null, 1 UNION ALL
SELECT 'b', null, 1 UNION ALL
SELECT 'a.a', 'a', 1 UNION ALL
SELECT 'a.b', 'a', 0 UNION ALL
SELECT 'a.a.a', 'a.a', 1 UNION ALL
SELECT 'a.b.a', 'a.b', 1 UNION ALL
SELECT 'a.b.b', 'a.b', 1 UNION ALL
SELECT 'a.b.b.a', 'a.b.b', 1
)
,RidHierarchyCTE AS
(
SELECT *
FROM RidCTE
WHERE ridkey = #UsersRIDkey
UNION ALL
SELECT R.ridkey, R.parentrid, R.isenabled
FROM RidHierarchyCTE H
JOIN RidCTE R ON R.ridkey = H.parentrid
)
SELECT ridkey
FROM RidHierarchyCTE
Oracle solution:
SQL> select u.user_id, r.rid_key, r.parent_rid, r.enabled
2 from users u
3 inner join rid r
4 on r.rid_key = u.rid_key
5 start with u.user_id = 'a.a.a-basa'
6 connect by prior r.parent_rid = r.rid_key and prior enabled = 'true'
7 /
USER_ID RID_KEY PAREN ENABL
------------ ------- ----- -----
a.a.a-basa a.a.a a.a true
a.a-basa a.a a true
a-basa a null true
SQL> select u.user_id, r.rid_key, r.parent_rid, r.enabled
2 from users u
3 inner join rid r
4 on r.rid_key = u.rid_key
5 start with u.user_id = 'a.b.b.a-basa'
6 connect by prior r.parent_rid = r.rid_key and prior enabled = 'true'
7 /
USER_ID RID_KEY PAREN ENABL
------------ ------- ----- -----
a.b.b.a-basa a.b.b.a a.b.b true
a.b.b-basa a.b.b a.b true
a.b-basa a.b a false
http://sqlfiddle.com/#!4/d529f/1