最近有很多FizzBu​​zz问题,这里还有一个问题!当然,使用计算使用SQL进行FizzBu​​zz非常简单。一般,特别是MySQL PostgreSQL。为了使其更有趣,我们将使用5个关键字而不是2个:Fizz,Buzz,Woof,Ping,Plop,它们将由用户分配。

将有多个脚本,所以请耐心等待。请注意,我在整个过程中都使用Pascal表示法。

创建原始架构
DROP SCHEMA IF EXISTS PhrancisFizzBuzz CASCADE;
CREATE SCHEMA PhrancisFizzBuzz;
-- this table will hold the list of numbers to check for FizzBuzz
CREATE TABLE PhrancisFizzBuzz.Number(
    n INT NOT NULL
); 
-- the following tables will hold multiplier values based on table PhrancisFizzBuzz.Number
CREATE TABLE PhrancisFizzBuzz.Fizz(
    Number INT,
    Word VARCHAR(4) NOT NULL DEFAULT 'Fizz'
);
CREATE TABLE PhrancisFizzBuzz.Buzz(
    Number INT,
    Word VARCHAR(4) NOT NULL DEFAULT 'Buzz'
);
CREATE TABLE PhrancisFizzBuzz.Woof(
    Number INT,
    Word VARCHAR(4) NOT NULL DEFAULT 'Woof'
);
CREATE TABLE PhrancisFizzBuzz.Ping(
    Number INT,
    Word VARCHAR(4) NOT NULL DEFAULT 'Ping'
);
CREATE TABLE PhrancisFizzBuzz.Plop(
    Number INT,
    Word VARCHAR(4) NOT NULL DEFAULT 'Plop'
);



填充FizzBu​​zz表

现在我将开始创建过程做工作。

CREATE OR REPLACE FUNCTION PhrancisFizzBuzz.fncPopulateFizzBuzz(
    prmMaxNumber INT, -- for example 100
    prmFizz INT,
    prmBuzz INT,
    prmWoof INT,
    prmPing INT,
    prmPlop INT
) RETURNS VOID
AS -- begin literal string with escape $$
$$
BEGIN
SET SEARCH_PATH = PhrancisFizzBuzz;
-- initialize and populate list of initial numbers
TRUNCATE TABLE Number;
INSERT INTO Number (n)
    SELECT * FROM GENERATE_SERIES(1,  prmMaxNumber);
-- populate the FizzBuzz tables with multiples
-- based on user parameter choice
TRUNCATE TABLE Fizz; 
INSERT INTO Fizz
    SELECT n
    FROM Number
    WHERE COALESCE(prmFizz,0) <> 0 AND n % prmFizz = 0;
INSERT INTO Buzz
    SELECT n
    FROM Number
    WHERE COALESCE(prmBuzz,0) <> 0 AND n % prmBuzz = 0;
INSERT INTO Woof
    SELECT n
    FROM Number
    WHERE COALESCE(prmWoof,0) <> 0 AND n % prmWoof = 0;
INSERT INTO Ping
    SELECT n
    FROM Number
    WHERE COALESCE(prmPing,0) <> 0 AND n % prmPing = 0;
INSERT INTO Plop
    SELECT n
    FROM Number
    WHERE COALESCE(prmPlop,0) <> 0 AND n % prmPlop = 0;
END;
$$ -- end literal string
LANGUAGE plpgsql;


用于测试:

SET SEARCH_PATH = PhrancisFizzBuzz;
SELECT fncPopulateFizzBuzz(100,3,5,0,0,0);
SELECT * FROM Number
    LEFT JOIN Fizz ON Number.n = Fizz.Number
    LEFT JOIN Buzz ON Number.n = Buzz.Number 
ORDER BY Number.n;


结果样本:



Total query runtime: 17 ms.
100 rows retrieved.


到带有JOIN的表中的FizzBu​​zz
将结果集显示到每个单词一行的表中:

