Get Values not in the second table using find_in_set - mysql

I have two tables and i need to get list of all store_ids that are not in the other table
BusinessUnit Table User Table
StoreId(varchar) StoreId(varchar)
1 1,2
2 3,4
3 1,5
4 4,6
7 4
How to get values of storeid 5,6 which are not present in the business unit table but are present in the user Table? Tried to use several using find_in_set and nothing works.

Use SUBSTRING_INDEX to get all the values from the CSV field. Since there can be up to 6 IDs in the CSV, you need to call it once for each position.
SELECT u.StoreId
FROM (
select substring_index(StoreId, ',', 1) AS StoreID
FROM User
UNION
select substring_index(substring_index(StoreId, ',', 2), ',', -1)
FROM User
UNION
select substring_index(substring_index(StoreId, ',', 3), ',', -1)
FROM User
UNION
select substring_index(substring_index(StoreId, ',', 4), ',', -1)
FROM User
UNION
select substring_index(substring_index(StoreId, ',', 5), ',', -1)
FROM User
UNION
select substring_index(substring_index(StoreId, ',', 6), ',', -1)
FROM User) AS u
LEFT JOIN BusinessUnit AS b ON u.StoreId = b.StoreID
WHERE b.StoreId IS NULL
DEMO

IF you know all the possible values (and the number of them is reasonably manageable) you can populate a new table with them (you can make it TEMPORARY or just DROP it afterwards), and do this
SELECT *
FROM (
SELECT allIDs.Id
FROM allIDs
INNER JOIN `User` AS u
-- ON CONCAT(',', u.StoreID, ',') LIKE CONCAT('%,', allIDs.Id, ',%')
ON FIND_IN_SET(allIDs.Id, u.StoreID)
) AS IDsInUserTable
LEFT JOIN `BusinessUnit` AS b ON IDsInUserTable.Id = b.StoreID
HAVING b.StoreID IS NULL
;
In this example, allIDs is the aforementioned "possible values" table.

Related

How to select from database using explode

I want export data from my SQL database.
Simply use :
SELECT `id`,`tags` FROM `posts`
This query give me those results :
(1, 'handshake,ssl,windows'),
(2, 'office,word,windows'),
(3, 'site')
I want results in this form:
(1, 'handshake'),
(1, 'ssl'),
(1, 'windows'),
(2, 'office'),
(2, 'word'),
(2, 'windows'),
(3, 'site')
How can write a query that give me this results?
Thank you and sorry for my poor English.
If you are using SQL Server
You can apply the fuction
STRING_SPLIT
SELECT id, value
FROM posts
CROSS APPLY STRING_SPLIT(tags, ',')
Check this out:
SQL Fiddle example
After many search and try finally i find the solution:
SELECT
DISTINCT postid , SUBSTRING_INDEX(SUBSTRING_INDEX(tags, ',', n.digit+1), ',', -1) val
FROM
posts
INNER JOIN
(SELECT 0 digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6) n
ON LENGTH(REPLACE(tags, ',' , '')) <= LENGTH(tags)-n.digit;
For a max of three words, the code below can be used. If you want more words then you just add more lines. The method may not be fully automated, but it works.
SELECT id, SUBSTRING_INDEX(SUBSTRING_INDEX(tags, ',', 1), ',', -1) FROM tabela
UNION
SELECT id, SUBSTRING_INDEX(SUBSTRING_INDEX(tags, ',', 2), ',', -1) FROM tabela
UNION
SELECT id, SUBSTRING_INDEX(SUBSTRING_INDEX(tags, ',', 3), ',', -1) FROM tabela
ORDER BY id;

How to split / unpivot group_concat in mysql?

