Answers:
我的绝招:
@
是t-sql中的有效变量。iif
了VB样式的case语句。这几乎总是比等效的短if
else
。\
是将货币类型中的数字初始化为0的一种有用方法。您可以通过添加将值转换为浮点数e
。例如4e
或\k
将k设置为0.00货币的值。rCTE
似乎是创建少于100个条目的数字表的最佳方法。比使用spt_values更短。如果需要100个以上,请交叉联接并添加它们。+=
和其他复合运算符在2008年添加。使用它们可以节省一些字符。;
。Select*from A,B where condition
短于select*from A join b on condition
goto
循环。STR()
是将int转换为字符串的最短函数。如果您要进行多个转换,或者可能需要合并许多不同的数据类型,请考虑使用该concat
函数。例如'hello'+str(@)
,短于concat('hello',@)
,但hello+str(@)+str(@a)
长于concat('hello',@,@a)
例如,这两个在语义上是等效的。
while @<100begin/*code*/set @+=1 end
s:/*code*/set @+=1if @<100goto s
您可以Values
用来创建表或子查询。仅当您需要一些恒定的行时,这才真正有好处。
使用SQL进行代码压缩
SQL是罗word的,得分很高,并且和我们所爱的一样,SELECT FROM WHERE
每次使用都花费23个字节。您可以压缩这些和其他重复的单词或整个代码段。这样做会将重复代码的边际成本降低到1个字节!*
工作原理:
问题:
前期费用接近100个字节,替换表中的每一行又花费了6个字节。除非您要处理大量无法精简的代码,或者挑战是基于压缩的,否则这种逻辑将不会非常有效。
这是一个例子
挑战在于获得2,3和5的最后10个倍数,直至n。可以说(这是343个字节的golfed)是我能想到的最佳解决方案:
WITH x AS(
SELECT 99 n
UNION ALL
SELECT n-1
FROM x
WHERE n>1
)
SELECT w.n,t.n,f.n
FROM
(SELECT n, ROW_NUMBER()OVER(ORDER BY n DESC)r
FROM x WHERE n%2=0
)w
,
(SELECT n, ROW_NUMBER()OVER(ORDER BY n DESC)r
FROM x WHERE n%3=0
)t
, (SELECT n, ROW_NUMBER()OVER(ORDER BY n DESC)r
FROM x WHERE n%5=0
)f
WHERE w.r=t.r AND w.r=f.r AND w.r<11
ORDER BY 1
代码压缩后的示例
这执行与上面相同的代码,大约302个字节。
DECLARE @a CHAR(999)='
WITH x AS(!99n UNION ALL !n-1 @x#n>1)
!w.n,t.n,f.n@$2=0)w,$3=0)t,$5=0)f
#w.r=t.r AND w.r=f.r AND w.r<11^1'
SELECT @a=REPLACE(@a,LEFT(i,1),SUBSTRING(i,2,99))
FROM(VALUES
('$(!n,ROW_NUMBER()OVER(^n DESC)r@x#n%'),
('! SELECT '),
('@ FROM '),
('# WHERE '),
('^ ORDER BY ')
)x(i)
EXEC(@a)
SELECT @=REPLACE(@,i,j)FROM(VALUES(...)x(i,j)
来节省字节,而不是使用LEFT()
和来代替单个列SUBSTRING()
。如果您有8个或更多,那么最好避免使用多余的引号和逗号。
SET @=REPLACE(REPLACE(REPLACE(...
这是一个有趣的。这会将列中的值转换为单个元组。
编辑:谢谢你的评论。似乎没有XML标签的最简单汇总方法是:
SELECT (SELECT column1+''
FROM table
ORDER BY column1
FOR XML PATH(''))
注意:如果XML是有效输出,则可以省略外部select和parens。同样column1+''
,,仅适用于字符串。对于数字类型,最好做column1+0
<column_name>value1</column_name><column_name>value2</column_name>...
。为了从某列获取CSV,您可以DECLARE @ VARCHAR(MAX)='';SELECT @+=column_name+',' FROM table_name;SELECT @
(感谢@MichaelB的第一个技巧),该列将返回value1,value2,...
。但是,实际上比您的XML技巧长9个字符:(
Ltrim
不需要,因为select(为xml path('')选择...)返回nvarchar(max)
。另外,要解决列问题,只需使用非变异表达式即可。对于数字,您可以执行此操作v+0
,对于字符串,可以添加空字符串等。尽管我并不真的认为这是一个打高尔夫球的技巧,但遗憾的是,这是如何在sql server中编写查询的现实。
Michael B提到对数字表使用递归CTE,但未显示示例。这是我们在另一个线程中得出的MS-SQL版本:
--ungolfed
WITH t AS (
SELECT 1 n
UNION ALL
SELECT n + 1
FROM t
WHERE n < 99)
SELECT n FROM t
--golfed
WITH t AS(SELECT 1n UNION ALL SELECT n+1FROM t WHERE n<99)SELECT n FROM t
请注意,您可以更改起始值(1 n
),间隔(n + 1
)和结束值(n < 99
)。
但是,如果需要100行以上,则需要添加option (maxrecursion 0)
:
WITH t AS(SELECT 0n UNION ALL SELECT n+1FROM t WHERE n<9999)
SELECT n FROM t option(maxrecursion 0)
或加入rCTE本身:
WITH t AS(SELECT 0n UNION ALL SELECT n+1FROM t WHERE n<99)
SELECT 100*z.n+t.n FROM t,t z
尽管不能保证最后一个数字不按数字顺序返回 ORDER BY 1
所以我知道SQL 2016添加了一个COMPRESS
函数(和一个DECOMPRESS
函数),(最终)带来了GZIP字符串或二进制代码的功能。
问题是目前尚不清楚如何利用此优势进行打高尔夫球。COMPRESS
可以采用字符串,但返回a VARBINARY
,该字节的字节数较短(当存储在SQL VARBINARY
字段中时),而字符的长度较长(原始十六进制)。
我以前玩过这个游戏,但是最终我可以根据SO上的这个旧答案来整理一个工作版本。该帖子未使用新的GZIP函数,但确实将转换为VARBINARY
以Base-64编码的字符串。我们只需要在正确的位置插入新功能,然后进行一些调整即可。
这是可用于将很长的字符串转换为Base-64编码的压缩字符串的代码:
DECLARE @s VARCHAR(MAX)='Your really long string goes right here'
SELECT CONVERT(VARCHAR(MAX),(SELECT CONVERT(VARBINARY(MAX),COMPRESS(@s))
FOR XML PATH(''),BINARY BASE64))
取得输出,并在代码中使用它代替原始的长字符串,以及:
--To use your compressed string and return the original:
DECLARE @e VARCHAR(MAX)='H4sIAAAAAAAEAIvMLy1SKEpNzMmpVMjJz0tXKC4pygRS6fmpxQpFmekZJQoZqUWpAGGwW5YnAAAA'
SELECT CAST(DECOMPRESS(CAST(@e as XML).value('.','varbinary(max)'))AS varchar(max))
因此,而不是原始代码(1471个字节)
SELECT'Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we can not dedicate — we can not consecrate — we can not hallow — this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us — that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion — that we here highly resolve that these dead shall not have died in vain — that this nation, under God, shall have a new birth of freedom — and that government of the people, by the people, for the people, shall not perish from the earth.'
您将拥有以下内容(1034个字节):
SELECT CAST(DECOMPRESS(CAST('H4sIAAAAAAAEAGVUW47bMAy8Cg/g5hD9aLFA0a8C/aYt2hZWEVNJjpGT5LodinE2i/0JIouPmeFQP3QrVCctQpwDVblKpptwqcSLkt3O3FbBeSy6LWujWUtbSTO1NVaaNLeYJbeBmLLslLlFzYNdTBKvEihm+hVHKe029CZBQpy44aYpighdil60RsvDmRtxSnQGEAasqUiPlX8bpxP91p126TeSF168PtNiYTTFa0y0cxmoSQWwhfZVDL8XPsBpAZLb40hVX9B+QgganCkp6kgOW5ET/fXmZ2mmwdF45NaSfJujpEA6ezfg6PErX8FDz2KEj9pIvUBJ63/E92xoBO3xP3Oi8iBxSTyJKY9ArQJSSiAltFhp8IuFEuBXL/TClc7RhmaXJ3prhJFxarq4KHNsvb6RtikcOkHhuuoGLkH7nE/0fcOIu9SJy4LAKrnKYKGmUdb2Qe3++hXSVpnKl+8rpoxh3t1HC9yVw4n+wA9jMVYwwGC4D3xBGOIY89rKtiwJwzINhkPfow0cAagzY8aj4sZMfFG1n90IKnEIZoEgrfDUvOmuBXT3COulaMM0kCieEdgNUOQsZ9gYEB4K8e0BYNwgbHNm2KBik4LCHgmhbxSigz1mYKPcane/Uxyo9D0bDN8oL0vS5/zYlC3DF7Gu+Ay872gQp9U7mDCzb2jPWN0ZaGJKwOJZx3QD9SvD6uEA4l2feHrvnv9lS93ojeu7ScHAAVFGme3tQOr94eGiZwuHSVeFduKDM70avwscZAtd++er+sqrp068VTf5C63D4HBdRfWtvwxcsYq2Ns8a96dvnTxMD7JYH0093+dQxcFU897DhLgO0V+RK0gdlbopj+cCzoRGPxX+89Se5u/dGPtzOIO5SAD5e3drL7LAfiXDyM13HE+d6CWZY26fjr7ZH+cPgFhJzPspK+FpbuvpP9RXxXK3BQAA'as XML).value('.','varbinary(max)'))AS varchar(max))
看到这个答案,为我节省了将近200个字节。
我还没有完成数学运算,但是显然由于开销,这仅对极长的字符串有效。可能还有其他地方无法使用。我已经发现您必须SELECT
这么做,不能PRINT
做到,否则您将得到:
Xml data type methods are not allowed in expressions in this context.
编辑:简化版本的解压缩代码,由@digscoop提供:
CAST
使用CONCAT
以下命令将外部更改为隐式转换,从而节省10个字节:
SELECT CONCAT('',DECOMPRESS(CAST('encoded_string_here'as XML).value('.','varbinary(max)')))
您还可以声明类型XML
而不是的变量VARCHAR(MAX)
,并保存在内部CAST
:
DECLARE @ XML='encoded_string_here'
SELECT CONCAT('',DECOMPRESS(@.value('.','varbinary(max)')))
它本身稍长一些,但是如果出于其他原因在变量中需要它,则可能会有所帮助。
有关创建和使用表格进行挑战的一些想法:
1. SQL输入可以通过预先存在的表获取
SQL可以从命名表中获取输入
使用输入值创建和填充此表不会计入您的字节总数,您可以假设它已经存在。
这意味着您的计算可以通过输入表中的简单SELECT输出:
SELECT 2*SQRT(a)FROM t
2.如果可能,根本不要创建表
代替(69个字节):
CREATE TABLE t(b INT)
INSERT t VALUES(7),(14),(21),(99)
SELECT b FROM t
只需(43个字节):
SELECT b FROM(VALUES(7),(14),(21),(99))t(b)
3.如果可能,使用SELECT INTO创建表
而不是(39个字节):
CREATE TABLE t(p INT)
INSERT t VALUES(2)
这样做(17个字节):
SELECT 2 p INTO t
4:考虑将多个列混在一起
这是两个返回相同输出的变体:
SELECT a,b FROM
(VALUES('W','Bob'),('X','Sam'),('Y','Darla'),('Z','Elizabeth'))t(a,b)
SELECT LEFT(a,1),SUBSTRING(a,2,99)FROM
(VALUES('WBob'),('XSam'),('YDarla'),('ZElizabeth'))t(a)
经过一些测试,顶部版本(多列)似乎较短,只有7行或更少的行,底部版本(由于LEFT和SUBSTRING)较短,只有8行或更多。您的里程可能会有所不同,具体取决于您的确切数据。
5:对很长的文本序列使用REPLACE和EXEC
按照fortreamdrei 的一个很好的回答,如果您有15个或更多的值,请REPLACE
在符号上使用来消除'),('
元素之间重复的分隔符:
114个字符:
SELECT a FROM(VALUES('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H')
,('I'),('J'),('K'),('L'),('M'),('N'),('O'))t(a)
112个字符:
DECLARE @ CHAR(999)=REPLACE('SELECT a FROM(VALUES(''
A-B-C-D-E-F-G-H-I-J-K-L-M-N-O''))t(a)','-','''),(''')EXEC(@)
如果由于其他原因已经在使用动态SQL(或有多个替换),那么值得使用的阈值会低很多。
6:使用带有命名列的SELECT而不是一堆变量
SELECT a+b+a+b+d+b+b+a+a+d+a+c+a+c+d+c+c+a+a
FROM(SELECT'Hare 'a,'Krishna 'b,'Rama 'c,'
'd)t
退货
Hare Krishna Hare Krishna
Krishna Krishna Hare Hare
Hare Rama Hare Rama
Rama Rama Hare Hare
(对于MS SQL我改变了\t
到直列回报,并改变CONCAT()
到+
保存字节)。
利用MS SQL 2016和SQL 2017中的新功能
如果没有本地副本,则可以 使用StackExchange Data Explorer(SQL 2016)或dbfiddle.uk(SQL 2016或SQL“ vNext”)在线播放。
STRING_SPLIT(SQL 2016及更高版本)
SELECT *
FROM STRING_SPLIT('one,two,three,four,five',',')
如果需要别名表或引用列名:
SELECT t.value
FROM STRING_SPLIT('one,two,three,four,five',',')t
TRIM(SQL 2017或更高版本)
比短RTRIM()
,当然比短LTRIM(RTRIM())
。
还具有从开头或结尾删除其他字符或字符集的选项:
SELECT TRIM('sq,0' FROM 'SQL Server 2000')
退货 L Server 2
TRANSLATE(SQL 2017或更高版本)
TRANSLATE
允许您一步替换多个字符,而不是一堆嵌套的REPLACE
语句。但是不要过分庆祝,它只会将单个单个字符替换为不同的单个字符。
SELECT TRANSLATE('2*[3+4]/{7-2}', '[]{}', '()()');
第二个字符串中的每个字符都被第三个字符串中的相应字符替换。
看起来我们可以用类似的方法消除一堆字符 REPLACE(TRANSLATE('source string','ABCD','XXXX'),'X','')
还有一些更有趣的内容,例如CONCAT_WS
和STRING_AGG
,也可能值得一看。
神圣的牛,我发现了PARSENAME
(SQL 2012或更高版本)的奇迹。
构建该函数的目的是隔离对象名称的各个部分,例如servername.dbname.dbo.tablename
,但它适用于任何以点分隔的值。请记住,它是从右边开始计数,而不是从左边开始计数:
SELECT PARSENAME('a.b.c.d',1), -- d
PARSENAME('a.b.c.d',2), -- c
PARSENAME('a.b.c.d',3), -- b
PARSENAME('a.b.c.d',4) -- a
如果少于4个点分隔的值,它将返回NULL
其余的值(但仍从右到左计数):
SELECT PARSENAME('a.b',1), -- b
PARSENAME('a.b',2), -- a
PARSENAME('a.b',3), -- NULL
PARSENAME('a.b',4) -- NULL
但是,这就是魔术的来历:将其与STRING_SPLIT
(2016年或更高版本)结合使用,以创建内存中多列表!
破旧:
SELECT a,b,c FROM
(VALUES('Bob','W','Smith'),
('Sam','X','Johnson'),
('Darla','Y','Anderson'),
('Elizabeth','Z','Turner'))t(a,b,c)
新热点:
SELECT PARSENAME(value,3)a,PARSENAME(value,2)b,PARSENAME(value,1)c
FROM string_split('Bob.W.Smith-Sam.X.Johnson-Darla.Y.Anderson-Elizabeth.Z.Turner','-')
显然,您的实际节省取决于表的大小和内容以及您使用表的方式。
请注意,如果您的字段是恒定宽度的,则最好使用LEFT
和RIGHT
分隔它们,而不是PARSENAME
(而不是因为函数名较短,而且因为可以完全消除分隔符),所以最好将它们分开。
我看到并想保留一些其他无关的技巧:
GO #
重复块的特定次数。在保罗出色的回答中看到了这个聪明的把戏。
PRINT'**********'
GO 10
当然,这将重置块中的所有计数器变量,因此您必须权衡一个WHILE
循环还是一个x: ... GOTO x
循环。
SELECT TOP ... FROM systypes
根据与上述Paul相同的问题,Anuj Tripathi使用以下技巧:
SELECT TOP 10 REPLICATE('*',10) FROM systypes
或者,如pinkfloydx33在评论中所建议:
SELECT TOP 10'**********'FROM systypes
请注意,这不依赖于任何实际内容的systypes
,只是在系统视图存在(这它在所有的MS SQL数据库),并包含至少10行(它看起来包含34,为最新版本的SQL )。我找不到名称较短的系统视图(不需要sys.
前缀),所以这可能是理想的。
有关将数字列添加到STRING_SPLIT结果中的一些有趣想法,请参见dba.stackexchange上的此问题。
给定一个像这样的字符串'one,two,three,four,five'
,我们想要得到这样的东西:
value n
------ ---
one 1
two 2
three 3
four 4
five 5
Per Joe Obbish的回答,使用ROW_NUMBER()
和排序NULL
或常数:
SELECT value, ROW_NUMBER() OVER(ORDER BY (SELECT 1))n
FROM STRING_SPLIT('one,two,three,four,five',',')
根据Paul White的回答,请使用SEQUENCE
:
CREATE SEQUENCE s START WITH 1
SELECT value, NEXT VALUE FOR s
FROM STRING_SPLIT('one,two,three,four,five', ',')
序列是有趣的持久对象。您可以定义数据类型,最小值和最大值,时间间隔以及是否回绕到开头:
CREATE SEQUENCE s TINYINT; --Starts at 0
CREATE SEQUENCE s MINVALUE 1; --Shorter than START WITH
SELECT NEXT VALUE FOR s --Retrieves the next value from the sequence
ALTER SEQUENCE s RESTART; --Restarts a sequence to its original start value
每何塞六必居的回答,您可以使用的IDENTITY()
功能(这是不一样的IDENTITY
性质为与插入结合:
SELECT value v,IDENTITY(INT) AS n
INTO t
FROM STRING_SPLIT('one,two,three,four,five',',')
SELECT * FROM t
请注意,其中的最后两个参数IDENTITY(INT,1,1)
是可选的,如果排除,则默认为1。
ORDER BY
(例如,参见我对Toasty,Burnt和Brulee的回答)。
_和#是有效的别名。我将它们与CROSS APPLY结合使用,使其看起来像它返回的列是FROM子句的一部分,例如
SELECT TOP 10 number, n2
FROM master.dbo.spt_values v
CROSS APPLY (SELECT number*2 n2) _
当CROSS APPLY的唯一目的是计算一个表达式时,我喜欢这样做。
因此,使用APPLY计算子表达式是一种使代码DRY-er(或更短)的精巧方法。根据我在执行计划中所看到的,这种方法没有增加成本。编译器会发现您只是在计算某些内容,并将其与其他任何表达式一样对待。