SQL Key value table--select ids that have multiple keys - mysql

I need to obtain records in a key-value table with the following structure:
CREATE TABLE `PROPERTY` (
`id` int(11) NOT NULL,
`key` varchar(64) NOT NULL,
`value` text NOT NULL,
PRIMARY KEY (`id`,`key`)
);
I need to get all ids that have MULTIPLE specific key-value entries. For example, all ids that have keys "foo", "bar", and "foobar".

Simply use GROUP BY to group and then check the group count to count multiple values:
Select
id
from
`PROPERTY`
group by
key, value
having
count(*) > 1

Given updated question...
since you know the specific keys, you also know how many there are... so a count distinct in having should do it... along with a where...
SELECT id
FROM `PROPERTY`
Where key in ('foo','bar','foobar')
GROUP BY ID
having count(distinct key) = 3

I f you need the id of all the rows for key,value count(= >1)
select id from `PROPERTY`
where (key, value) in (select key, value from `PROPERTY`group by
key, value
having
count(*) > 1)

Related

View with derived value column

I'm having some trouble trying to make a view with a calculated average column, for every movie row I need an average rating based on all the ratings for this movie in the rating table.
Movie table:
CREATE TABLE IF NOT EXISTS streaming_db.movie(
id BIGINT NOT NULL auto_increment
,name VARCHAR (100)
,description VARCHAR (1000)
,PRIMARY KEY (id)
) engine = InnoDB;
Rating table:
CREATE TABLE IF NOT EXISTS streaming_db.rating(
id BIGINT NOT NULL auto_increment
,rating_score DECIMAL(4, 2) NOT NULL
,comment VARCHAR (255) NULL
,id_profile BIGINT NOT NULL
,id_movie BIGINT NOT NULL
,PRIMARY KEY (id)
) engine = InnoDB;
Here's what I have so far:
CREATE VIEW streaming_db.midia
AS
SELECT name,
description
FROM streaming_db.movie a
INNER JOIN (SELECT avg(rating_score) AS averageRating from streaming_db.rating where
rating.id_movie = a.id);
It was telling me that a derived table needs its own alias, and I don't know if that really gives me the average per row.
You are attempting a correlated subquery in the FROM clause. Well, this is actually a real thing, called a lateral join.
But that is not your intention. Move the logic to the SELECT:
SELECT m.name, m.description,
(SELECT avg(rating_score)
FROM sistema_streaming_db.rating r
WHERE r.id_movie = m.id
) as averageRating
FROM streaming_db.movie m;
Note that I fixed the table aliases so they are abbreviations for the table names, which makes the query much easier to read.

Using SQL Sub-queries in an INSERT Statement

