如何在 SQL 中计算逗号分隔列表中的字符串项

Posted

技术标签:

【中文标题】如何在 SQL 中计算逗号分隔列表中的字符串项【英文标题】:How to count string items in comma separated list in SQL 【发布时间】:2021-10-08 20:32:10 【问题描述】:

在我的表格中,我有一列标签,它是一个字符串列表。

["conda"]
["intel"]
["pandas", "conda"]
["api", "partner"]
["dask", "distributed computing", "conda"]

我希望能够获得每个不同字符串的计数。

-------------------
 tag   | count
-------------------
conda  | 3
pandas | 1
...
-------------------

到目前为止,我一直在用困难的方式来做这件事......

SELECT tags
FROM "public"."content"
WHERE concat(tags) LIKE '%INSERT_TAG_NAME_HERE%'

提前致谢!

【问题讨论】:

数据库是什么? 如果您使用的是关系引擎,那么最好将数据存储在 3NF 中。这种设计不符合 1NF 的最低级别。我强烈建议您重新设计数据库。 【参考方案1】:

您使用的是什么版本的 SQL?如果使用 SQL Server (T-SQL),一种方法是使用表值函数将连接的文本字段拆分为多个片段。这样的事情可以让你开始:

编辑: 糟糕!较新的 SQL Server 具有string_split() 功能,您可以类似地使用cross apply,从而无需tvfSplitToTableStrings()。见the docs。

CREATE FUNCTION dbo.tvfSplitToTableStrings
(
        @items NVARCHAR(max),
        @delimiter CHAR(1)
)
RETURNS @itemTable table
(
        [item]  NVARCHAR(100)
        , sequence INT
)
as
BEGIN
        DECLARE
                @tempItemList NVARCHAR(max),
                @i int,
                @item NVARCHAR(100)
        SET @tempItemList = @items
        SET @tempItemList = LTRIM(RTRIM(@items))
        -- get index where our delimiter was found
        SET @i = CHARINDEX(@delimiter, @tempItemList)
        -- loop while all the characters in the list have not been traversed yet
        declare @count int = 0
        WHILE (LEN(@tempItemList) > 0)
        BEGIN
            IF @i = 0
                -- if there are no delimiters, then this is the only item in our list
                SET @item = @tempItemList
            ELSE
                -- get the first word (from the left) less the delimiter character
                SET @item = LEFT(@tempItemList, @i - 1)
            set @count = @count + 1
            INSERT INTO @itemTable (item, sequence) VALUES (LTRIM(RTRIM(@item)), @count)
            IF @i = 0
                SET @tempItemList = ''
            ELSE
                -- remove the word we just added to the table
                SET @tempItemList = RIGHT(@tempItemList, LEN(@tempItemList) - @i)
            -- lather, rinse, repeat
            SET @i = CHARINDEX(@delimiter, @tempItemList)
        END 
        RETURN;
END

然后您可以将其与cross apply 一起使用,如下所示:

;with a as (
    select 1 as id, 'dog,cat,fish' as animal
    union
    select 2 as id, 'snake,cat,racoon' as animal
)
select a.id, b.item, b.sequence
    from a
    cross apply app.tvfSplitToTableStrings(a.animal, ',') b

由于您的字符串似乎包含额外的标记["...", "..."],您可以使用TRANSLATE() 清除它。或者您可以修改tvfSplitToTableStrings 函数本身以更智能地解析和去除不需要的标记。假设您的字符串值不包含任何标记字符(包括嵌入的空格),以下应该有效:

;with a as (
    select 1 as id, '["dog", "cat", "fish"]' as animal
    union
    select 2 as id, '["snake", "cat", "racoon"]' as animal
)
, b as (
    select a.id
    , TRIM(TRANSLATE(b.item, '[]" ', '    ')) as item
    , b.sequence
    from a
    cross apply app.tvfSplitToTableStrings(a.animal, ',') b
)
select item, count(*) as [count] 
    from b
    group by item
    order by item;

如果您使用的是 mysql,您可能希望采用https://***.com/a/17942691 之类的方法。

另请注意,您可能需要注意性能。 cross apply 适用于较小的数据集,但可能无法很好地扩展。

【讨论】:

我实际上并不清楚正在使用哪个版本的 SQL,因为我正在尝试在 Metabase 中执行这种 voo doo 魔术。 metabase.com/docs/latest/users-guide/writing-sql.html【参考方案2】:

我现在对此有了更好的理解。关于 Metabase 的说明,Metabase 使用的服务在我的情况下使用的是 Postgres。

我认为我的一些查询的问题在于一些 null 和空值。

SELECT tag, count(*) AS cnt FROM (
    SELECT 
        content.id, content.tags, tag FROM content,
        jsonb_array_elements(
            case jsonb_typeof(content.tags::jsonb) 
                when 'array' then content.tags 
                else '[]' end
    ) j(tag)
) inn
GROUP BY tag
ORDER BY cnt DESC

希望这对将来的其他人有所帮助。

【讨论】:

以上是关于如何在 SQL 中计算逗号分隔列表中的字符串项的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Oracle 中的正则表达式从逗号分隔列表中删除重复项,但我不想要重复值? [复制]

如何在选择语句的“NOT IN”子句中使用逗号分隔的字符串列表作为 pl/sql 存储的函数参数

如何通过 Oracle 中的 regexp_replace 从逗号分隔列表中删除重复项?

如何通过 Oracle regexp_replace 中的正则表达式从逗号分隔列表中删除重复项? [复制]

如何使用 LINQ 查找包含 2 个逗号分隔的字符串的匹配项

PL/SQL 逗号分隔列表;删除重复并放入数组