MySQL: выбрать строку по умолчанию (не значения по умолчанию), если другая строка недоступна - PullRequest
2 голосов
/ 13 февраля 2011

edit:

Я включил операторы создания и небольшой набор тестовых данных, которые вы можете попробовать.Поэтому я изменил пример id на 2 вместо 5 для представления существующего id в данных испытаний.

/ edit

У меня есть три таблицы MySQL для хранения информации о локализованной странице:

CREATE TABLE `locale` (
  `languageCode` char(3) NOT NULL,
  `regionCode` char(3) NOT NULL default 'ZZ',
  `isActive` enum('yes','no') NOT NULL default 'no',
  PRIMARY KEY  (`languageCode`,`regionCode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `page` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `parentId` int(10) unsigned default NULL,
  PRIMARY KEY  (`id`),
  KEY `FK_page_page_1` (`parentId`),
  CONSTRAINT `FK_page_page_1` FOREIGN KEY (`parentId`) REFERENCES `page` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `pageInfo` (
  `pageId` int(10) unsigned NOT NULL,
  `languageCode` char(3) NOT NULL,
  `regionCode` char(3) NOT NULL default 'ZZ',
  PRIMARY KEY  (`pageId`,`languageCode`,`regionCode`),
  KEY `FK_pageInfo_locale_1` (`languageCode`,`regionCode`),
  KEY `FK_pageInfo_page_1` (`pageId`),
  CONSTRAINT `FK_pageInfo_locale_1` FOREIGN KEY (`languageCode`, `regionCode`) REFERENCES `locale` (`languageCode`, `regionCode`) ON DELETE CASCADE,
  CONSTRAINT `FK_pageInfo_page_1` FOREIGN KEY (`pageId`) REFERENCES `page` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

А вот некоторые тестовые данные:

/* locale */
INSERT INTO `locale` (languageCode,regionCode,isActive) VALUES ('de','ZZ','yes');
INSERT INTO `locale` (languageCode,regionCode,isActive) VALUES ('en','ZZ','yes');
INSERT INTO `locale` (languageCode,regionCode,isActive) VALUES ('nl','ZZ','yes');
/* page */
INSERT INTO `page` (id,parentId) VALUES (1,NULL);
INSERT INTO `page` (id,parentId) VALUES (2,1);
/* pageInfo */
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (1,'de','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (1,'en','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (1,'nl','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (2,'en','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (2,'nl','ZZ');

Дилемма:
Чтобы получить страницы с id 2 всех активных языковых стандартов, я выполняю следующий оператор SQL:

SELECT 
        p.*,   pi.languageCode,   pi.regionCode
    FROM
        page AS p
        INNER JOIN pageInfo AS pi
            ON pi.pageId = p.id
        INNER JOIN locale AS l
            ON l.languageCode = pi.languageCode AND l.regionCode = pi.regionCode
    WHERE
        (p.id = '2')
        AND (l.isActive = 'yes')

Как бы я изменил этот оператор, чтобы, если id 2 недоступно для конкретной языковой системы,он автоматически возвращается на корневую страницу для этой локали (то есть: где page.parentId IS NULL)?Моя цель состоит в том, чтобы MySQL дал мне один, но не оба, для активных локалей.

Я пытался:

WHERE
    (p.id = '2' OR (p.parentId IS NULL))

Но, конечно, это дает мне две записи для локалейкоторые на самом деле имеют id 2 также.Я почти уверен, что это возможно (либо с UNION, вложенными выборками, либо с дублирующим соединением на странице), но здесь у меня есть блок писателей всего SQL.Буду признателен за вашу помощь.

Ответы [ 3 ]

0 голосов
/ 14 февраля 2011

Используйте левое соединение и соедините обе таблицы дважды. Второй раз вы присоединяетесь к ним специально для случая p.parentId IS NULL.

В списке SELECT используйте IFNULL. Первые аргументы предназначены для id, вторые - это запасные варианты, то есть значения для корневой страницы.

SELECT
    p.*,
    IFNULL(l.languageCode, lr.LanguageCode) AS languageCode,
    IFNULL(l.regionCode, lr.regionCode) AS regionCode
FROM
    page AS p
    LEFT JOIN pageInfo AS pi
        ON pi.pageId = p.id
    LEFT JOIN locale AS l
        ON l.languageCode = pi.languageCode AND l.regionCode = pi.regionCode
    LEFT JOIN pageInfo AS pir
        ON pir.pageId = p.id AND p.parentId IS NULL
    LEFT JOIN locale AS lr
        ON lr.languageCode = pir.languageCode AND lr.regionCode = pir.regionCode
WHERE
    (p.id = '5' AND l.isActive = 'yes')
    OR (p.parentId IS NULL AND lr.isActive = 'yes')
0 голосов
/ 14 февраля 2011

Вы можете заказать по p.id = 5 и использовать предел 1. Это немного хак, но это будет работать.

SELECT 
        p.*,   pi.languageCode,   pi.regionCode
    FROM
        page AS p
        INNER JOIN pageInfo AS pi
            ON pi.pageId = p.id
        INNER JOIN locale AS l
            ON l.languageCode = pi.languageCode AND l.regionCode = pi.regionCode
    WHERE
        (p.id = '5' OR (p.parentId IS NULL))
        AND (l.isActive = 'yes')
    ORDER BY p.id = '5' DESC
    LIMIT 1
0 голосов
/ 13 февраля 2011

Как насчет XOR?«один или другой, но не оба»:

WHERE
    (p.id = '5' XOR (p.parentId IS NULL))
                ^---here
...