SQL Select 语句中的动态列,保留“未定义”值

Posted

技术标签:

【中文标题】SQL Select 语句中的动态列,保留“未定义”值【英文标题】:Dynamic Columns in SQL Select statement, keeping "undefined" values 【发布时间】:2017-09-27 11:22:51 【问题描述】:

这是一个基于我之前的问题的新问题,该问题被标记为问题的“重复” mysql - Create a New Table Using Data and Columns from Three Tables

这个问题看起来很相似,但有一个重要的部分是不同的 我尝试使用链接中的解决方案,但 Ansers 不适合我的问题。

我个人的 SQL 技能有限。环顾了几天后,我没有在下面找到针对我的数据库查询问题的任何有效解决方案。我在这个问题的末尾附上了完整的示例数据库 SQL 文本。 我的示例数据库(使用 MariaDB 制作)包含两个表:

项目和 项目属性。

对于每个项目,只定义了一个 item.ID 和一个 item.Name。 (在现实生活中,名称将被定义为唯一的。)

对于每个项目,一个动态的用户定义的属性集是可能的。这些属性被定义为名称-值-对。 例如,对于名为“Banana”的项目,可能存在值为“yellow”的属性“Color”。

只有一个项目有一个“颜色”属性才有效,因此不能将两种不同的颜色分配给一个项目。

(在我的实际问题中,属性名称仅包含两个字符,因此不需要额外的属性名称表,随后为了便于显示示例中未使用的问题)。

items 表的示例数据:

ID, Name
1,  Car
2,  House
3,  Homer
4,  Earth

并且为上述项目定义了总共九个属性。条目“(NULL)”表示没有为给定项目定义此属性

ItemID, ItemName, Color,    Speed,  Price
1,      Car,      blue,       200,  50000
2,      House,    red,     (NULL), 250000
3,      Homer,    yellow,       5, (NULL)
4,      Earth,    blue,    108000, (NULL)

不幸的是我的选择语句

SELECT items.ID as ItemID, items.Name as ItemName,
CASE WHEN (itemproperties.Name = 'Color')
       THEN itemproperties.Value
       #ELSE NULL
END as Color,
CASE WHEN (itemproperties.Name = 'Speed')
       THEN itemproperties.Value
       #ELSE NULL
END as Speed,
CASE WHEN (itemproperties.Name = 'Price')
       THEN itemproperties.Value
       #ELSE NULL
END as Price
FROM items left join itemproperties 
ON  (items.ID=itemproperties.ItemID)

像这样返回数据

ItemID, ItemName, Color,   Speed, Price
1,      Car,      blue,   (NULL), (NULL)
1,      Car,      (NULL),  200,   (NULL)
1,      Car,      (NULL), (NULL), 50000
2,      House,    red,    (NULL), (NULL)
2,      House,    (NULL), (NULL), 250000
3,      Homer,    yellow, (NULL), (NULL)
3,      Homer,    (NULL),      5, (NULL)
4,      Earth,    blue,   (NULL), (NULL)
4,      Earth,    (NULL), 108000, (NULL)

问题:如何编写select语句以整理形式获取数据,每个项目一行?

根据上面的链接问题,我也尝试了以下方法

SELECT i.ID as ItemID, i.Name as ItemName, 
       p1.Value AS Color, p2.Value AS Speed, p3.Value AS Price
FROM items as i
JOIN itemproperties AS p1 ON (i.ID=p1.ItemID)
JOIN itemproperties AS p2 ON (i.ID=p2.ItemID)
JOIN itemproperties AS p3 ON (i.ID=p3.ItemID)
WHERE (p1.Name = 'Color') and (p2.Name = 'Speed') and (p3.Name = 'Price')

但结果只有一行:

ItemID, ItemName, Color, Speed, Price
1,      Car,      blue,    200, 50000

这种行为的原因是,只有项目“汽车”具有填充值的所有三个属性“颜色”、“速度”和“价格”。 项目“房子”被跳过,因为它有一个“颜色”和一个“价格”,但显然没有“速度”。

那么如何以想要的方式拿到桌子呢?

你好,埃克哈德

数据库定义:

-- --------------------------------------------------------
-- Host:                         127.0.0.1
-- Server Version:               10.1.13-MariaDB - mariadb.org binary distribution
-- Server Betriebssystem:        Win32
-- HeidiSQL Version:             9.4.0.5125
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;


-- Exportiere Datenbank Struktur für DynamicColTest
CREATE DATABASE IF NOT EXISTS `dynamiccoltest` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE `DynamicColTest`;