CREATE OR REPLACE FUNCTION PhrancisFizzBuzz.fcnTableFizzBuzz(
    prmMaxNumber INT, -- for example 100
    prmFizz INT,
    prmBuzz INT,
    prmWoof INT,
    prmPing INT,
    prmPlop INT
    ) RETURNS VOID
    AS 
$$
BEGIN
SET SEARCH_PATH = PhrancisFizzBuzz;
-- call populate function with input parameters
PERFORM fncPopulateFizzBuzz(
    prmMaxNumber,
    prmFizz,
    prmBuzz,
    prmWoof,
    prmPing,
    prmPlop
);
DROP TABLE IF EXISTS tmpFizzBuzzResults;
CREATE TEMP TABLE tmpFizzBuzzResults AS
SELECT    num.n AS Number,
    Fizz.Word AS Fizz,
    Buzz.Word AS Buzz,
    Woof.Word AS Woof,
    Ping.Word AS Ping,
    Plop.Word AS Plop
FROM Number AS num
    LEFT JOIN Fizz ON Fizz.Number = num.n
    LEFT JOIN Buzz ON Buzz.Number = num.n
    LEFT JOIN Woof ON Woof.Number = num.n
    LEFT JOIN Ping ON Ping.Number = num.n
    LEFT JOIN Plop ON Plop.Number = num.n
;
END;
$$
LANGUAGE plpgsql;


用于测试:

SET SEARCH_PATH = PhrancisFizzBuzz;
SELECT fcnTableFizzBuzz(100,3,5,7,11,13);
SELECT * FROM tmpFizzBuzzResults ORDER BY Number ASC;


输出样本:



Total query runtime: 78 ms.
100 rows retrieved.


我确实尝试在RETURN TABLE中使用该功能,但无法正常工作。欢迎对此提出任何建议。


最后但并非最不重要的是,这会生成经典的FizzBu​​zz:

CREATE OR REPLACE FUNCTION PhrancisFizzBuzz.fcnClassicFizzBuzz(
      prmMaxNumber INT,
      prmFizz INT,
      prmBuzz INT,
      prmWoof INT,
      prmPing INT,
      prmPlop INT
)
RETURNS VOID
AS
$BODY$
BEGIN

-- populate tables based on user input
-- using previously created function
SET SEARCH_PATH = PhrancisFizzBuzz;
    PERFORM fncPopulateFizzBuzz(
        prmMaxNumber,
        prmFizz,
        prmBuzz,
        prmWoof,
        prmPing,
        prmPlop
    );
DROP TABLE IF EXISTS tmpFizzBuzzResults;
CREATE TEMP TABLE tmpFizzBuzzResults AS
    WITH cteFizzBuzzConcat AS(
          SELECT    Number.n AS Number,
            CONCAT(Fizz.Word, Buzz.Word, Woof.Word, Ping.Word, Plop.Word) AS Word
          FROM Number
        LEFT JOIN Fizz ON Number.n = Fizz.Number
        LEFT JOIN Buzz ON Number.n = Buzz.Number
        LEFT JOIN Woof ON Number.n = Woof.Number
        LEFT JOIN Ping ON Number.n = Ping.Number
        LEFT JOIN Plop ON Number.n = Plop.Number
    )
    SELECT    Number
    ,    (CASE 
        WHEN Word <> '' THEN Word
        -- WHEN Word IS NOT NULL THEN Word
        ELSE CAST(Number AS VARCHAR)
        END) AS Value
    FROM cteFizzBuzzConcat;
END;
$BODY$ LANGUAGE plpgsql;

-- For testing:

    SET SEARCH_PATH = PhrancisFizzBuzz;
    SELECT fcnClassicFizzBuzz(100,3,5,7,11,13);
    SELECT Value FROM tmpFizzBuzzResults ORDER BY Number ASC;


执行:
Total query runtime: 53 ms.
100 rows retrieved.


输出示例:



Update

我运行了超过100,000个数字的最后一个函数,并在3.5秒

评论

大约要花时间... +1就是纯粹的努力

最后,有人加紧做两个以上的单词。接下来,我们需要有一个素数规则,当N作为一个因子或数字出现时适用的规则,当一个规则不只一次应用于一个数字时重复单词,等等。至少您的设计可能具有足够的可扩展性!

注意:concat()是PostgreSQL 9.1中引入的。

@DavidK不久之后,我们将创建基础拟声词(与主流数学家使用的那可怕的基础10相对)。

#1 楼

表的扩散

数据库的架构不应设计成需要创建更多的表来容纳更多的数据。同样的原则在这里适用。您应该只有一个Fizz表,而不是单独的表BuzzWoofPingPlopNoiseDefs。经典的无参数FizzBu​​zz应该更简单...

WITH NoiseDefs(multiple, noise) AS (
    VALUES (3, 'Fizz')
         , (5, 'Buzz')
), CombinedNoises AS (
    SELECT n
         , string_agg(noise, '' ORDER BY multiple) AS noise
        FROM generate_series(1, 100) AS n
            LEFT OUTER JOIN NoiseDefs
                ON n % multiple = 0
        GROUP BY n
)
SELECT n
     , coalesce(noise, CAST(n AS TEXT)) AS noiseOrNum
    FROM CombinedNoises
    ORDER BY n;


请注意VALUESstring_agg()的使用。 />
在PostgreSQL中,使用TEXT而不是VARCHAR(n)没什么意义-如果字符串长度超过限制,这只会引起问题。您还可能允许噪声包含四个以上的字符。他们每个人都在幕后操纵临时表,并且调用者需要了解该行为才能使用这些功能。换句话说,VARCHAR(n)不返回任何内容是很不正确的,您必须使用TEXT分别检索结果。 (当您说我确实尝试使用该功能在SELECT fcnClassicFizzBuzz(100, 3, 5, 7, 11, 13);中工作,但无法使它正常工作时,我想您可能已经意识到了这个问题。) SELECT Value FROM tmpFizzBuzzResults ORDER BY Number ASC;RETURN TABLEfcnClassicFizzBuzz()表,但是它们自身不会清除。因此,它会在那些表中留下垃圾,这是副作用。重复调用Fizz将导致越来越不正确的输出。用户唯一的办法就是发出Buzz-您甚至都没有提供撤消Woof的便利功能。噪声也应由用户指定。

CREATE OR REPLACE FUNCTION FlexibleFizzBuzz(
    max INTEGER,
    n1 INTEGER, noise1 TEXT,
    n2 INTEGER, noise2 TEXT,
    n3 INTEGER, noise3 TEXT,
    n4 INTEGER, noise4 TEXT,
    n5 INTEGER, noise5 TEXT
) RETURNS TABLE(n INTEGER, noiseOrNum TEXT) AS $BODY$
    BEGIN
        RETURN QUERY
        WITH NoiseDefs(multiple, noise) AS (
            VALUES (n1, noise1)
                 , (n2, noise2)
                 , (n3, noise3)
                 , (n4, noise4)
                 , (n5, noise5)
        ), CombinedNoises AS (
            SELECT num
                 , string_agg(noise, '' ORDER BY multiple) AS noise
                FROM generate_series(1, max) AS num
                    LEFT OUTER JOIN NoiseDefs
                        ON num % multiple = 0
                GROUP BY num
        )
        SELECT num
             , coalesce(noise, CAST(num AS TEXT))
            FROM CombinedNoises
            ORDER BY num;
    END;
$BODY$ LANGUAGE plpgsql;


如果可能,请完全避免使用临时表。无需担心的清理工作和更少的I / O浪费。要缓存结果,请调用方执行:

CREATE TEMPORARY TABLE FizzBuzzResults10000 AS
    SELECT *
        FROM FlexibleFizzBuzz(10000, 3, 'Fizz', 5, 'Buzz', 7, 'Woof', 11, 'Ping', 13, 'Plop');


