PostgreSQL, advanced swapping - postgresql

I get some help on swapping certain data in rows of same table here.
Unfortunately I can't apply those solution in practice because I represent a problem too weak so with offered solutions I can't get expected results.
For that I improve examples and make it very easy to use and for try at the same time more likely my concrete situation with hope that this post will not be treated as duplicate or offensive.
Creating a table:
DROP TABLE IF EXISTS kalksad1;
CREATE TABLE kalksad1(
kalk_id int PRIMARY KEY,
brkalk integer,
brred integer,
description text
);
INSERT INTO kalksad1 VALUES
(12, 2, 5, 'text index 12 doc 2 row 5'),
(26, 2, 1, 'text index 26 doc 2 row 1'),
(30, 2, 2, 'text index 30 doc 2 row 2'),
(32, 4, 1, 'text index 32 doc 4 row 1'),
(36, 1, 1, 'text index 36 doc 1 row 1'),
(37, 1, 2, 'text index 37 doc 1 row 2'),
(38, 5, 1, 'text index 38 doc 5 row 1'),
(39, 5, 2, 'text index 39 doc 5 row 2'),
(42, 2, 3, 'text index 42 doc 2 row 3'),
(43, 2, 4, 'text index 43 doc 2 row 4'),
(46, 3, 1, 'text index 46 doc 3 row 1'),
(47, 3, 2, 'text index 47 doc 3 row 2');
What is needed?
To make a query which will swap numbers only in column 'brred' of same 'brkalk'.
Both, 'brred' and 'brkalk' are defined externally, through program.
For example purpose we will take brkalk=2, brred=3.
That mean we should swap brred value only in rows WHEN brkalk=2.
Here are two offered solution's which may be taken as reference.
Both solutions can be useful if they would work.
First one because it can swap rows no matter of order and distance and second because it swap with only first row upper or lower what is most common need.
Problem with second solution is that I don't know what it swaps but swaps first and last row instead of row 3 and 2.
That should be repaired.
First query don't work at all in new circumstances so I would like if anyone could repair it. It can be useful for swapping rows no matter of "direction" by external arguments, say swap rows 4 and 1.
Just to clarify, when I say "swap row" I mean to swap ONLY values in 'brred' column which belongs to same 'brkalk' (in this case 2).
First query:
UPDATE kalksad1 dst
SET brred=src.brred
FROM kalksad1 src
WHERE src.brkalk='2'
AND dst.kalk_id IN(2,3)
AND src.kalk_id IN(2,3)
AND dst.kalk_id <> src.kalk_id;
Second query
WITH cte1 AS (
SELECT row_number() OVER(ORDER BY kalk_id ASC) AS row_num, kalk_id, brred
FROM kalksad1
WHERE kalk_id >= 3 ORDER BY kalk_id LIMIT 2
)
UPDATE kalksad1 AS t
SET brred = COALESCE(c2.brred, t.brred)
FROM cte1 AS c1
LEFT OUTER JOIN cte1 AS c2 ON c2.row_num <> c1.row_num
WHERE t.kalk_id = c1.kalk_id AND brkalk='2';
To view data it is best to use:
SELECT * FROM kalksad1 WHERE brkalk='2' ORDER BY brred;
I would like that someone repair upper queries to become workable according to described needs or offer new solution which may be usable for that kind of swapping.
So, thanks to Roman and wildplasser I get this...
Private Function swap_row(ByVal doc_num As Integer, ByVal src_row As Integer, ByVal dest_row As Integer) As Integer
Dim affected As Integer = 0
Dim conn As NpgsqlConnection = getConnection()
Dim t As NpgsqlTransaction = conn.BeginTransaction()
Using cmd As New NpgsqlCommand( _
"UPDATE " & myKalkSadTable & " AS dst SET brred = src.brred " & _
"FROM " & myKalkSadTable & " AS src " & _
"WHERE(src.brkalk = " & doc_num.ToString & ") " & _
"AND dst.brkalk = " & doc_num.ToString & " " & _
"AND dst.brred IN (" & src_row.ToString & "," & dest_row.ToString & ") " & _
"AND src.brred IN (" & src_row.ToString & "," & dest_row.ToString & ") " & _
"AND src.kalk_id <> dst.kalk_id", conn)
affected = CInt(cmd.ExecuteNonQuery())
cmd.Dispose()
End Using
If affected = 2 then t.Commit()
t.Dispose()
conn.Close()
conn.Dispose()
Return affected
End Function
Private Sub DataGridView2_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles DataGridView2.KeyDown
If e.Control And e.KeyCode = Keys.Left Then
swap_row(kalkbr, selected_row, selected_row - 1)
Refreshlist(kalkbr)
End If
If e.Control And e.KeyCode = Keys.Right Then
swap_row(kalkbr, selected_row, selected_row + 1)
Refreshlist(kalkbr)
End If
...etc ...

