Autor Zpráva
Petr_
Profil *
Dobrý den,

dejme tomu, že mám klasický případ databáze s články (articles) a jejich tagy (tags). Vztah 1:N realizovaný přes vazební tabulku articles_tags. Jednoduchou strukturu i s pár daty uvádím níže.

CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=4 ;

INSERT INTO `articles` (`id`, `content`) VALUES
(1, 'First article...'),
(2, 'Second article...'),
(3, 'Third article...');

CREATE TABLE IF NOT EXISTS `articles_tags` (
  `articles_id` int(11) NOT NULL,
  `tags_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `articles_tags` (`articles_id`, `tags_id`) VALUES
(1, 1),
(2, 1),
(2, 3),
(3, 1),
(3, 2),
(3, 3);

CREATE TABLE IF NOT EXISTS `tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=4 ;

INSERT INTO `tags` (`id`, `name`) VALUES
(1, 'First tag'),
(2, 'Second tag'),
(3, 'Third tag');

Není problém vybrat články podle určitého (jednoho) tagu třeba tímto způsobem:
SELECT * FROM articles JOIN articles_tags ON articles.id=articles_tags.articles_id JOIN tags ON tags.id=articles_tags.tags_id WHERE tags.id = 1

Co kdybych ale chtěl vybrat články, které obsahují třeba přesně 2 (nebo volitelně i více) konkrétní tagy? Tedy chtěl bych vybrat články, které mají tagy zároveň s id 1 a 3. Měly by se vybrat články s id 2 a 3. Jde to nějak jednoduše udělat jedním dotazem?
scheras
Profil *
SELECT * FROM articles JOIN articles_tags ON articles.id=articles_tags.articles_id JOIN tags ON tags.id=articles_tags.tags_id WHERE tags.id = 1 AND tags.id = 3
Tori
Profil
id se nemůže v jednom řádku rovnat dvěma hodnotám zároveň. Použijte dotaz z Některé časteji řešené dotazy pro MySQL - FAQ » Nalezení položek mající všechny hledané vlastnosti - pokud místo ID tagů máte jen jejich názvy, tak místo IN(1,2,3) bude poddotaz.
Petr_
Profil *
Tori:
Děkuji za nakopnutí. Takže ono to díky tomu HAVING COUNT(DISTINCT tags.id) = 2 dodatečně vyfiltruje na základě toho, že tam existuje stejný počet unikátních iděček. To je teda pěkně "ujeté" řešení :-).

SELECT *, GROUP_CONCAT(tags.id) FROM articles JOIN articles_tags ON articles.id=articles_tags.articles_id JOIN tags ON tags.id=articles_tags.tags_id WHERE tags.id IN( 1,3) GROUP BY articles.id HAVING COUNT(DISTINCT tags.id) = 2

Já jsem původně uvažoval s něčím takovýmto:
SELECT *, CONCAT(",", GROUP_CONCAT(DISTINCT(tags.id)), ",") AS tagy FROM articles JOIN articles_tags ON articles.id=articles_tags.articles_id JOIN tags ON tags.id=articles_tags.tags_id  GROUP BY articles.id HAVING tagy LIKE "%,1,3,%" OR tagy LIKE "%,1,%,3,%" OR tagy LIKE "%,3,1,%" OR tagy LIKE "%,3,%,1,%"
To sice taky funguje, ale je to ještě ujetější. Navíc by se s vícero tagy muselo ošetřit více těch LIKE kombinací, což by se ale dalo generovat v PHP a připojit k tomu SQL dotazu. Případně to řešit nějakým šíleným regulárem nebo kombinací obého.
Tori
Profil
Petr:
Ten druhý dotaz by byl o dost pomalejší při větším počtu článků/tagů (jednak taháte všechny články+tagy a taky kvůli 4x LIKE).

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: