情况如下:我有一个页面,我可以一次编辑多个记录(让我们说发票)。它们不会在彼此下方显示,但它们被编辑为一条记录。在页面加载时,只有在整个记录集中它们相同时,才会在给定字段中显示值。
现在,除此之外,我还想编辑子记录(1-n关系;发票行)。我设法显示了所有已编辑的发票中相同的发票行记录,如下所示:
Invoice 1 Invoice 2 Lines edited
A B D
D C G
F D =>
G E
G
假设A,B,......是发票行
通过发票行排序,因此每行发票都有一个位置字段。这就是我想要做的事情:允许重新排序发票行。编辑一张发票时,这是一项简单的任务。但是,当一次编辑多个发票时,会出现一些问题。请考虑以下事项:
Invoice 1 Invoice 2 Lines edited
F(1) B(1) F(1)
A(2) C(2) B(2)
B(3) D(3) => C(3)
C(4) F(4)
E(5)
当在行B之后移动行F时,在发票1中,F不仅在B之后移动,而且在A之后移动,并且用户不知道它;在发票2中,F已经在B之后,但是用户不知道它。那么B应该放在F之前(在第3位)还是留在原处?目前还不清楚。
我想要做的是在行为不清楚(或意外)时防止重新排序,并在其他情况下允许重新排序。这是我的解决方案:对于每行发票,查找是否可以向上移动一步(position--)以及是否可以向下移动一步(位置++)。怎么样 ?对于版本页面中的每个邻居线对(在示例中:F-B; B-C),如果源发票行中的对应对是相邻的并且顺序相同,则可以切换该对。因此,在该示例中,这意味着可以切换B和C但不能切换F和B.因此结果将是:
Lines edited move down move up
F no no
B yes no
C no yes
这或多或少是我目前的情况:
CREATE TABLE [InvoiceLine](
[id] [int] IDENTITY(1,1) NOT NULL,
[invoiceId] [int] NOT NULL,
[position] [int] NOT NULL,
[text] [nvarchar](255) NULL,
[price] [decimal](18,2) NULL,
CONSTRAINT [PK_InvoiceLine] PRIMARY KEY CLUSTERED ([id] ASC) ON [PRIMARY]
)
CREATE TABLE [Invoice](
[id] [int] IDENTITY(1,1) NOT NULL,
[customerId] [int] NULL,
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([id] ASC) ON [PRIMARY]
)
INSERT INTO [Invoice]([customerId]) VALUES
(1000),
(2000);
INSERT INTO [InvoiceLine]([invoiceId],[position],[text],[price]) VALUES
(1000,1,'F',10.5),
(1000,2,'A',3.0),
(1000,3,'B',4.0),
(1000,4,'C',1.0),
(1000,5,'E',1.0),
(2000,1,'B',4.15),
(2000,2,'C',1.35),
(2000,3,'D',1.20),
(2000,4,'F',12.10);
DECLARE @ids TABLE(n int);
INSERT INTO @ids (n) VALUES (1000),(2000);
DECLARE @n int;
SET @n = (SELECT COUNT(*) FROM @ids);
SELECT
CAST(text AS nvarchar) AS id,
CASE WHEN rank_position = _rankMain THEN position ELSE NULL END AS position,
CASE WHEN rank_text = _rankMain THEN text ELSE NULL END AS text,
CASE WHEN rank_price = _rankMain THEN price ELSE NULL END AS price,
0 AS isRecordDeleted
FROM (
SELECT
T4.position, RANK() OVER (PARTITION BY T4.text, T4.position ORDER BY id) AS rank_position,
T4.text, RANK() OVER (PARTITION BY T4.text ORDER BY id) AS rank_text,
T4.price, RANK() OVER (PARTITION BY T4.text, T4.price ORDER BY id) AS rank_price,
RANK() OVER (PARTITION BY T4.text ORDER BY id) AS _rankMain,
_cnt
FROM
(
-- Filter lines
SELECT
text,
(
SELECT COUNT(id) FROM InvoiceLine WHERE invoiceId IN (SELECT * FROM @ids) AND text = T2.text
) AS _cnt FROM
(
-- add rank on text field (an invoice line is considered equal to another one if both text fields are equal)
SELECT RANK() OVER (PARTITION BY text ORDER BY invoiceId) AS rnk, text FROM
(
-- distinct lines
SELECT DISTINCT invoiceId, text FROM InvoiceLine WHERE invoiceId IN (SELECT n FROM @ids)
) T1
) T2
WHERE rnk = (SELECT COUNT(n) FROM @ids)
) T3 INNER JOIN InvoiceLine T4 ON T4.text = T3.text
) T5 WHERE _cnt = _rankMain ORDER BY position
我的问题是:
我应该如何转换该查询(实际存储过程)以获得“向上移动”和“向下移动”字段?
我的第一个想法是从结果中取出所有邻居对,并在每个源发票行中找到它们的距离(距离是位置差的绝对值)并取最大距离。如果最大值等于1且差值都具有相同的符号,则可以切换该对的位置。但后来我不知道如何将其转换为SQL ...
[编辑]还有一件事:最高编辑记录应始终向上移动=否,而最低编辑记录应始终向下移动=否。
[编辑2012-02-23 ]在查询结尾处添加了ORDER BY
[编辑2012-02-23 ] 这是第二组数据及其预期输出:
INSERT INTO [Invoice]([customerId]) VALUES
(1000),
(2000),
(3000);
INSERT INTO [InvoiceLine]([invoiceId],[position],[text],[price]) VALUES
(1000,1,'F',10.5),
(1000,2,'A',3.0),
(1000,3,'B',4.0),
(1000,4,'C',1.0),
(1000,5,'E',1.0),
(1000,6,'G',4.2),
(1000,7,'H',9.0),
(1000,8,'K',9.0),
(2000,1,'B',4.15),
(2000,2,'C',1.35),
(2000,3,'D',1.20),
(2000,4,'F',12.10),
(2000,6,'G',4.2),
(2000,7,'H',2.7),
(2000,8,'I',1.3),
(3000,1,'B',41.15),
(3000,2,'C',15.35),
(3000,3,'D',12.20),
(3000,4,'F',11.10),
(3000,5,'I',4.0),
(3000,6,'G',4.2),
(3000,7,'H',6.7),
(3000,8,'E',7.3);
DECLARE @ids TABLE(n int);
INSERT INTO @ids (n) VALUES (1000),(2000),(3000);
应该收益:
id position text price isRecordDeleted moveUp moveDown
B NULL B NULL 0 no yes
C NULL C NULL 0 yes no
F NULL F NULL 0 no no
G 6 G 4.20 0 no yes
H 7 H NULL 0 yes no
[编辑2012-02-24 ] 重复的行应该只出现一次,只有当它们是直接邻居时才有moveUp和moveDown
以下是第三组数据及其预期输出:
INSERT INTO [Invoice]([customerId]) VALUES
(1000),
(2000),
(3000);
INSERT INTO [InvoiceLine]([invoiceId],[position],[text],[price]) VALUES
(1000,1,'F',10.5),
(1000,2,'A',3.0),
(1000,3,'B',4.0),
(1000,4,'C',1.0),
(1000,5,'E',1.0),
(1000,6,'J',3.2),
(1000,7,'G',4.2),
(1000,8,'H',9.0),
(1000,9,'K',9.0),
(1000,10,'F',3.0),
(2000,1,'B',4.15),
(2000,2,'C',1.35),
(2000,3,'D',1.20),
(2000,4,'C',1.35),
(2000,5,'F',12.10),
(2000,6,'J',6.2),
(2000,7,'G',4.2),
(2000,8,'H',2.7),
(2000,9,'H',3.1),
(2000,10,'I',1.3),
(3000,1,'B',41.15),
(3000,2,'C',15.35),
(3000,3,'D',12.20),
(3000,4,'F',11.10),
(3000,5,'I',4.0),
(3000,6,'J',2.3),
(3000,7,'G',4.2),
(3000,8,'H',6.7),
(3000,9,'E',7.3);
DECLARE @ids TABLE(n int);
INSERT INTO @ids (n) VALUES (1000),(2000),(3000);
应该收益:
id position text price isRecordDeleted moveUp moveDown
B NULL B NULL 0 no no
C NULL C NULL 0 no no
F NULL F NULL 0 no no
J 6 J NULL 0 no yes
G 7 G 4.20 0 yes no
H NULL H NULL 0 no no
或更好:
id position text price isRecordDeleted moveUp moveDown
B NULL B NULL 0 no no
C NULL C NULL 0 no no
F NULL F NULL 0 no no
J 6 J NULL 0 no yes
G 7 G 4.20 0 yes yes
H NULL H NULL 0 yes no
[编辑2012-03-02 ]
第二个结果更好,因为虽然H在发票2000中出现两次,但两条线都是邻居,因此切换H和G的位置是安全的:两条H线都将被切换。
但是这最后的结果可能会导致查询过于复杂。
答案 0 :(得分:1)
这应该有用。
DECLARE @ids TABLE(n int);
INSERT INTO @ids (n) VALUES (1000);
INSERT INTO @ids (n) VALUES (2000);
INSERT INTO @ids (n) VALUES (3000);
SELECT il.*
INTO #InvoiceLineQuery
FROM [InvoiceLine] il
INNER JOIN @ids i ON il.invoiceId = i.n
SELECT letter.text id,
CASE WHEN pos.maxPos = pos.minPos THEN pos.maxPos ELSE NULL END AS position,
letter.text,
CASE WHEN price.maxPrice = price.minPrice THEN price.maxPrice ELSE NULL END AS price,
0 AS isRecordDeleted,
CASE WHEN moveUP.text IS NOT NULL THEN 'yes' ELSE 'no' END AS moveUP,
CASE WHEN moveDOWN.text IS NOT NULL THEN 'yes' ELSE 'no' END AS moveDown
FROM (SELECT il.text
FROM #InvoiceLineQuery il
GROUP BY il.text
HAVING count(il.position) = (SELECT count(1) FROM @ids)) letter
INNER JOIN (SELECT il.text, MAX(il.position) maxPos, MIN(il.position) minPos
FROM #InvoiceLineQuery il
GROUP BY il.text) pos on letter.text = pos.text
INNER JOIN (SELECT il.text, MAX(il.price) maxPrice, MIN(il.price) minPrice
FROM #InvoiceLineQuery il
GROUP BY il.text) price on letter.text = price.text
LEFT JOIN (SELECT upNeighbour.text
FROM (SELECT mainLine.text text, upLine.text upText
FROM #InvoiceLineQuery mainLine
INNER JOIN #InvoiceLineQuery upLine ON mainLine.position = upLine.Position + 1
AND mainLine.invoiceId = upLine.invoiceId) upNeighbour
GROUP BY upNeighbour.text,upNeighbour.upText
HAVING count(upNeighbour.upText) = (SELECT count(1) FROM @ids)) moveUP ON letter.text = moveUP.text
LEFT JOIN (SELECT downNeighbour.text
FROM (SELECT mainLine.text text, downLine.text downText
FROM #InvoiceLineQuery mainLine
INNER JOIN #InvoiceLineQuery downLine ON mainLine.position + 1 = downLine.Position
AND mainLine.invoiceId = downLine.invoiceId) downNeighbour
GROUP BY downNeighbour.text,downNeighbour.downText
HAVING count(downNeighbour.downText) = (SELECT count(1) FROM @ids)) moveDOWN ON letter.text = moveDOWN.text
DROP TABLE #InvoiceLineQuery
答案 1 :(得分:1)
好吧,我在这个上睡不着觉。我认为这有效:
;with CommonLines as (
select [text],
MIN(position) as minPos,MAX(position) as maxPos,
MIN(price) as minPrice,MAX(price) as maxPrice
from @InvoiceLine
where invoiceId in (select n from @ids)
group by [text]
having COUNT(*) = (select COUNT(*) from @ids)
), InvoiceOrders as (
select invoiceId,[text],ROW_NUMBER() OVER (PARTITION BY InvoiceId order by Position) as rn
from @InvoiceLine where [text] in (select [text] from CommonLines) and
invoiceId in (select n from @ids)
), AlwaysAdjacent as ( --Ignoring lines that aren't going to appear at all
select a1.[text] as FirstText,a2.[text] as SecondText,ROW_NUMBER() OVER (ORDER BY a1.[text]) as Ord
from
InvoiceOrders a1
inner join
InvoiceOrders a2
on
a1.invoiceId = a2.invoiceId and
a1.rn = a2.rn - 1
group by a1.text,a2.text
having COUNT(*) = (select COUNT(*) from @ids)
)
select
cl.[text],
CASE WHEN aa1.Ord IS NOT NULL THEN 1 ELSE 0 END as MoveDown,
CASE WHEN aa2.Ord IS NOT NULL THEN 1 ELSE 0 END as MoveUp,
CASE WHEN minPrice=maxPrice THEN minPrice END as price,
CASE WHEN minPos=maxPos THEN minPos END as Position
from
CommonLines cl
left join
AlwaysAdjacent aa1
on
cl.[text] = aa1.FirstText
left join
AlwaysAdjacent aa2
on
cl.[text] = aa2.SecondText
order by
COALESCE(aa2.Ord,aa1.Ord),
CASE
WHEN aa1.Ord IS NOT NULL THEN 0
WHEN aa2.Ord IS NOT NULL THEN 1
ELSE 2 END
我也将发票数据放入表变量中,并忽略发票,因为它似乎不相关 - 下面的设置数据。
希望这很容易阅读,但无论如何都有一些解释。 CommonLines
针对InvoiceLine
进行关系划分,以找到我们将要使用的text
。 InvoiceOrders
然后,对于每张发票,计算这些text
出现的顺序 - 此排序会忽略任何其他行,并且仅基于position
。
AlwaysAdjacent
然后执行另一个关系分工,使用InvoiceOrders
两次来确定出现在所有发票中相同(相对)位置的那些text
值 - 也就是说,两者之间的差异即使绝对值不同,它们的排序也是常数。
最后,我们输出文本,并使用AlwaysAdjacent
确定是否应允许向下或向上移动。最后的ORDER BY
有点棘手,但我认为是正确的 - 它试图容纳比2更长的运行,其中中间行可以向上和向下移动(即交换F
和{{1} } E
中的位置和F应该出现在C和G之间,整个集合可以重新排序)
结果:
1001
设置数据:
text MoveDown MoveUp price Position
---- ----------- ----------- --------------------------------------- -----------
F 0 0 NULL NULL
B 1 0 NULL NULL
C 0 1 NULL NULL
G 1 0 4.20 6
H 0 1 NULL 7
答案 2 :(得分:0)
根据Damien_The_Unbeliever的答案,这里是第三组数据的结果1的解决方案(结果2的解决方案会更好):
DECLARE @InvoiceLine TABLE (
[id] [int] IDENTITY(1,1) NOT NULL,
[invoiceId] [int] NOT NULL,
[position] [int] NOT NULL,
[text] [nvarchar](255) NULL,
[price] [decimal](18,2) NULL
)
DECLARE @Invoice TABLE (
[id] [int] IDENTITY(1,1) NOT NULL,
[customerId] [int] NULL
)
INSERT INTO @Invoice([customerId]) VALUES
(1000),
(2000),
(3000);
INSERT INTO @InvoiceLine([invoiceId],[position],[text],[price]) VALUES
(1000,1,'F',10.5),
(1000,2,'A',3.0),
(1000,3,'B',4.0),
(1000,4,'C',1.0),
(1000,5,'E',1.0),
(2000,1,'B',4.15),
(2000,2,'C',1.35),
(2000,3,'D',1.20),
(2000,4,'F',12.10);
DECLARE @ids TABLE(n int);
INSERT INTO @ids (n) VALUES (1000),(2000);
;WITH Lines AS (
SELECT
CAST(text AS nvarchar) AS id,
position AS pos,
CASE WHEN rank_position = _rankMain THEN position ELSE NULL END AS position,
CASE WHEN rank_text = _rankMain THEN text ELSE NULL END AS text,
CASE WHEN rank_price = _rankMain THEN price ELSE NULL END AS price,
0 AS isRecordDeleted
FROM (
SELECT
T4.position, RANK() OVER (PARTITION BY T4.text, T4.position ORDER BY id) AS rank_position,
T4.text, RANK() OVER (PARTITION BY T4.text ORDER BY id) AS rank_text,
T4.price, RANK() OVER (PARTITION BY T4.text, T4.price ORDER BY id) AS rank_price,
RANK() OVER (PARTITION BY T4.text ORDER BY id) AS _rankMain,
_cnt
FROM
(
-- Filter lines
SELECT
text,
(
SELECT COUNT(id) FROM @InvoiceLine WHERE invoiceId IN (SELECT * FROM @ids) AND text = T2.text
) AS _cnt FROM
(
-- add rank on text field (an invoice line is considered equal to another one if both text fields are equal)
SELECT RANK() OVER (PARTITION BY text ORDER BY invoiceId) AS rnk, text FROM
(
-- distinct lines
SELECT DISTINCT invoiceId, text FROM @InvoiceLine WHERE invoiceId IN (SELECT n FROM @ids)
) T1
) T2
WHERE rnk = (SELECT COUNT(n) FROM @ids)
) T3 INNER JOIN @InvoiceLine T4 ON T4.text = T3.text
) T5 WHERE _cnt = _rankMain
),
LinesNoDuplicates AS ( -- All lines of invoice from the selected invoices that aren't duplicated in any of those invoices
SELECT * FROM @InvoiceLine
WHERE
text NOT IN( -- Exclude texts that appear twice in an invoice
SELECT text FROM ( -- texts that appear twice in an invoice
SELECT text, RANK() OVER (PARTITION BY text, invoiceId ORDER BY position) as rnk FROM @InvoiceLine WHERE invoiceId IN (SELECT n FROM @ids)
) T1 WHERE rnk = 2
)
AND
invoiceId IN (SELECT n FROM @ids)
),
Switchables AS ( -- Which lines can be switched
SELECT
UpperLine.text UpperText,
LowerLine.text LowerText
FROM
LinesNoDuplicates UpperLine INNER JOIN
LinesNoDuplicates LowerLine ON UpperLine.invoiceId = LowerLine.invoiceId AND UpperLine.position = LowerLine.position - 1
GROUP BY
UpperLine.text,
LowerLine.text
HAVING COUNT(*) = (SELECT COUNT(*) FROM @ids)
)
SELECT id, position, text, price, isRecordDeleted,
CASE WHEN SwitchUp.LowerText IS NOT NULL THEN 'yes' ELSE 'no' END AS moveUp,
CASE WHEN SwitchDown.UpperText IS NOT NULL THEN 'yes' ELSE 'no' END AS moveDown
FROM
Lines LEFT JOIN
Switchables SwitchUp ON text = SwitchUp.LowerText LEFT JOIN
Switchables SwitchDown ON text = SwitchDown.UpperText
ORDER BY pos;