带变量的复杂sql查询

Posted

技术标签:

【中文标题】带变量的复杂sql查询【英文标题】:Complicated sql query with variables 【发布时间】:2017-12-12 00:16:29 【问题描述】:

这是我为每个城市/子类别组合获取第一行 $count 的查询

$contacts = $dbh->prepare("
    SELECT *
    FROM (SELECT c.*,
                 (@rn := IF(@cc = CONCAT_WS(':', city_id, subcategory_id), @rn + 1,
                            IF(@cc := CONCAT_WS(':', city_id, subcategory_id), 1, 1)
                           )
                 ) as rn
          FROM (SELECT reg.title as region_title, cnt.title, cnt.city_id, cnt.id, cnt.catalog_id, cnt.address, cnt.phone, cnt.email, cnt.website,  cnt.subcategory_title, cnt.subcategory_id, cnt.manufacturer 
                FROM contacts as cnt
                LEFT JOIN regions as reg 
                ON cnt.city_id = reg.id 
                WHERE city_id IN (".implode(',', $regions).") AND 
                      subcategory_id IN (".implode(',', $categories).") 
                ORDER BY subcategory_title, city_id, title
               ) c CROSS JOIN
               (SELECT @cc := '', @rn := 0) params
         ) c
    WHERE rn <= $count");

我正在使用 $contacts-&gt;fetchAll(PDO::FETCH_GROUP); 按 reg.title 对行进行分组

[ 
 ['City 1'] = > [ 
   [ contact 1 ],
   [ contact 2 ],
   ...
 ],
 ['City 2'] = > [ 
   [ contact 3 ],
   [ contact 4 ],
   ...
 ]
 ...
]

现在我需要升级该查询,但它对我来说太复杂了 :( 所选行必须具有唯一的 contacts.catalog_id 值。 怎么做?

UPD 这是一个演示数据库 - http://sqlfiddle.com/#!9/ac71d7/2

【问题讨论】:

见meta.***.com/questions/333952/… 当我们说“唯一的contacts.catalog_id”值时,我们是否要从结果中排除任何和所有行,其中有多个行具有给定的catalog_id?还是我们只是想确保catalog_id 在结果中不重复?或者对于给定的(city_id,subcategory_id),catalog_id 没有重复?理解和传达规范是战斗的 80%。示例数据以及预期输出将大大有助于阐明规范。 我添加了一个示例数据。我们需要全局唯一的 catalog_id。 【参考方案1】:

我们需要全局唯一的 catalog_id

要识别catalog_idcontacts 中的唯一 值,我们可以使用如下查询:

   SELECT r.catalog_id
     FROM contacts r
    GROUP BY r.catalog_id
   HAVING COUNT(1) = 1

也就是说,对于contacts 中的给定行,如果catalog_id 的值与contacts任何其他行上的catalog_id 匹配,则catalog_id 将被排除在外结果。

如果我们想将原始查询限制为仅返回 catalog_id 的值,我们可以将此查询作为内联视图包含在内,并将其连接到具有匹配 catalog_id 的联系人中的行。


                    FROM contacts cnt
  -- ------------
                    JOIN ( SELECT r.catalog_id
                             FROM contacts r
                            GROUP BY r.catalog_id
                           HAVING COUNT(1) = 1
                         ) s
                      ON s.catalog_id = cnt.catalog_id
  -- ------------
                    LEFT
                    JOIN regions reg
                      ON reg.id = cnt.city_id

编辑

如果对规范的解释不同,而不是意味着catalog_id 在联系人中必须是唯一,我们的意思是catalog_id 不应在结果中重复...我们可以使用相同的方法,但是对于每个catalog_id,从contacts 获取id 的单个值。我们可以这样写一个查询:

   SELECT MAX(r.id) AS max_id
        , r.catalog_id
     FROM contacts r
    GROUP BY r.catalog_id

我们可以使用 MIN() 聚合代替 MAX()。目标是为catalog_id 的每个离散值返回一个contacts.id

我们可以将其作为内联视图合并到查询中,将内联视图中的max_idcontacts 表中的id 匹配。

类似这样的:

                    FROM contacts cnt
  -- ------------
                    JOIN ( SELECT MAX(r.id) AS max_id
                             FROM contacts r
                            WHERE ... 
                            GROUP BY r.catalog_id
                         ) s
                      ON s.max_id = cnt.id
  -- ------------
                    LEFT
                    JOIN regions reg
                      ON reg.id = cnt.city_id

我们可能希望将外部查询的WHERE 子句中的条件移动到该内联视图中。如果我们不这样做,则内联视图返回的 max_id 可能会引用 contacts 中不满足 WHERE 子句中的条件的行 (id)。

cnt 上的WHERE 条件重新定位到内联视图中...

SELECT d.*
  FROM ( SELECT c.*
              , ( @rn := IF( @cc = CONCAT_WS(':', city_id, subcategory_id)
                           , @rn + 1
                           , IF( @cc := CONCAT_WS(':', city_id, subcategory_id),1,1)
                         )
                ) AS rn
           FROM ( SELECT reg.title AS region_title
                       , cnt.title
                       , cnt.city_id
                       , cnt.id
                       , cnt.catalog_id
                       , cnt.address
                       , cnt.phone
                       , cnt.email
                       , cnt.website
                       , cnt.category_title
                       , cnt.subcategory_title
                       , cnt.subcategory_id
                       , cnt.manufacturer
                    FROM contacts cnt
  -- --------------
                    JOIN ( SELECT MAX(r.id) AS max_id
                             FROM contacts r
                            WHERE r.city_id        IN ( ... ) 
                              AND r.subcategory_id IN ( ... )
                              AND r.email          IS NOT NULL
                              AND r.manufacturer   = 1
                            GROUP BY r.catalog_id
                         ) s
                      ON s.max_id = cnt.id
  -- --------------
                    LEFT
                    JOIN regions reg
                      ON reg.id = cnt.city_id
                   ORDER
                      BY cnt.category_title
                       , cnt.subcategory_title
                       , cnt.city_id
                       , cnt.title
                ) c
          CROSS
           JOIN ( SELECT @cc := '', @rn := 0) i
       ) d
 WHERE d.rn <= 10

【讨论】:

感谢您的回答!但是这个查询有点错误。看这里sqlfiddle.com/#!9/e42628/5这里应该是5行。应包含 catalog_id 为 '4926348963357290 和 '70000001018295015 的行 catalog_id 4926348963357290 似乎不满足“全球唯一catalog_id”规范。正如我在对该问题的评论中所说,这实际上取决于规范是什么,规范是如何解释的。我的评论询问规范是否意味着我们应该只返回联系人中唯一的 catalog_id 值,或者我们是否只是确保在结果中不重复 catalog_id 值。同样,这也是提供预期结果的示例有助于阐明规范的地方(以“全球唯一catalog_id”没有的方式。) 抱歉语言障碍,感谢您的帮助!

以上是关于带变量的复杂sql查询的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL 查询中使用 PHP 变量变量

sql插入int类型

sql server 递归查询

带参数的sql查询语句

sql server里的查询结果要保存到一个变量里,怎么弄?

SQL查询语句之查询数据