for first one you have to filter brkalk on both dst and src:
update kalksad1 as dst set
brred = src.brred
from kalksad1 as src
where
src.brkalk = 2 and dst.brkalk = 2 and
dst.brred in (2,3) and
src.brred in (2,3) and
src.kalk_id <> dst.kalk_id;
sql fiddle demo
I think second one is too complicated, I've created it when I thought that you want to swap row with exact ID and next one

Related

Postgresql data calculation

Im trying to do some calculations using postgres, but no sucess so far. My query goes something like this:
select ....,
(select json_agg(data_table)
from (..... HERE GOES DE RESULT OF THE CALCULATION + a lot of business and data.... ) as data_table)
from foo
So i gonna exemplify with a table:
create temp table tbdata (id smallint, parent_id smallint, value numeric(25,2));
insert into tbdata values(1, null, 100), (2, 1, 50), (3, 1, 49), (4, 3, 20), (5, 3, 29);
select * from tbdata;
I need to calculate the difference between the sum of the siblings and the parent value. Example:
ID 2(50) + ID 3(49) = 99
ID 1(parent) = 100
so i need to add 1 to any of the childs (lets say 3), the result gonna be:
ID 2(50) + ID 3(49 + 1) = 100
ID 1(parent) = 100
After that, my ID3 have changed, so i need to update any of his childs:
ID 4(20) + ID 5(29) = 49
ID 3(parent) = 50
then again, updating value of ID 5 with the difference (50 - 49)
ID 4(20) + ID 5(29 + 1) = 50
ID 3(parent) = 50
I tried using recursive queries, windows function, and cte, but i always stuck in something. I was able to do using a function with a loop, but i dont want to do that.
Theres any way i can do it with a single SQL?

Multiple Cases For Same Result Column

Have a table where one of the columns has all of the info I need for a report.
I want to substring certain portions of this column as a column in this report, but the problem is that this column has results from 3 varying character lengths.
Example:
Row1: 20180101_ABC_12
Row2: 20180102_DEFG_23
Row3: 20180103_HIJKL_45
In this particular example I want the middle portion (eg. ABC) to be a column called 'Initials', problem is I am using CASE logic for each LEN. Not sure else how to achieve this.
My sample query below. It pulls all of the possible options, but as separate columns. What would I need to do to have these 3 options pull into one column, let's call it 'Initials'?
Thanks
SELECT
FileName
, CASE WHEN LEN(FileName) = 10 THEN SUBSTRING(FileName, 10, 3) ELSE NULL END
, CASE WHEN LEN(FileName) = 11 THEN SUBSTRING(FileName, 10, 4) ELSE NULL END
, CASE WHEN LEN(FileName) = 12 THEN SUBSTRING(FileName, 10, 5) ELSE NULL END
FROM File
In Tableau, you would accomplish this using a calculated field.
Initials:
CASE LEN(FileName)
WHEN 10 THEN SUBSTRING(FileName, 10, 3)
WHEN 11 THEN SUBSTRING(FileName, 10, 4)
WHEN 12 THEN SUBSTRING(FileName, 10, 5)
END
Or maybe
SUBSTRING(FileName
,10
,CASE LEN(FileName)
WHEN 10 THEN 3
WHEN 11 THEN 4
WHEN 12 THEN 5
END
)
But barring the more technical aspect, this can be solved with math (assuming your data is either limited to the 10, 11, and 12, or that the pattern holds):
SUBSTRING(FileName
,10
,LEN(FileName)-7
)
You need 1 CASE statement covering every possible case and not 3 separate ones, because each one creates a new column:
SELECT
FileName
, CASE LEN(FileName)
WHEN 10 THEN SUBSTRING(FileName, 10, 3)
WHEN 11 THEN SUBSTRING(FileName, 10, 4)
WHEN 12 THEN SUBSTRING(FileName, 10, 5)
ELSE NULL
END AS Initials
FROM File
Another way to get everything between the 2 _:
SELECT
FileName
, substring(
left(FileName, len(FileName) - charindex('_', reverse(FileName) + '_')),
charindex('_', FileName) + 1,
len(FileName)
) AS Initials
FROM File
but from your logic I assume that the values in column FileName have the same patern:
<9 digits>_<Initials>_<2 digits>
If this is the case then you can get what you want like this:
SELECT
FileName
, substring(FileName, 10, len(FileName) - 12) AS Initials
FROM File

