MySQL: Compare comma separated values with values in different rows - mysql

I have following requirements.
I have two tables T1 and T2 like
Table T1
Product Geography
P1 G1
P2 G1
P2 G2
Table T2
Product Geography
P1 G1, G2
P2 G1, G2
I want a query to get data from table T2 if comma separated Geography have exactly matching records in T1. If there are less or more geographies in any table than it should not return that row. Sequence of geographies in T2 is not fixed. So return of that query from above example will be:
Product Geography
P2 G1, G2

Join on concating values
SELECT t2.Product,t2.geography FROM t2
JOIN
(SELECT t1.Product,GROUP_CONCAT(t1.geography ORDER BY t1.Geography SEPARATOR ', ') as concatgeo FROM t1
GROUP BY t1.product)x
ON t2.Geography=x.concatgeo AND t2.Product=x.Product

SELECT
firstTable.Product,
firstTable.geographies
FROM
(
SELECT
Product,
REPLACE(GROUP_CONCAT(Geography),' ','') AS geographies
FROM T1
GROUP BY Product) firstTable
INNER JOIN
(
SELECT
Product,
REPLACE(Geography,' ','') AS geographies
FROM T2 ) secondTable
ON firstTable.Product = secondTable.Product
WHERE firstTable.geographies = secondTable.geographies;
Note: I've replaced the spaces using REPLACE function just to ensure the two queries from two tables generate the same string
SQL FIDDLE DEMO
TEST (If you cannot access sqlfiddle):
DROP TABLE IF EXISTS `t1`;
CREATE TABLE `t1` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Product` varchar(50) CHARACTER SET utf8 NOT NULL,
`Geography` varchar(100) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`ID`)
);
INSERT INTO `t1` VALUES ('1', 'P1', 'G1');
INSERT INTO `t1` VALUES ('2', 'P2', 'G1');
INSERT INTO `t1` VALUES ('3', 'P2', 'G2');
DROP TABLE IF EXISTS `t2`;
CREATE TABLE `t2` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Product` varchar(50) CHARACTER SET utf8 NOT NULL,
`Geography` varchar(100) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`ID`)
);
INSERT INTO `t2` VALUES ('1', 'P1', 'G1, G2');
INSERT INTO `t2` VALUES ('2', 'P2', 'G1, G2');
Running the above query on this test data you will get output like below:
Product geographies
P2 G1,G2

Related

Convert MySQL JSON array with unpredictable keys to a table, and join