Here are the two tables created:
CREATE TABLE category_tbl(
id int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
subcategory varchar(255) NOT NULL,
PRIMARY KEY(id),
CONSTRAINT nameSubcategory UNIQUE KEY(name, subcategory)
) ENGINE=InnoDB;
CREATE TABLE device(
id INT NOT NULL AUTO_INCREMENT,
cid INT DEFAULT NULL,
name VARCHAR(255) NOT NULL,
received DATE,
isbroken BOOLEAN,
PRIMARY KEY(id),
FOREIGN KEY(cid) REFERENCES category_tbl(id)
) ENGINE=InnoDB;
Below is the instruction that was given to me:
-- insert the following devices instances into the device table (you should use a subquery to set up foriegn keys referecnes, no hard coded numbers):
-- cid - reference to name: phone subcategory: maybe a tablet?
-- name - Samsung Atlas
-- received - 1/2/1970
-- isbroken - True
I'm getting errors on the insert statement below from attempting to use a sub-query within an insert statement. How would you solve this issue?
INSERT INTO devices(cid, name, received, isbroken)
VALUES((SELECT id FROM category_tbl WHERE subcategory = 'tablet') , 'Samsung Atlas', 1/2/1970, 'True');
You have different table name in CREATE TABLE and INSERT INTO so just choose one device or devices
When insert date format use the good one like DATE('1970-02-01')
When insert boolean - just TRUE with no qoutes I beleive.
http://sqlfiddle.com/#!9/b7180/1
INSERT INTO devices(cid, name, received, isbroken)
VALUES((SELECT id FROM category_tbl WHERE subcategory = 'tablet') , 'Samsung Atlas', DATE('1970-02-01'), TRUE);
It's not possible to use a SELECT in an INSERT ... VALUES ... statement. The key here is the VALUES keyword. (EDIT: It is actually possible, my bad.)
If you remove the VALUES keyword, you can use the INSERT ... SELECT ... form of the INSERT statement statement.
For example:
INSERT INTO mytable ( a, b, c) SELECT 'a','b','c'
In your case, you could run a query that returns the needed value of the foreign key column, e.g.
SELECT c.id
FROM category_tbl c
WHERE c.name = 'tablet'
ORDER BY c.id
LIMIT 1
If we add some literals in the SELECT list, like this...
SELECT c.id AS `cid`
, 'Samsung Atlas' AS `name`
, '1970-01-02' AS `received`
, 'True' AS `isBroken`
FROM category_tbl c
WHERE c.name = 'tablet'
ORDER BY c.id
LIMIT 1
That will return a "row" that we could insert. Just precede the SELECT with
INSERT INTO device (`cid`, `name`, `received`, `isbroken`)
NOTE: The expressions returned by the SELECT are "lined up" with the columns in the column list by position, not by name. The aliases assigned to the expressions in the SELECT list are arbitrary, they are basically ignored. They could be omitted, but I think having the aliases assigned makes it easier to understand when we run just the SELECT portion.

Query field has to match multiple values

