4

I have complex function to swap data from same name column in different rows. I read data from first, store it in local temporary variables, read data from second row and if certain conditions are met (min/max), save it to first row and then save temporary variables to second row what is slow and error prone operation.

So I thought that maybe same result could be achieved with SQL only.

Here is sample data:

CREATE TEMP TABLE tbl(
id        int PRIMARY KEY,
doc_num   integer, 
doc_text  text 
);

INSERT INTO tbl VALUES
  (1, 1, 'First column text1'),
  (2, 2, 'First column text2'),
  (4, 3, 'First column text3'),
  (7, 4, 'First column text4');

Piont is to swap only 'doc_num' column data in desired direction which may be to up or down what I do with separate functions.

If I can write a simple query in english that will sound like this:

First query:

SWAP DOC_NUM in row 2 with DOC_NUM in row 3 IF DOC_NUM in row 3 IS <= MAX(DOC_NUM);

Second query:

SWAP DOC_NUM in row 3 with DOC_NUM in row 2 IF DOC_NUM in row 2 IS >= MIN(DOC_NUM);

Is those queries possible to write with PostgreSQL and how?

Here is real code (which is ugly) from a real program which "do a job" and need improvements.

 Private Sub DataGridView2_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles DataGridView2.KeyDown

    'SWAP --------------------------------------
    If e.Control And e.KeyCode = Keys.Left Then
        Debug.Print("Swap left/down")

        Dim target_nrow As Integer
        Dim target_index As Integer
        Dim selected_nrow As Integer
        Dim selected_index As Integer
        Dim target_row As Integer = selected_row - 1
        Using conn As New NpgsqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", dbServer, dbPort, dbUser, dbPass, mydatabase))
            conn.Open()
            Dim t As NpgsqlTransaction = conn.BeginTransaction()

            Using cmd As New NpgsqlCommand( _
                  "SELECT cur_id, doc_num, nrow " & _
                  "FROM " & mytable & " " & _
                  "WHERE doc_num='" & active_doc.ToString & "' AND nrow='" & selected_row.ToString & "'", conn)

                Using dr As NpgsqlDataReader = cmd.ExecuteReader()
                    While dr.Read()
                        selected_index = CInt(dr(0))
                        selected_nrow = CInt(dr(2))
                    End While
                End Using
            End Using

            Using cmd As New NpgsqlCommand( _
                  "SELECT cur_id, doc_num, nrow " & _
                  "FROM " & mytable & " " & _
                  "WHERE doc_num='" & active_doc.ToString & "' AND nrow='" & target_row.ToString & "'", conn)

                Using dr As NpgsqlDataReader = cmd.ExecuteReader()
                    While dr.Read()
                        target_index = CInt(dr(0))
                        target_nrow = CInt(dr(2))
                    End While
                End Using
            End Using

            Dim updated_t As Integer = 0
            Using cmd As New NpgsqlCommand( _
                  "UPDATE " & mytable & " SET " & _
                  "nrow=" & selected_nrow & " " & _
                  "WHERE cur_id=" & target_index.ToString, conn)

                updated_t = CInt(cmd.ExecuteNonQuery())
                cmd.Dispose()
            End Using

            Dim updated_s As Integer = 0
            Using cmd As New NpgsqlCommand( _
                  "UPDATE " & mytable & " SET " & _
                  "nrow=" & target_nrow & " " & _
                  "WHERE cur_id=" & selected_index.ToString, conn)

                updated_s = CInt(cmd.ExecuteNonQuery())
                cmd.Dispose()
            End Using

            If updated_s > 0 And updated_t > 0 Then
                t.Commit()
            Else
                t.Rollback()
            End If

            t.Dispose()
            conn.Close()
            conn.Dispose()
        End Using

        Refreshlist(active_doc)
    End If

    If e.Control And e.KeyCode = Keys.Right Then
        Debug.Print("Swap right/up")

        'similar code to swap up again

    End If