Given the following MySQL 8 table structures...
CREATE TABLE `products` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(1000) NOT NULL,
PRIMARY KEY (`id`)
);
/* the "names" of the custom attributes that could exist for any given product */
CREATE TABLE `custom_attributes` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
/* the custom attributes of products stored as json array, when "key" is FK to custom_attributes table, and "value" is the attribute value for the given product */
/* in an ideal design, this table would not exist as there is a one-to-one relationship with `product`, and so could have simply been an `attributes` column in the product table, but we are where we are... */
CREATE TABLE `product_custom_attributes` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`product_id` bigint unsigned NOT NULL,
`attribute_value` json DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `product_custom_attributes_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
);
...and the following data...
INSERT INTO products (`id`, `name`) VALUES (1, 'Big Blue Ball');
INSERT INTO products (`id`, `name`) VALUES (2, 'Small Wooden Bat');
INSERT INTO custom_attributes (`id`, `title`) VALUES (1, 'Size');
INSERT INTO custom_attributes (`id`, `title`) VALUES (2, 'Color');
INSERT INTO custom_attributes (`id`, `title`) VALUES (3, 'Material');
INSERT INTO custom_attributes (`id`, `title`) VALUES (4, 'Height');
INSERT INTO product_custom_attributes (`id`, `product_id`, `attribute_value`) VALUES (1, 1, '{"1": "Big", "2": "Blue"}');
INSERT INTO product_custom_attributes (`id`, `product_id`, `attribute_value`) VALUES (2, 2, '{"1": "Small", "3": "Wood"}');
...how can I write a query to return the following results...?
product_id
name
Size
Color
Material
Height
1
Big Blue Ball
Big
Blue
2
Small Wooden Bat
Small
Wood
I cannot predict, at runtime, what custom_attributes might exist for products because it's likely this list will change over time, e.g. new attributes added.
I'm pretty sure I'll need to use the JSON_TABLE() function, but I've struggled to get result with the unpredicatable attributes (and therefore unpredictable JSON "columns") in play.
Edit - Solution
SET SESSION group_concat_max_len=4294967295;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(ca.title = ''',
ca.title,
''', attribute_values.attribute_value, NULL)) AS `',
ca.title, '`'
)
) INTO #sql
FROM
products p
LEFT OUTER JOIN custom_attributes ca ON p.project_id = ca.project_id
LEFT OUTER JOIN product_custom_attributes pca ON p.id = pca.product_id
LEFT OUTER JOIN JSON_TABLE(
JSON_KEYS(pca.attribute_value),
'$[*]' COLUMNS (
rn FOR ORDINALITY,
attribute_key VARCHAR(255) PATH '$'
)
) attribute_keys ON ca.id = attribute_keys.attribute_key
LEFT OUTER JOIN JSON_TABLE(
JSON_EXTRACT(pca.attribute_value, '$.*'),
'$[*]' COLUMNS (
rn FOR ORDINALITY,
attribute_value VARCHAR(255) PATH '$'
)
) attribute_values ON attribute_values.rn = attribute_keys.rn;
-- if there are no custom attributes #sql will be NULL and when appended to the following query will make the whole thing NULL, so deal with this...
SET #sql = CASE WHEN ISNULL(#sql) THEN '' ELSE CONCAT(', ', #sql) END;
SET #sql = CONCAT('
SELECT
p.id
, p.name
', #sql, '
FROM
products p
LEFT OUTER JOIN custom_attributes ca ON p.project_id = ca.project_id
LEFT OUTER JOIN product_custom_attributes pca ON p.id = pca.product_id
LEFT OUTER JOIN JSON_TABLE(
JSON_KEYS(pca.attribute_value),
''$[*]'' COLUMNS (
rn FOR ORDINALITY,
attribute_key VARCHAR(255) PATH ''$''
)
) attribute_keys ON ca.id = attribute_keys.attribute_key
LEFT OUTER JOIN JSON_TABLE(
JSON_EXTRACT(pca.attribute_value, ''$.*''),
''$[*]'' COLUMNS (
rn FOR ORDINALITY,
attribute_value VARCHAR(255) PATH ''$''
)
) attribute_values ON attribute_values.rn = attribute_keys.rn
GROUP BY p.id, p.name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

How to retrieve data from table with join