I'm currently having an issue with my query and i can't figure out the final step in order to make it work.
The problem is that i have a filter option on my website which gives visitors the possibility to filter on specific settings.
My query so far is
SELECT DISTINCT `spt`.`title`
FROM `shop_product_specs` as `sps` JOIN
`shop_product_texts` as `spt`
ON `spt`.`product_id` = `sps`.`product_id`
WHERE `cat_spec_id` IN (2, 3) AND (`value` IN ("1200", "1400")) AND (`value` IN ("A", "A+"))
What i am trying to accomplish is that when i execute the query the value field matches multiple values.
So for example i want a product which has the cat_spec_id of 2 with the value of 1200 or 1400 BUT ALSO matches cat_spec_id of 3 with the value of "A" or "A+".
The main problem of the query is that it is the same field, so is this even possible?
This is my structure
CREATE TABLE IF NOT EXISTS `shop_product_specs` (
`product_spec_id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`cat_spec_id` int(11) NOT NULL,
`value` varchar(50) NOT NULL,
PRIMARY KEY (`product_spec_id`),
KEY `product_id` (`product_id`),
KEY `cat_spec_id` (`cat_spec_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=312 ;
I think this is an example of a set-within-sets subquery. Here is a method that solves this with aggregation and a having clause:
SELECT `spt`.`title`
FROM `shop_product_specs` as `sps` JOIN
`shop_product_texts` as `spt`
ON `spt`.`product_id` = `sps`.`product_id`
group by spt.title
having max(cat_spec_id = 2 and value in ('1200', '1400')) > 0 and
max(cat_spec_id = 3 and value in ('A', 'A+')) > 0
Each of the conditions in the having clause is verifying that a row exists with the corresponding conditions.

Mysql. Select where field=1 or field=2 with IF

i need some query.
CREATE TABLE `location_areas_localized` (
`id` int(11) DEFAULT NULL,
`lang_index` varchar(5) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
KEY `id` (`id`),
KEY `lang_index` (`lang_index`),
KEY `name` (`name`),
FULLTEXT KEY `name_2` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `location_areas_localized` (`id`, `lang_index`,`name`)
VALUES
(1,'ru','Нью Йорк'),
(1,'en','New York'),
(2,'en','Boston'),
(2,'ch','波士顿')
;
Logic of search.
If row with lang_index='ru' AND id IN(1,2) found. it must return all with lang_index='ru'
If one or more rows with lang_index='ru' not exists But exists with lang_index='en' and with some id.
Then it must return all exists with land_index='ru' AND id IN(1,2) and all that not found with lang_index='ru' but found with lang_index='en' (in table - all rows with lang_index='en' always exists)
See on sqlfiddle
I need only one result per id. I tried GROUP BY id but its not works correctly.
Output must be
1,'ru','Нью Йорк'
2,'en','Boston' (because lang_index='ru' with id 2 not found)
SELECT
coalesce(max(CASE WHEN lang_index='ru' THEN name ELSE null END), name) as name
FROM
location_areas_localized
WHERE
id IN (1,2)
AND (lang_index='en' OR lang_index='ru')
group by
id
ORDER BY
FIELD(lang_index,'ru','en');
Without using aggregation functions, it only takes the first matching row. The subquery with ORDER BY enforce the fact that for the same id either the "ru" (or "en", if "ru" is not present) row is the first one.
SELECT *
FROM(
SELECT *
FROM location_areas_localized
ORDER BY FIELD(lang_index,'ru','en','ch')
) as inv
WHERE id IN (1,2)
GROUP BY id
See SQLFiddle example

Mysql query to get detail of comma-separated ids data

I have 2 tables, items and members :
CREATE TABLE IF NOT EXISTS `items` (
`id` int(5) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`member` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `members` (
`id` int(5) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
What if, for example I have a record inside items, such as
INSERT INTO `test`.`items` (
`id` ,
`name` ,
`member`
)
VALUES (
NULL , 'xxxx', '1, 2, 3'
);
in members :
INSERT INTO `members` (`id`, `name`) VALUES
(1, 'asdf'),
(2, 'qwert'),
(3, 'uiop'),
(4, 'jkl;');
and I'd like to display items.member data with members.name, something like 1#asdf, 2#qwert, 3#uiop??
I've tried the following query,
SELECT items.id, items.name, GROUP_CONCAT(CONCAT_WS('#', members.id, members.name) ) as member
FROM `items`
LEFT JOIN members AS members on (members.id = items.member)
WHERE items.id = 1
But the result is not like I expected. Is there any other way to display the data via one call query? Because I'm using PHP, right now, i'm explode items.member and loop it one by one, to display the members.name.
You could look into using FIND_IN_SET() in your join criteria:
FROM items JOIN members ON FIND_IN_SET(members.id, items.member)
However, note from the definition of FIND_IN_SET():
A string list is a string composed of substrings separated by “,” characters.
Therefore the items.member column should not contain any spaces (I suppose you could use FIND_IN_SET(members.id, REPLACE(items.member, ' ', '')) - but this is going to be extremely costly as your database grows).
Really, you should normalise your schema:
CREATE TABLE memberItems (
item_id INT(5) NOT NULL,
member_id INT(5) NOT NULL,
FOREIGN KEY item_id REFERENCES items (id),
FOREIGN KEY member_id REFERENCES members (id)
);
INSERT INTO memberItems
(item_id, member_id)
SELECT items.id, members.id
FROM items
JOIN members ON FIND_IN_SET(members.id, REPLACE(items.member,' ',''))
;
ALTER TABLE items DROP member;
This is both index-friendly (and therefore can be queried very efficiently) and has the database enforce referential integrity.
Then you can do:
FROM items JOIN memberItems ON memberItems.item_id = items.id
JOIN members ON members.id = memberItems.member_id
Note also that it's generally unwise to use GROUP_CONCAT() to combine separate records into a string in this fashion: your application should instead be prepared to loop over the resultset to fetch each member.
Please take a look at this sample:
SQLFIDDLE
Your query seems to work for what you have mentioned in the question... :)
SELECT I.ID, I.ITEM,
GROUP_CONCAT(CONCAT("#",M.ID,
M.NAME, " ")) AS MEMB
FROM ITEMS AS I
LEFT JOIN MEMBERS AS M
ON M.ID = I.MID
WHERE i.id = 1
;
EDITTED ANSWER
This query will not work for you¬ as your schema doesn't seem to have any integrity... or proper references. Plus your memeber IDs are delimtted by a comma, which has been neglected in this answer.