Whole story is in question on how to make this shorter, faster and more elegant?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Wine Too
  • 4,515
  • 22
  • 83
  • 137
  • 1
    It would be interesting to know (a) your PostgreSQL version, (b) whether you ever do this from multiple connections at a time, and (c) why on earth you want to do this in the first place. What are you trying to achieve with this rather strange procedure? It is an attempt at some kind of sort? I think if you want to do it in SQL you'll be better off phrasing it as a *set operation* using an `UPDATE ... FROM`, but to do that I'd need to know the *why*, the bigger picture of what you're trying to do here. – Craig Ringer Aug 13 '13 at 11:34
  • I look my data in data grid with this command: SELECT id, doc_num, doc_text FROM tbl ORDER BY doc_num;, so when I swap 'doc_num' then my rows change an order and indirectly my document changes a number what is very wanted functionality for me. If you can help with query it would be nice to. I havent multiple connections since I connect with my programs just to do a job, very short time. True, I was never try PostgreSQL in network yet. My PG version is 9.1.3. (windows) – Wine Too Aug 13 '13 at 11:46
  • `... IF DOC_NUM in row 3 IS <= MAX(DOC_NUM)` .. that is *always* true by definition. You may want to clarify what you mean by `MAX(DOC_NUM)`. – Erwin Brandstetter Aug 13 '13 at 11:47
  • @Ervin, if I stand (in grid) on last row then I shouldn't swap to UP and also if I stand on first row I shouldn't swap to less than first row (which is not allways 1, can be say 95). – Wine Too Aug 13 '13 at 11:49
  • 1
    Sorry I can't make sense of your description. Seems to be a simple problem, that can certainly be solved with a simple SQL statement. I just don't comprehend what you are trying to do exactly. – Erwin Brandstetter Aug 13 '13 at 11:55
  • Sorry, english is not my first language. I use PG database from data grid in NET. By swaping 'doc_num' all indexes remain untouched but in my datagrid an order of document changes so I can "travel" one document from beginning to end of list of documents and those document will (for my user) stay on place where it leave it. Whole problem is that I dont know how to swap 'doc_num' in starting and destination rows. – Wine Too Aug 13 '13 at 11:59
  • I still didn't solve a problem so any idea will be welcome. – Wine Too Aug 13 '13 at 19:34

2 Answers2

11

Example: swap doc_num for ids 2 and 4:

UPDATE tbl dst
SET doc_num = src.doc_num
FROM tbl src
WHERE dst.id IN(2,4)
AND src.id IN(2,4)
AND dst.id <> src.id -- don't try this at home!
        ;

SELECT * FROm tbl
ORDER BY id;

Result:

 id | doc_num |      doc_text      
----+---------+--------------------
  1 |       1 | First column text1
  2 |       3 | First column text2
  4 |       2 | First column text3
  7 |       4 | First column text4
(4 rows)
wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • WOW wildplasser, absolutely strongest sql I ever seen!! Short but rich, and it work at example data. But I have problem to get it working with my real data. Based on your example I try this "UPDATE kalksad dst SET brred = src.brred FROM kalksad dst WHERE dst.kalk_id IN(3,4) AND src.kalk_id IN(3,4) AND dst.kalk_id <> src.kalk_id;" where table name=kalksad, id=kalk_id, doc_num=brred. By executing I get error "ERROR: table name "dst" specified more than once". What do I do wrong? – Wine Too Aug 14 '13 at 18:07
  • `What do I do wrong?` You are changing the rules while playing the game. BTW: Your `FROM kalksad dst` should be `FROM kalksad src` for instance. – wildplasser Aug 14 '13 at 18:40
  • Yes, you are right. It is often simply impossible to give real example and isolate problem from real situation. For that I do such examples which helps me to solve a problem. Still don't work in real situation for me but I will try further to get it on this excellent SQL example. Thanks for that! – Wine Too Aug 14 '13 at 19:56
  • Isolating the problem is almost equal to solving it. Most SQL solutions are _in essence_ pretty simple. BTW: I did not read any of your procedural code, I cannot see its relevance, and it only disturbs my thinking. – wildplasser Aug 14 '13 at 19:59
  • It was my mistake that I make too short example for real needs. Actually i have involved one additional column so structure is index, doc_number, doc_row, doc_text. For that reason I have few same doc_num numbers and for that this query probably don't work as expected. I shouldn't swap rows in different documents but only in one same. So: UPDATE kalksad dst SET brred=src.brred FROM kalksad src WHERE src.brkalk='2' AND dst.kalk_id IN(3,4) AND src.kalk_id IN(3,4) AND dst.kalk_id <> src.kalk_id; returns no error but 0 affected rows. What more can I try? Should I make new example/question? – Wine Too Aug 14 '13 at 21:15
2

to swap data down you could try something like.:

with cte1 as (
    select
        row_number() over(order by id asc) as row_num,
        id, doc_num
    from tbl
    where id >= %your current id% order by id limit 2
)
update tbl as t set
    doc_num = coalesce(c2.doc_num, t.doc_num)
from cte1 as c1
    left outer join cte1 as c2 on c2.row_num <> c1.row_num
where t.id = c1.id;

see sql fiddle demo

Up will be the same but where id <= %your current id% order by id desc limit 2

Roman Pekar
  • 107,110
  • 28
  • 195
  • 197
  • Thanks Roman, your solution work as expected on given data. Now I need a little more time to test it in real situation. – Wine Too Aug 14 '13 at 17:59