Extract string with multiple criteria - sql

We have a column of varying string values like below. How to extract part of the string and return the desired result in Redshift?
Example
Remove last part that starts with an underscore and number (_1_MN, number can be 1-1000)
Remove leading part (Ed_)
Replace any remaining underscore with a space
String:
Ed_Westside Ind School District 94_Williams Elementary School_1_MN
Desired result:
Westside Ind School District 94 Williams Elementary School
MySQL
UPDATE products
SET col = SUBSTRING(col FROM 3)
WHERE col LIKE ('Ed_%')
UPDATE products
SET col = SUBSTRING(col FROM -5 )
WHERE col LIKE ('%_1_MN')
UPDATE products
SET col = REPLACE(col, '_', ' ')
While not very elegant, this worked.
REGEXP_REPLACE(LEFT(RIGHT(name, LEN(name) - 3), LEN(name) -8) , '_', ' ')

Extracting values from non-standard markup strings in PostgreSQL

Unfortunately, I have a table like the following:
DROP TABLE IF EXISTS my_list;
CREATE TABLE my_list (index int PRIMARY KEY, mystring text, status text);
INSERT INTO my_list
(index, mystring, status) VALUES
(12, '', 'D'),
(14, '[id] 5', 'A'),
(15, '[id] 12[num] 03952145815', 'C'),
(16, '[id] 314[num] 03952145815[name] Sweet', 'E'),
(19, '[id] 01211[num] 03952145815[name] Home[oth] Alabama', 'B');
Is there any trick to get out number of [id] as integer from the mystring text shown above? As though I ran the following query:
SELECT index, extract_id_function(mystring), status FROM my_list;
and got results like:
12 0 D
14 5 A
15 12 C
16 314 E
19 1211 B
Preferably with only simple string functions and if not regular expression will be fine.
If I understand correctly, you have a rather unconventional markup format where [id] is followed by a space, then a series of digits that represents a numeric identifier. There is no closing tag, the next non-numeric field ends the ID.
If so, you're going to be able to do this with non-regexp string ops, but only quite badly. What you'd really need is the SQL equivalent of strtol, which consumes input up to the first non-digit and just returns that. A cast to integer will not do that, it'll report an error if it sees non-numeric garbage after the number. (As it happens I just wrote a C extension that exposes strtol for decoding hex values, but I'm guessing you don't want to use C extensions if you don't even want regex...)
It can be done with string ops if you make the simplifying assumption that an [id] nnnn tag always ends with either end of string or another tag, so it's always [ at the end of the number. We also assume that you're only interested in the first [id] if multiple appear in a string. That way you can write something like the following horrible monstrosity:
select
"index",
case
when next_tag_idx > 0 then substring(cut_id from 0 for next_tag_idx)
else cut_id
end AS "my_id",
"status"
from (
select
position('[' in cut_id) AS next_tag_idx,
*
from (
select
case
when id_offset = 0 then null
else substring(mystring from id_offset + 4)
end AS cut_id,
*
from (
select
position('[id] ' in mystring) AS id_offset,
*
from my_list
) x
) y
) z;
(If anybody ever actually uses that query for anything, kittens will fall from the sky and splat upon the pavement, wailing in horror all the way down).
Or you can be sensible and just use a regular expression for this kind of string processing, in which case your query (assuming you only want the first [id]) is:
regress=> SELECT
"index",
coalesce((SELECT (regexp_matches(mystring, '\[id\]\s?(\d+)'))[1])::integer, 0) AS my_id,
status
FROM my_list;
index | my_id | status
-------+----------------+--------
12 | 0 | D
14 | 5 | A
15 | 12 | C
16 | 314 | E
19 | 01211 | B
(5 rows)
Update: If you're having issues with unicode handling in regex, upgrade to Pg 9.2. See https://stackoverflow.com/a/14293924/398670