I currently have a view that has a column called alt_email_contact and I used the group_concat function in order to get multiple emails associated with one contact. However I want to be able to extract each email and create a separate column for each.
Example:
id email
1 SkyW#gmail.com, SW#gmail.com, WW#gmail.com, WalterW#gmail.com
the amount of emails is subject to change from one user to another therefore there wont always be four emails for each user. I want to create a new column per each email like so:
id email_1 email_2 email_3 email_4
1 SkyW#gmail.com SW#gmail.com WW#gmail.com WalterW#gmail.com
(I am using phpmyadmin) I would like to be able to modify my view to contain the variable amount of emails per user.
You can use substring_index() to achieve what you want. It is not really pretty, but it will work:
select id,
substring_index(emails, ', ', 1) as email_1
(case when length(emails) - length(replace(emails, ',', '')) >= 1
then substring_index(substring_index(emails, ', ', 2), ', ', -1)
end) as email_2,
(case when length(emails) - length(replace(emails, ',', '')) >= 2
then substring_index(substring_index(emails, ', ', 3), ', ', -1)
end) as email_3,
(case when length(emails) - length(replace(emails, ',', '')) >= 3
then substring_index(substring_index(emails, ', ', 4), ', ', -1)
end) as email_4,
(case when length(emails) - length(replace(emails, ',', '')) >= 4
then substring_index(substring_index(emails, ', ', 5), ', ', -1)
end) as email_5
from table t;
You can insert these values into another table, if you like.
Don't use that view, since GROUP_CONCAT() has already ruined the normalization. You want to simulate a pivot table using the limited SQL capabilities of MySQL.
Let's assume that your view is based on a Contacts table that looks like this:
CREATE TABLE Contacts
( id INTEGER NOT NULL
, alt_email_contact VARCHAR(256) NOT NULL
);
Create this helper view instead (which is basically RANK() OVER (PARTITION BY id ORDER BY alt_email_contact), except that MySQL doesn't support RANK()):
CREATE VIEW NumberedContacts AS
SELECT c1.id, c1.alt_email_contact, COUNT(*) AS rank
FROM Contacts c1
INNER JOIN Contacts c2
ON c2.id = c1.id AND
c1.alt_email_contact >= c2.alt_email_contact
GROUP BY c1.id, c1.alt_email_contact;
Then you can write this query or view, which gives you up to 5 alternate e-mail addresses, ordered alphabetically:
CREATE VIEW ContactsForImport AS
SELECT c1.id
, c1.alt_email_contact AS email_1
, c2.alt_email_contact AS email_2
, c3.alt_email_contact AS email_3
, c4.alt_email_contact AS email_4
, c5.alt_email_contact AS email_5
FROM NumberedContacts AS c1
LEFT OUTER JOIN NumberedContacts AS c2
ON c1.id = c2.id AND c2.rank = 2
LEFT OUTER JOIN NumberedContacts AS c3
ON c1.id = c3.id AND c3.rank = 3
LEFT OUTER JOIN NumberedContacts AS c4
ON c1.id = c4.id AND c4.rank = 4
LEFT OUTER JOIN NumberedContacts AS c5
ON c1.id = c5.id AND c5.rank = 5
WHERE c1.rank = 1;
SQL Fiddle

MySQL GROUP BY each comma separated value

Before anyone comments, I did not design this database with comma separated values :)
I have spent time trying to find the answer but all I could find was GROUP_CONCAT() which seemed to do the opposite of what I wanted.
I would like to GROUP BY each of the values within the comma separated value field.
SELECT round(avg(DATEDIFF( dateClosed , dateAded ) * 1.0), 2) AS avg, department
FROM tickets GROUP BY assignedto
the assignedto field is the comma separated value field
row1 54,69,555
row2 54,75,555
row3 75,555
DESIRED OUTPUT: an average rounded figure for each value in assignedto field grouped.
EDIT - TRYING TO TAKE THIS TO THE NEXT LEVEL:
I want to include the ticket answer table to get the first response for that ticket, use its datetime field to work out the average response time for each user.
SELECT a.id as theuser, round(avg(DATEDIFF( ta.dateAded , t.dateAded ) * 1.0), 2) as avg
FROM tickets t join
mdl_user a
on find_in_set(a.id, t.assignedto) > 0
INNER JOIN (SELECT MIN(ta.dateAded) as started FROM ticketanswer GROUP BY ta.ticketId) ta ON t.id = ta.ticketId
GROUP BY a.id ORDER BY avg ASC
Yuck. You can do this, assuming you know the maximum number of assignments. Here is an approach:
select substring_index(substring_index(assignedto, ',', n.n), ',', -1) as assignedto,
round(avg(DATEDIFF( dateClosed , dateAded ) * 1.0), 2) as avg
from tickets t join
(select 1 as n union all select 2 union all select 3)
on length(assignedto) - length(replace(assignedto, ',', '')) < n.n
group by substring_index(substring_index(assignedto, ',', n.n), ',', -1);
Or, an easier way if you have a list of assigned values, say in an AssignedTo table:
select a.assignedto, round(avg(DATEDIFF( dateClosed , dateAded ) * 1.0), 2) as avg
from tickets t join
assignedto a
on find_in_set(a.assignedto, t.assignedto) > 0
group by a.assignedto;
I'm sorry you have to deal with this malformed database structure.

MySQL: Why does where clause in subselect on result of another subselect not work?

