MySQL查询/搜索实验

时间:2017-04-14 03:41:31

标签: mysql stored-procedures pivot-table

益智拼图,谜语功能(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行获取动态列。

3 个答案:

答案 0 :(得分:4)

要使其作为数据透视表工作,您必须运行两个查询:

  1. 获取要使用的列

    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         |
    +--------------------+-----------+---------------+
    
  2. 结合唯一ID并将每个值作为子查询,为了防止子查询多个结果必须包含聚合函数,我使用了max()

  3. 我创建了一个存储过程:

    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的索引和数据量

    它基于An approach to mysql dynamic cross reference文章。

答案 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查询,每个不同的值有一列。
  • 运行动态查询以获取数据透视表结果的第二个查询。

原因是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名称的字段的哈希数组你描述过。