在T-SQL中打高尔夫球的技巧


16

您在T-SQL中打高尔夫球有哪些一般技巧?我正在寻找可以应用于编码高尔夫问题的想法,这些想法至少在某种程度上特定于T-SQL。请为每个答案发布一个提示。

感谢Marcog的初衷。:)


提示-打高尔夫球时请使用其他语言。SQL答案通常很少或根本没有投票。
t-clausen.dk

Answers:


16

我的绝招:

  • @ 是t-sql中的有效变量。
  • T-sql 2012添加iif了VB样式的case语句。这几乎总是比等效的短if else
  • \是将货币类型中的数字初始化为0的一种有用方法。您可以通过添加将值转换为浮点数e。例如4e\k将k设置为0.00货币的值。
  • rCTE似乎是创建少于100个条目的数字表的最佳方法。比使用spt_values更短。如果需要100个以上,请交叉联接并添加它们。
  • += 和其他复合运算符在2008年添加。使用它们可以节省一些字符。
  • 对于别名而言,文字通常是一个足够好的分隔符。您很少需要空格或;
  • 如果需要,请使用ANSI SQL连接。Select*from A,B where condition短于select*from A join b on condition
  • 如果可以确定while循环将执行第一次迭代,则最好将其重写为do-style样式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用来创建表或子查询。仅当您需要一些恒定的行时,这才真正有好处。


对我来说,$较\更为明显,可以将货币类型的数字初始化为0。YMMV
user1443098 '18

5

使用SQL进行代码压缩

SQL是罗word的,得分很高,并且和我们所爱的一样,SELECT FROM WHERE每次使用都花费23个字节。您可以压缩这些和其他重复的单词或整个代码段。这样做会将重复代码的边际成本降低到1个字节!*

工作原理:

  • 声明变量并为其分配压缩的SQL代码
  • 一个表会修改变量。每行放宽变量。
  • 执行修改后的变量。

问题:

前期费用接近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)

伟大的策略是,多替换样式在更传统的场景中也可能有用。
BradC