I have this select statement:
select A.id,
(select id from B order by rand() limit 1)b1,
(select id from B where not id in(b1) order by rand() limit 1)b2,
(select id from B where not id in(b1,b2) order by rand() limit 1)b3,
(select id from B where not id in(b1,b2,b3) order by rand() limit 1)b4,
(select id from B where not id in(b1,b2,b3,b4) order by rand() limit 1)b5
from A
It's not very fast, it doesn't give me an error, but also doesn't do what I want.
I want to read 5 random id's from Table B and connect them to Table A.
So far so good, i get a result with 5 id's from Table B, BUT there are doubles.
Even though i have this where clause that should prevent doubles, i get them.
For example A.id:1 has b1=1, b2=6, b3=1, b4=9, B5=3
I would understand if MySQL throws an error because it can't handle the statement, but there is nothing, so I think it should work, but it doesn't.
Anyone has an answer to this?
Edit:
It doesn't matter if the result looks like this(subquery):
1:2,7,3,9,6
or like this(join):
1:2
1:7
1:3
1:9
1:6
As long as every A.id has different B.id's. It's ok for two or more A.Id's to have the same B.id's, but it should be coincidental.
Still the question why MySQL accepts the query and gives a wrong result.
select id, b1, b2, b3, b4, b5
from (
select A.id,
# := (select GROUP_CONCAT(DISTINCT id ORDER BY RAND()) AS ids from B),
SUBSTRING_INDEX(SUBSTRING_INDEX(#, ',', 1), ',', -1) b1,
SUBSTRING_INDEX(SUBSTRING_INDEX(#, ',', 2), ',', -1) b2,
SUBSTRING_INDEX(SUBSTRING_INDEX(#, ',', 3), ',', -1) b3,
SUBSTRING_INDEX(SUBSTRING_INDEX(#, ',', 4), ',', -1) b4,
SUBSTRING_INDEX(SUBSTRING_INDEX(#, ',', 5), ',', -1) b5
from A
) t
Example: http://sqlfiddle.com/#!2/d7df9/9
Please check this post for selecting random rows: How to randomly select rows in SQL? You can then join those with the other table
SELECT B.id
FROM B JOIN A ON B.id=A.id
ORDER BY RAND()
LIMIT 5

SQL GROUP_CONCAT split in different columns

I searched a lot, but didn't find a proper solution to my problem.
What do I want to do?
I have 2 tables in MySQL:
- Country
- Currency
(I join them together via CountryCurrency --> due to many to many relationship)
See this for a working example: http://sqlfiddle.com/#!2/317d3/8/0
I want to link both tables together using a join, but I want to show just one row per country (some countries have multiple currencies, so that was the first problem).
I found the group_concat function:
SELECT country.Name, country.ISOCode_2, group_concat(currency.name) AS currency
FROM country
INNER JOIN countryCurrency ON country.country_id = countryCurrency.country_id
INNER JOIN currency ON currency.currency_id = countryCurrency.currency_id
GROUP BY country.name
This has the following result:
NAME ISOCODE_2 CURRENCY
Afghanistan AF Afghani
Ă…land Islands AX Euro
Albania AL Lek
Algeria DZ Algerian Dinar
American Samoa AS US Dollar,Kwanza,East Caribbean Dollar
But what I want now is to split the currencies in different columns (currency 1, currency 2, ...). I already tried functions like MAKE_SET() but this doesn't work.
You can do this with substring_index(). The following query uses yours as a subquery and then applies this logic:
select Name, ISOCode_2,
substring_index(currencies, ',', 1) as Currency1,
(case when numc >= 2 then substring_index(substring_index(currencies, ',', 2), ',', -1) end) as Currency2,
(case when numc >= 3 then substring_index(substring_index(currencies, ',', 3), ',', -1) end) as Currency3,
(case when numc >= 4 then substring_index(substring_index(currencies, ',', 4), ',', -1) end) as Currency4,
(case when numc >= 5 then substring_index(substring_index(currencies, ',', 5), ',', -1) end) as Currency5,
(case when numc >= 6 then substring_index(substring_index(currencies, ',', 6), ',', -1) end) as Currency6,
(case when numc >= 7 then substring_index(substring_index(currencies, ',', 7), ',', -1) end) as Currency7,
(case when numc >= 8 then substring_index(substring_index(currencies, ',', 8), ',', -1) end) as Currency8
from (SELECT country.Name, country.ISOCode_2, group_concat(currency.name) AS currencies,
count(*) as numc
FROM country
INNER JOIN countryCurrency ON country.country_id = countryCurrency.country_id
INNER JOIN currency ON currency.currency_id = countryCurrency.currency_id
GROUP BY country.name
) t
The expression substring_index(currencies, ',' 2) takes the list in currencies up to the second one. For American Somoa, that would be 'US Dollar,Kwanza'. The next call with -1 as the argument takes the last element of the list, which would be 'Kwanza', which is the second element of currencies.
Also note that SQL queries return a well-defined set of columns. A query cannot have a variable number of columns (unless you are using dynamic SQL through a prepare statement).
Use this query to work out the number of currency columns you'll need:
SELECT MAX(c) FROM
((SELECT count(currency.name) AS c
FROM country
INNER JOIN countryCurrency ON country.country_id = countryCurrency.country_id
INNER JOIN currency ON currency.currency_id = countryCurrency.currency_id
GROUP BY country.name) as t)
Then dynamically create and execute prepared statement to generate the result, using Gordon Linoff solution with query result above to in this thread.
Ypu can use dynamic SQL, but you will have to use procedure