此实现的运行时间约为原始代码的一半。重载

提供便利功能会很好,因此可以省略某些参数。例如:

CREATE OR REPLACE FUNCTION FlexibleFizzBuzz(
    max INTEGER,
    n1 INTEGER, noise1 TEXT,
    n2 INTEGER, noise2 TEXT
) RETURNS TABLE(n INTEGER, noiseOrNum TEXT) AS $BODY$
    BEGIN
    RETURN QUERY SELECT * FROM FlexibleFizzBuzz(
        max,
        n1, noise1,
        n2, noise2,
        NULL, NULL,
        NULL, NULL,
        NULL, NULL);
    END;
$BODY$ LANGUAGE plpgsql;


我不相信PL / pgSQL函数可以接受可变数量的参数。

从上面的实现可以推断,我不喜欢您使用的前缀,例如PingPlopfcnClassicFizzBuzz()TRUNCATE Fizz; TRUNCATE Buzz; TRUNCATE Woof; TRUNCATE Ping; TRUNCATE Plop;。相反,我会将fncPopulateFizzBuzz()用于类似表的对象,例如返回行的表,视图和函数。对于标量,请使用fnc…。我认为这是一个优势,例如您可以用视图替换临时表。 (PostgreSQL标识符实际上不区分大小写,除非您对它们进行prm…,但是在源代码中具有该约定就足够了。)

评论


\ $ \ begingroup \ $
遇到一些功能错误,但应该能够轻松修复。再次感谢
\ $ \ endgroup \ $
– ran
2014年7月25日下午6:36

\ $ \ begingroup \ $
此解决方案适用于“我的机器认证的产品”(PostgreSQL 9.0)。不过,请随时纠正发现的任何问题。
\ $ \ endgroup \ $
– 200_success
2014年7月25日下午6:48

#2 楼

我认为您的脚本可以使用更多的垂直空白;也许是最麻烦的事情,但我发现了: >
BEGIN
SET SEARCH_PATH = PhrancisFizzBuzz;
-- call populate function with input parameters
PERFORM fncPopulateFizzBuzz(
    prmMaxNumber,
    prmFizz,
    prmBuzz,
    prmWoof,
    prmPing,
    prmPlop
);
DROP TABLE IF EXISTS tmpFizzBuzzResults;
CREATE TEMP TABLE tmpFizzBuzzResults AS
...


我也希望缩进BEGIN...END块。


我不明白,这是一个Tab吗?


BEGIN

    SET SEARCH_PATH = PhrancisFizzBuzz;
    -- call populate function with input parameters
    PERFORM fncPopulateFizzBuzz(
        prmMaxNumber,
        prmFizz,
        prmBuzz,
        prmWoof,
        prmPing,
        prmPlop
    );

    DROP TABLE IF EXISTS tmpFizzBuzzResults;

    CREATE TEMP TABLE tmpFizzBuzzResults AS
    ...



在这里您需要一个空格: >在cteFizzBuzzConcat中;您在tmpFizzBuzzResults中有一​​个类似的查询,但别名不同:来源相同。


您存储了很多VARCHAR(4)非值,我希望将它们视为NULL值。对于100000个数字,您将存储400000 x4个无用的空字符;您应该在将它们串联时对它们进行空提示。


还有一件事-我在这里引用臭名昭著的Monkey:


唯一明智的评论是:不要使用SQL进行FizzBu​​zz


;)

评论


\ $ \ begingroup \ $
哦,来吧,运行100K记录需要3.5秒,这是支持SQL执行FizzBu​​zz的一个论点;)感谢@ Mat'sMug的评论
\ $ \ endgroup \ $
– ran
2014年7月23日下午4:28

\ $ \ begingroup \ $
我同意,但是我个人不喜欢BEGIN之后的新行,这使我想起人们在if语句中的Curly之后添加新行。
\ $ \ endgroup \ $
–马拉奇♦
2014年7月23日下午13:20