I'm using MySQL 5.5. with two tables in it:
DROP TABLE IF EXISTS `events_dictionary`;
CREATE TABLE `events_dictionary` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `events_dictionary` VALUES (1, 'Light'),(2, 'Switch'),(3, 'on'),(4, 'off');
DROP TABLE IF EXISTS `events_log`;
CREATE TABLE `events_log` (
`log_id` bigint(20) NOT NULL AUTO_INCREMENT,
`event_name_id` int(11) NOT NULL DEFAULT '0',
`event_param1` int(11) DEFAULT NULL,
`event_value1` int(11) DEFAULT NULL,
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `events_log` VALUES (1, 1, 2, 3),(2, 1, 2, 4);
Table events_dictionary contains names for events_log events names,params and values.
So, my question is - how could i select data from event_log table with columns event_name_id, event_param1, event_value1 mapped to name values from events_dictionary table?
I tried to do this query:
SELECT name, event_param1, event_value1
FROM events_log
JOIN events_dictionary ON events_log.event_name_id = events_dictionary.id;
But, in this case i see only event_name_id replaced with values from events_dictionary, like this:
name | event_param1 | event_value1
Light | 1 | 1
Light | 1 | 2
And i want to replace event_param1, and event_value1 with names from events_dictionary too.
Thanks in advance!
You need to join to the events_dictionary multiple times
SELECT a.name, b.name, c.name
FROM events_log
JOIN events_dictionary a ON events_log.event_name_id = a.id
JOIN events_dictionary b ON events_log.event_param1 = b.id
JOIN events_dictionary c ON events_log.event_value1 = c.id;
PS
Your example for the event_log isn't that helpful , instead insert the values (1,1,2,3),(2,1,2,4) to turn the switch on and off for the light.
DS
You can use correlated subqueries:
SELECT name,
(SELECT t.name
FROM events_dictionary AS t
WHERE t.id = event_param1) AS param_name,
(SELECT t2.name
FROM events_dictionary AS t2
WHERE t2.id = event_value1) AS event_name
FROM events_log AS el
JOIN events_dictionary AS ed ON el.event_name_id = ed.id;
Demo here

Add records randomly between select result

I want to add records randomly in select result like this example:
1 a
2 a
- b
3 a
- b
4 a
5 a
6 a
- b
7 a
8 a
- b
In a simple query I select from a table but in this case I want result mix with b table without random a result otherwise I simply could union them and then order random().
EDIT:
-- ----------------------------
-- Table structure for t1
-- ----------------------------
DROP TABLE IF EXISTS `t1`;
CREATE TABLE `t1` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
INSERT INTO `t1` VALUES ('1', 'a');
INSERT INTO `t1` VALUES ('2', 'a');
INSERT INTO `t1` VALUES ('3', 'a');
INSERT INTO `t1` VALUES ('4', 'a');
INSERT INTO `t1` VALUES ('5', 'a');
INSERT INTO `t1` VALUES ('6', 'a');
INSERT INTO `t1` VALUES ('7', 'a');
INSERT INTO `t1` VALUES ('8', 'a');
INSERT INTO `t1` VALUES ('9', 'a');
INSERT INTO `t1` VALUES ('10', 'a');
-- ----------------------------
-- Table structure for t2
-- ----------------------------
DROP TABLE IF EXISTS `t2`;
CREATE TABLE `t2` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `t2` VALUES ('1', 'b');
INSERT INTO `t2` VALUES ('2', 'b');
INSERT INTO `t2` VALUES ('3', 'b');
So I can union two tables and then order by random and the result will be like this:
SELECT
t1.ID,
t1.name
FROM
t1
UNION ALL
SELECT
t2.ID,
t2.name
FROM
t2
ORDER BY RAND();
5 a
2 a
6 a
3 a
7 a
2 b
9 a
1 a
10 a
3 b
4 a
8 a
1 b
But I don't want to random records I just only want mix two tables records like first example.
I hope my question is now clear.
I don't think pure SQL is the best solution here, I would do it in some end framework (I don't know what you use), like PHP, Delphi, Java, etc. This way is more easier and you can control it better. But if you would like to do it in SQL, here an idea:
SET #max_t1 = (SELECT MAX(ID) FROM t1);
SET #max_t2 = (SELECT MAX(ID) FROM t2);
SET #last_order = RAND()*#max_t1/#max_t2;
SELECT
t1.ID, t1.name, t1.ID AS "Ord"
FROM
t1
UNION ALL
SELECT
t2.ID, t2.name, #last_order:=#last_order+RAND()*#max_t1/#max_t2
FROM
t2
ORDER BY Ord
This way we generate a random position for TableB, then in every row we add some random value to it, in according to the size differency between the two tables, then order the whole result on this column.

SQL join table to self to find difference