-- Exportiere Struktur von Tabelle DynamicColTest.itemproperties
CREATE TABLE IF NOT EXISTS `itemproperties` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique ID of the property',
  `ItemID` int(10) unsigned DEFAULT '0' COMMENT 'ID of the Item this property belongs to',
  `Name` varchar(20) DEFAULT '0' COMMENT 'Name of the property',
  `Value` varchar(20) DEFAULT '0' COMMENT 'Value of the property',
  UNIQUE KEY `Schlüssel 3` (`Name`,`ItemID`),
  KEY `Schlüssel 1` (`ID`),
  KEY `FK_itemproperties_items` (`ItemID`),
  CONSTRAINT `FK_itemproperties_items` FOREIGN KEY (`ItemID`) REFERENCES `items` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COMMENT='The properties of the items';

-- Exportiere Daten aus Tabelle DynamicColTest.itemproperties: ~9 rows (ungefähr)
DELETE FROM `itemproperties`;
/*!40000 ALTER TABLE `itemproperties` DISABLE KEYS */;
INSERT INTO `itemproperties` (`ID`, `ItemID`, `Name`, `Value`) VALUES
    (1, 1, 'Color', 'blue'),
    (1, 4, 'Color', 'blue'),
    (1, 2, 'Color', 'red'),
    (2, 3, 'Color', 'yellow'),
    (3, 1, 'Speed', '200'),
    (3, 4, 'Speed', '108000'),
    (4, 3, 'Speed', '5'),
    (5, 1, 'Price', '50000'),
    (5, 2, 'Price', '250000');
/*!40000 ALTER TABLE `itemproperties` ENABLE KEYS */;

-- Exportiere Struktur von Tabelle DynamicColTest.items
CREATE TABLE IF NOT EXISTS `items` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique Item ID',
  `Name` varchar(25) DEFAULT '0' COMMENT 'Name of the Item',
  KEY `Schlüssel 1` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 COMMENT='Contains all Items, with a minimum of definitions';

-- Exportiere Daten aus Tabelle DynamicColTest.items: ~4 rows (ungefähr)
DELETE FROM `items`;
/*!40000 ALTER TABLE `items` DISABLE KEYS */;
INSERT INTO `items` (`ID`, `Name`) VALUES
    (1, 'Car'),
    (2, 'House'),
    (3, 'Homer'),
    (4, 'Earth');
/*!40000 ALTER TABLE `items` ENABLE KEYS */;

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

【问题讨论】:

好的,现在你确实有一个副本。 Ekkehard,请找出适合您的答案(我认为我的答案是最好的),然后将另一个标记为 dup。 (如果需要,请有更多“声誉”的人去做)。 The dup 【参考方案1】:

你很亲密。您需要为需要检索的每个不同的键(属性)加入一次键/值表itemproperties。问题是,您需要使用LEFT JOIN。当连接条件不满足时,普通内部 JOIN 会抑制输出行。

试试这个。

SELECT i.ID as ItemID, i.Name as ItemName, 
       p1.Value AS Color, p2.Value AS Speed, p3.Value AS Price
  FROM items as i
  LEFT JOIN itemproperties AS p1 ON (i.ID=p1.ItemID) AND (p1.Name = 'Color')
  LEFT JOIN itemproperties AS p2 ON (i.ID=p2.ItemID) AND (p2.Name = 'Speed')
  LEFT JOIN itemproperties AS p3 ON (i.ID=p3.ItemID) AND (p3.Name = 'Price')

选择 Name 值的表达式 (z.B. p3.Name = 'Price') 进入您的 ON 子句而不是 WHERE 子句。

【讨论】:

非常感谢,您的 select 语句确实运行良好!我确信需要“左连接”(如我的第一种方法所示),但我真正错过的是AND (p1.Name = 'Color') 部分。现在我可以继续处理我真正的生活问题了。【参考方案2】:

下面的答案是针对SQL SERVER,但我希望它可以对你有所帮助。我正在使用PIVOT函数来实现结果。你特别可以类似的函数in MariaDB。

WITH cte as(   
SELECT i.ID as ItemID, i.Name as ItemName,p1.name,p1.value
FROM items as i
JOIN itemproperties AS p1 ON (i.ID=p1.ItemID)
)

select * from cte
PIVOT
(
    max(value) for Name in ([Color],[Speed],[Price])
)A

REXTESTER DEMO

【讨论】:

@O.Jones 这是快速谷歌搜索返回的结果。 link CTE 将出现在 MariaDB 10.2 和 MySQL 8.0.1 中。但PIVOT 关键字不是。

以上是关于SQL Select 语句中的动态列,保留“未定义”值的主要内容,如果未能解决你的问题,请参考以下文章

SELECT 语句中的列别名不适用于 SQuirrel SQL + Firebird

动态执行SQL语句,拼接字符串,select中带有一个变量

如何在Select语句中声明SQL oracle中的null列

在 Oracle Sql 中的 Select 语句中根据条件排除和包含列标题

如何在动态SQL(SQL Server)中的Select语句中使用游标值

SQL动态SELECT语句来自存储在表中的值