益智拼图,谜语功能(MySQL查询/搜索实验)
存储表
--------------------------------------------
| id | namespace | key | value |
--------------------------------------------
| 1 | page | item.id | test1 |
| 1 | page | content.title | page2 |
| 1 | trigger | tag | val1 |
| 2 | page | item.id | t1 |
| 2 | page | content.title | page3 |
| 2 | trigger | tag | val2 |
| 2 | oddball | num | in |
| 3 | truck | plate | 12345 |
--------------------------------------------
搜索参数:" page"可以在任何地方但不在id
所需的请求输出:
---------------------------------------------------------------------
|id | page.item.id | page.content.title | trigger.tag | oddball.num |
---------------------------------------------------------------------
|1 | test1 | page2 | val1 | NULL |
|2 | t1 | page3 | val2 | in |
---------------------------------------------------------------------
提示:
ok解决方案:使用后端语言的解决方案(例如:php)+ SQL查询
更好的解决方案:使用存储过程的解决方案
最佳解决方案:使用单个SQL查询的解决方案,(数据透视表?,临时表?)
最快的解决方案获胜! (50点赏金)
干杯!
目标是从agregated行获取动态列。
答案 0 :(得分:4)
要使其作为数据透视表工作,您必须运行两个查询:
获取要使用的列
select distinct concat(namespace,'.',`key`) as `column`,
namespace,`key` from your_table;
+--------------------+-----------+---------------+
| column | namespace | key |
+--------------------+-----------+---------------+
| page.item.id | page | item.id |
| page.content.title | page | content.title |
| trigger.tag | trigger | tag |
| oddball.num | oddball | num |
| truck.plate | truck | plate |
+--------------------+-----------+---------------+
结合唯一ID并将每个值作为子查询,为了防止子查询多个结果必须包含聚合函数,我使用了max()
。
我创建了一个存储过程:
DELIMITER $$
DROP PROCEDURE IF EXISTS `get_pivot_table`$$
CREATE PROCEDURE `get_pivot_table`()
BEGIN
declare done int default 0;
declare v_sql text;
declare v_column varchar(100);
declare v_namespace varchar(100);
declare v_key varchar(100);
-- (1) getting the columns with this cursor
declare c_columns cursor for
select distinct concat(namespace,'.',`key`) as `column`
, namespace
,`key`
from your_table;
declare continue handler for not found set done = 1;
open c_columns;
-- (2) now creating the sub-queries based on cursor results
set v_sql = "select p.id ";
read_loop: loop
fetch c_columns into v_column, v_namespace, v_key;
if done then
leave read_loop;
end if;
set v_sql = concat(v_sql,", (select max(t.`value`) from your_table t
where t.id = p.id
and t.namespace = '", v_namespace ,"'
and t.`key` = '", v_key ,"') as `", v_column,"` ");
end loop;
close c_columns;
-- now run the entire query
set @sql = concat(v_sql," from (select distinct id from your_table) as p");
prepare stmt1 from @sql;
execute stmt1;
deallocate prepare stmt1;
END$$
DELIMITER ;
然后你可以调用存储过程:
mysql> call get_pivot_table();
+------+--------------+--------------------+-------------+-------------+-------------+
| id | page.item.id | page.content.title | trigger.tag | oddball.num | truck.plate |
+------+--------------+--------------------+-------------+-------------+-------------+
| 1 | test1 | page2 | val1 | NULL | NULL |
| 2 | t1 | page3 | val2 | in | NULL |
| 3 | NULL | NULL | NULL | NULL | 12345 |
+------+--------------+--------------------+-------------+-------------+-------------+
3 rows in set (0.00 sec)
该查询的速度取决于your_table
的索引和数据量。
答案 1 :(得分:1)
这是我使用数据透视表的解决方案。虽然不在一个查询中......
USE tempdb
GO
CREATE TABLE _temp ([id] int, [namespace] varchar(20), [key] varchar(20), [value] varchar(20))
INSERT INTO _temp VALUES (1, 'page', 'content.title', 'page2')
INSERT INTO _temp VALUES (1, 'page', 'item.id', 'test1')
INSERT INTO _temp VALUES(1, 'trigger', 'tag', 'val1')
INSERT INTO _temp VALUES (2, 'oddball', 'num', 'in')
INSERT INTO _temp VALUES (2, 'page', 'content.title', 'page3')
INSERT INTO _temp VALUES (2, 'page', 'item.id', 't1')
INSERT INTO _temp VALUES (2, 'trigger', 'tag', 'val2')
INSERT INTO _temp VALUES (3, 'truck', 'plate', '12345')
DECLARE @param AS varchar(15)
SET @param = 'page'
DECLARE @c AS nvarchar(100)
DECLARE @sql AS nvarchar(max)
SELECT @c =
ISNULL(
@c + ',[' + c + ']',
'[' + c + ']'
)
FROM (SELECT DISTINCT [namespace] + '.' + [key] AS c FROM _temp WHERE id IN (SELECT id FROM _temp WHERE ISNULL([namespace], '') + ISNULL([key], '') + ISNULL([value], '') LIKE '%' + @param + '%') ) AS col
SET @sql = N'
SELECT *
FROM
(
SELECT id,
namespace + ''.'' + [key] AS [column],
value
FROM _temp
WHERE id IN (SELECT id FROM _temp WHERE ISNULL([namespace], '''') + ISNULL([key], '''') + ISNULL([value], '''') LIKE ''%' + @param + '%'')
) AS src
PIVOT
(
MAX(value)
FOR [column]
IN (' + @c + ')
) AS piv'
EXECUTE (@sql)
DROP TABLE _temp
答案 2 :(得分:1)
SQL中数据透视表的本质是它需要两个查询。
原因是SQL要求您在访问任何数据之前定义select-list列。没有SQL查询可以根据扫描表时发现的不同数据值动态扩展选择列的列。
换句话说:您无法在单个SQL查询中进行转化。
即使在具有内置PIVOT
操作的SQL实现(如Microsoft SQL Server)中,您仍然必须在运行之前在查询语法中命名列。这意味着您需要知道在此之前要在列中表示的不同值。
您可以使用这样的简单查询发现不同的值:
SELECT DISTINCT namespace, `key` FROM NoOneEverNamesTheirTableInSqlQuestions;
然后使用它的结果来构建动态SQL查询。
$sql = "SELECT DISTINCT namespace, `key` FROM NoOneEverNamesTheirTableInSqlQuestions";
$stmt = $pdo->query($sql);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
$select_list = [];
foreach ($results as $row) {
$select_list[] = sprintf(
"MAX(CASE WHEN namespace=%s AND `key`=%s THEN value END) AS `%s.%s`",
$pdo->quote($row['namespace']), $pdo->quote($row['key']),
$row['namespace'], $row['key']);
}
$dynamic_sql = sprintf(
"SELECT id, %s FROM NoOneEverNamesTheirTableInSqlQuestions GROUP BY id",
implode(', ', $select_list));
您也可以使用SQL同时执行这两项操作,方法是以新SQL查询的形式返回第一个查询的结果,以执行实际的数据透视。
SELECT CONCAT('SELECT id, ', GROUP_CONCAT(DISTINCT CONCAT(
'MAX(CASE WHEN namespace=', QUOTE(namespace), ' AND `key`=', QUOTE(`key`),
' THEN value END) AS `', CONCAT_WS('.', namespace, `key`), '`')),
' FROM NoOneEverNamesTheirTableInSqlQuestions GROUP BY id;') AS _sql
FROM NoOneEverNamesTheirTableInSqlQuestions;
上面查询的输出是数据透视查询的真实动态SQL,填充了选择列表的每个相应列:
SELECT id,
MAX(CASE WHEN namespace='page' AND `key`='content.title' THEN value END) AS `page.content.title`,
MAX(CASE WHEN namespace='page' AND `key`='item.id' THEN value END) AS `page.item.id`,
MAX(CASE WHEN namespace='trigger' AND `key`='tag' THEN value END) AS `trigger.tag`,
MAX(CASE WHEN namespace='oddball' AND `key`='num' THEN value END) AS `oddball.num`,
MAX(CASE WHEN namespace='truck' AND `key`='plate' THEN value END) AS `truck.plate`
FROM NoOneEverNamesTheirTableInSqlQuestions GROUP BY id;
然后运行动态查询,您将获得要求的结果:
+----+--------------------+--------------+-------------+-------------+-------------+
| id | page.content.title | page.item.id | trigger.tag | oddball.num | truck.plate |
+----+--------------------+--------------+-------------+-------------+-------------+
| 1 | page2 | test1 | val1 | NULL | NULL |
| 2 | page3 | t1 | val2 | in | NULL |
| 3 | NULL | NULL | NULL | NULL | 12345 |
+----+--------------------+--------------+-------------+-------------+-------------+
这里的两个步骤都是作为MySQL存储过程实现的:
DELIMITER ;;
CREATE PROCEDURE PivotProc()
BEGIN
SELECT CONCAT('SELECT id, ', GROUP_CONCAT(DISTINCT CONCAT(
'MAX(CASE WHEN namespace=', QUOTE(namespace), ' AND `key`=', QUOTE(`key`),
' THEN value END) AS `', CONCAT_WS('.', namespace, `key`), '`')),
' FROM NoOneEverNamesTheirTableInSqlQuestions GROUP BY id;') AS _sql
FROM NoOneEverNamesTheirTableInSqlQuestions
INTO @sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;
END;;
那么,如果您不想运行两个查询,那还有什么选择呢?
另一种方法是运行单个简单查询来获取数据库中存在的数据,每个id
有多行。然后通过对应用程序进行后处理来修复它。
$sql = "SELECT id, namespace, `key`, value FROM NoOneEverNamesTheirTableInSqlQuestions";
$stmt = $pdo->query($sql);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
$pivot_results = [];
foreach ($results as $row) {
if (!array_key_exists($row['id'], $pivot_results)) {
$pivot_results[$row['id']] = ['id' = $row['id']];
}
$field = sprintf("%s.%s", $row['namespace'], $row['key']);
$pivot_results[$row['id']][$field] = $row['value'];
}
完成后期处理后,您将拥有一个哈希数组,每个id
一行,每个都指向一个索引为namespace.key
名称的字段的哈希数组你描述过。