1
经过一些测试,我确定如果替换列表中有7个或更少的项目,那么您可以这样做SELECT @=REPLACE(@,i,j)FROM(VALUES(...)x(i,j)来节省字节,而不是使用LEFT()和来代替单个列SUBSTRING()。如果您有8个或更多,那么最好避免使用多余的引号和逗号。
BradC

实际上,对于4个或更少的替换,您将使用老式的字节保存SET @=REPLACE(REPLACE(REPLACE(...
BradC

4

这是一个有趣的。这会将列中的值转换为单个元组。

编辑:谢谢你的评论。似乎没有XML标签的最简单汇总方法是:

SELECT (SELECT column1+''
FROM table
ORDER BY column1
FOR XML PATH(''))

注意:如果XML是有效输出,则可以省略外部select和parens。同样column1+'',,仅适用于字符串。对于数字类型,最好做column1+0


1
实际上它将返回<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个字符:(
Jacob

1
请注意,您可以将其缩短。Ltrim不需要,因为select(为xml path('')选择...)返回nvarchar(max)。另外,要解决列问题,只需使用非变异表达式即可。对于数字,您可以执行此操作v+0,对于字符串,可以添加空字符串等。尽管我并不真的认为这是一个打高尔夫球的技巧,但遗憾的是,这是如何在sql server中编写查询的现实。
Michael B

3

在T-SQL中可以使用一些按位运算

我没有具体的例子,但是我相信在使用T-SQL打高尔夫球时这是一个众所周知的事实。


1
这是非常有效的。无需编写类似的条件x=0 or y=0,您可以将其写为逻辑等效项x|y=0,这样可以节省很多字节!
Michael B

3

打印而不是选择

就这么简单!因此,这是一个T-SQL / Python多语言:

print'Hello, World!'

在线尝试


3

科学计数法是一种表示非常大和非常小的数字(例如select 1000000000= select 1E9select 0.000001= )的较短方法select 1E-6


2

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


2

对很长的字符串使用GZIP压缩!

所以我知道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)')))

它本身稍长一些,但是如果出于其他原因在变量中需要它,则可能会有所帮助。


很好,我不知道SQL,但是看起来仍然很酷
MilkyWay90

1

有关创建和使用表格进行挑战的一些想法:

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而不是一堆变量

受到jmlt出色答案的启发,通过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()+保存字节)。


1

标记代码以突出显示T-SQL语法

不仅仅是:

CREATE TABLE t(b INT)
INSERT t VALUES(7),(14),(21),(99)
SELECT b FROM t

包括这样的语言标签:

<!-- language: lang-sql -->

    CREATE TABLE t(b INT)
    INSERT t VALUES(7),(14),(21),(99)
    SELECT b FROM t

结果将是:

CREATE TABLE t(b INT)
INSERT t VALUES(7),(14),(21),(99)
SELECT b FROM t

1

利用MS SQL 2016和SQL 2017中的新功能

如果没有本地副本,则可以 使用StackExchange Data Explorer(SQL 2016)或dbfiddle.uk(SQL 2016或SQL“ vNext”)在线播放。

STRING_SPLITSQL 2016及更高版本

SELECT *
FROM STRING_SPLIT('one,two,three,four,five',',')

如果需要别名表或引用列名:

SELECT t.value
FROM STRING_SPLIT('one,two,three,four,five',',')t

TRIMSQL 2017或更高版本

比短RTRIM(),当然比短LTRIM(RTRIM())

还具有从开头或结尾删除其他字符或字符集的选项

SELECT TRIM('sq,0' FROM 'SQL Server 2000')

退货 L Server 2

TRANSLATESQL 2017或更高版本

TRANSLATE允许您一步替换多个字符,而不是一堆嵌套的REPLACE语句。但是不要过分庆祝,它只会将单个单个字符替换为不同的单个字符。

SELECT TRANSLATE('2*[3+4]/{7-2}', '[]{}', '()()');

第二个字符串中的每个字符都被第三个字符串中的相应字符替换。

看起来我们可以用类似的方法消除一堆字符 REPLACE(TRANSLATE('source string','ABCD','XXXX'),'X','')


还有一些更有趣的内容,例如CONCAT_WSSTRING_AGG,也可能值得一看。


1

神圣的牛,我发现了PARSENAMESQL 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','-')

显然,您的实际节省取决于表的大小和内容以及您使用表的方式。

请注意,如果您的字段是恒定宽度的,则最好使用LEFTRIGHT分隔它们,而不是PARSENAME(而不是因为函数名较短,而且因为可以完全消除分隔符),所以最好将它们分开。


我不确定PARSENAME何时发布,但是有一些文章描述了2003年
t-clausen.dk,

1

我看到并想保留一些其他无关的技巧:

  1. 使用GO #重复块的特定次数

保罗出色的回答中看到了这个聪明的把戏。

PRINT'**********'
GO 10

当然,这将重置块中的所有计数器变量,因此您必须权衡一个WHILE循环还是一个x: ... GOTO x循环。

  1. 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.前缀),所以这可能是理想的。


1

有关将数字列添加到STRING_SPLIT结果中的一些有趣想法,请参见dba.stackexchange上的此问题

给定一个像这样的字符串'one,two,three,four,five',我们想要得到这样的东西:

value   n
------ ---
one     1
two     2
three   3
four    4
five    5
  1. Per Joe Obbish的回答,使用ROW_NUMBER()和排序NULL或常数:

    SELECT value, ROW_NUMBER() OVER(ORDER BY (SELECT 1))n
    FROM STRING_SPLIT('one,two,three,four,five',',')
    
  2. 根据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
  1. 每何塞六必居的回答,您可以使用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。


问题是STRING_SPLIT不保证任何退货单。您可能会认为它将始终按照原始字符串中标记的顺序返回行集。确实,它甚至可以做到这一点!但是,文档中不能保证。如果您不在乎订单,那很好。但是,如果您这样做(例如,以CSV格式解析一行),就会出现问题。
user1443098 '18

1
@ user1443098我最终出于商业目的推荐代码的情况下同意您的意见,就像我们在dba.SE上看到的那样。但是对于PPCG方面的挑战,我的标准有所不同。如果在测试中我的代码按我想要的顺序返回行,那么我将在可能的地方保存字节。类似于我会遗忘的方式ORDER BY(例如,参见我对Toasty,Burnt和Brulee的回答)。
BradC

1

刚刚发现可以将数字用于单个字符REPLACE来消除引号

--44 bytes
PRINT REPLACE('Baby Shark******','*',' doo')

--42 bytes
PRINT REPLACE('Baby Shark000000',0,' doo')

这是因为REPLACE会隐式转换为字符串。

两者产生相同的输出:

Baby Shark doo doo doo doo doo doo

0

_和#是有效的别名。我将它们与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(或更短)的精巧方法。根据我在执行计划中所看到的,这种方法没有增加成本。编译器会发现您只是在计算某些内容,并将其与其他任何表达式一样对待。


我发现交叉申请适用于很长时间,使用交叉申请很难找到有用的情况,而又找不到另一种较短的方法
t-clausen.dk 19-4-4

好-缩短上面给出的示例!
user1443098'4

SELECT TOP 10 number,number * 2 n2 FROM master.dbo.spt_values v
t-clausen.dk

我的意思是,保持加入。顺便说一句,一旦构建xml查询,CROSS APPLY就可以成为唯一的方法,因为子查询中可能没有列可以进行联接。
user1443098

子查询比交叉查询短:SELECT top 10 * FROM(SELECT number n,number * 2n2 FROM master..spt_values)x
t-clausen.dk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.