Consider the below table
CREATE TABLE `temp` (
`id` int(11) NOT NULL,
`lang` char(2) COLLATE utf8_unicode_ci NOT NULL,
`channel` char(2) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`,`lang`,`channel`)
)
insert into `temp` (`id`, `lang`, `channel`, `name`) values('1','fr','ds','Jacket');
insert into `temp` (`id`, `lang`, `channel`, `name`) values('1','en','ds','Jacket');
insert into `temp` (`id`, `lang`, `channel`, `name`) values('2','en','ds','Jeans');
insert into `temp` (`id`, `lang`, `channel`, `name`) values('3','en','ds','Sweater');
insert into `temp` (`id`, `lang`, `channel`, `name`) values('1','de','ds','Jacket');
The question is how can I find which entries with lang en do not exist for fr?
My head is stuck and I believe this to be a trivial query but I am having one of these days.
There are several ways of achieving this. One way is to use a sub-select with not exists clause:
select id, channel
from temp t1
where t1.lang = 'en'
and not exists (
select 1
from temp t2
where t2.lang = 'fr'
and t1.id = t2.id
)
Alternatively, you can use an outer join:
select t1.id, t1.channel
from temp t1
left outer join temp t2 on t1.id = t2.id
where t1.lang = 'en'
and t2.id is null
based on #AleksG
SELECT t1.id, t1.channel, t1.name
FROM temp t1
LEFT JOIN temp t2 ON t1.id = t2.id AND t2.lang = 'fr'
WHERE t1.lang = 'en' AND t2.lang IS NULL
You can do this with aggregation:
select t.channel, t.name
from temp t
group by t.channel, t.name
having sum(case when t.lang = 'fr' then 1 else 0 end) = 0 and
sum(case when t.lang = 'en' then 1 else 0 end) > 0;
The first condition in the having clause counts up the number of times that French appears. The second counts up the number of times that English appears. When there are none for French and at least one for English, then the channel and name are in the result set.

compare data in a table

I have a scenario in which I have to check whether a data already exist in a table or not. If a user n application already exists then we don't need to perform any operation else perform insertion.
CREATE TABLE `table1` (
`ID` INT(11) NOT NULL AUTO_INCREMENT,
`GroupName` VARCHAR(100) DEFAULT NULL,
`UserName` VARCHAR(100) DEFAULT NULL,
`ApplicationName` VARCHAR(100) DEFAULT NULL,
`UserDeleted` BIT DEFAULT 0,
PRIMARY KEY (`ID`)
)
CREATE TABLE `temp_table` (
`ID` INT(11) NOT NULL AUTO_INCREMENT,
`GroupName` VARCHAR(100) DEFAULT NULL,
`UserName` VARCHAR(100) DEFAULT NULL,
`ApplicationName` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`ID`)
)
table1 is the main table while temp_table from which I have to perform comparision.
Sample date script:
INSERT INTO `table1`(`ID`,`GroupName`,`UserName`,`ApplicationName`,`userdeleted`)
VALUES
('MidasSQLUsers','Kevin Nikkhoo','MySql Application',0),
('MidasSQLUsers','adtest','MySql Application',0),
('Salesforce','Kevin Nikkhoo','Salesforce',0),
('Salesforce','devendra talmale','Salesforce',0);
INSERT INTO `temp_table`(`ID`,`GroupName`,`UserName`,`ApplicationName`,`userdeleted`)
VALUES
('MidasSQLUsers','Kevin Nikkhoo','MySql Application',0),
('MidasSQLUsers','adtest','MySql Application',0),
('Salesforce','Kevin Nikkhoo','Salesforce',0),
('Salesforce','Kapil Singh','Salesforce',0);
Also if a row of temp_table does not exist int table1 then its status should be as userdeleted 1 as i mentioned in desired output.
Result: table1
ID GroupName UserName ApplicationName Deleted
1 MidasSQLUsers Kevin Nikkhoo MySql Application 0
2 MidasSQLUsers adtest MySql ApplicationName 0
3 Salesforce Kevin Nikkhoo Salesforce 0
4 Salesforce devendra talmale Salesforce 1
5 SalesForce Kapil Singh Salesforce 0
Please help
this should do the trick, change whatever is inside the concat, to met your specification.
first do one update to "delete" the rows that are not in tempTable:
update table t1 set deleted = 'YES'
where concat( t1.groupName, t1.Username, t1.application) NOT IN
(select concat( t2.groupName, t2.Username, t2.application) from tempTable t2);
second: insert the new records
insert into table1 t1 (t1.groupName, t1.username, t1.application_name, t1.deleted)
(select t2.groupName, t2.Username, t2.application, t2.deleted from tempTable t2
where concat(t2.userName, t2.application, t2.groupName, t2.deleted) **not in**
(select concat(t3.userName, t3.application, t3.groupName, t3.deleted)
from table1 t3)
the concat function will make a new row from existing rows... this way I can compare multiple fields at same time as if I was comparing only one...
the "not in" lets make you a query inside a query...
Slight variation on the above queries.
Using JOINs, which if you have an index on the userName, application and groupName fields will likely be faster
UPDATE table1 t1
LEFT OUTER JOIN temp_table t2
ON t1.userName = t2.userName
AND t1.application = t2.application
AND t1.groupName = t2.groupName
SET t1.deleted = CASE WHEN t2.ID IS NULL THEN 1 ELSE 0 END
For the normal insert
INSERT INTO table1 t1 (t1.groupName, t1.username, t1.application_name, t1.deleted)
(SELECT t2.groupName, t2.Username, t2.application, t2.deleted
FROM tempTable t2
LEFT OUTER JOIN table1 t3
ON t2.userName = t3.userName
AND t2.application = t3.application
AND t2.groupName = t3.groupName
WHERE t3.ID IS NULL)