Invalid length parameter passed to the LEFT or SUBSTRING function

I have the following description: 'Sample Product Maker Product Name XYZ - Size' and I would like to only get the value 'Product Name XYZ' from this. If this were just one row I'd have no issue just using SUBSTRING but I have thousands of records and although the initial value Sample Product Maker is the same for all products the Product Name could be different and I don't want anything after the hyphen.
What I have so far has generated the error in the header of this question.
SELECT i.Itemid,
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 25, (SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)))))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription, 0) - 25 as charindexpos
FROM t_items i
I am getting 'Argument data type varchar is invalid for argument 3 of substring function'
As you can see, I am getting the value for the last line the sql statement but when I try and plug that into the SUBSTRING function I get various issues.
Chances are good you have rows where the '-' is missing, which is causing your error.
Try this...
SELECT i.Itemid,
SUBSTRING(i.ShortDescription, 22, CHARINDEX('-', i.ShortDescription+'-', 22)) AS ProductDescriptionAbbrev,
FROM t_items i
You could also strip out the Sample Product Maker text and go from there:
SELECT RTRIM(LEFT(
LTRIM(REPLACE(i.ShortDescription, 'Sample Product Maker', '')),
CHARINDEX('-', LTRIM(REPLACE(i.ShortDescription, 'Sample Product Maker',
'' ))) - 1))
AS ShortDescription
Your first call to SUBSTRING specifies a length of SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)).
You might try:
declare #t_items as Table ( ItemId Int Identity, ShortDescription VarChar(100) )
insert into #t_items ( ShortDescription ) values
( 'Sample Product Maker Product Name XYZ - Size' )
declare #SkipLength as Int = Len( 'Sample Product Maker' )
select ItemId,
RTrim( LTrim( Substring( ShortDescription, #SkipLength + 1, CharIndex( '-', ShortDescription, #SkipLength ) - #SkipLength - 1 ) ) ) as ProductDescriptionAbbrev
from #t_items
The problem is that your outer call to SUBSTRING is being passed a character data type from the inner SUBSTRING call in the third parameter.
+--This call does not return an integer type
SELECT i.Itemid, V
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 25, (SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)))))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription, 0) - 25 as charindexpos
FROM t_items i
The third parameter must evaluate to the length that you want. Perhaps you meant LEN(SUBSTRING(...))?
Seems like you want something like this (22, not 25):
SELECT i.Itemid,
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 22, CHARINDEX('-', i.ShortDescription)-22))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription)-22 as charindexpos
FROM t_items i
You want:
LEFT(i.ShortDescription, isnull(nullif(CHARINDEX('-', i.ShortDescription),0) - 1, 8000))
Note that a good practice is to wrap charindex(...)'s and patindex(...)'s with nullif(...,0), and then handle the null case if desired (sometimes null is the right result, in this case we want all the text so we isnull(...,8000) for the length we want).