Generate sequential number in SQL - not by using Identity - sql

I am working on a task where my query will produce a fixed width column. One of the fields in the fixed width column needs to be a sequentially generated number.
Below is my query:
select
_row_ord = 40,
_cid = t.client_num,
_segment = 'ABC',
_value =
concat(
'ABC*',
'XX**', --Hierarchical ID number-this field should be sequentially generated
'20*',
'1*','~'
)
from #temp1 t
My output:
Is there a way to declare #num as a parameter that generates number sequentially?
PS: The fields inside the CONCAT function is all hardcoded. Only the 'XX' i.e., the sequential number has to be dynamically generated
Any help?!

You could create a SEQUENCE object, then call the NEXT VALUE FOR the SEQUENCE in your query.
Something along these lines:
CREATE SEQUENCE dbo.ExportValues
START WITH 1
INCREMENT BY 1 ;
GO
And then:
select
_row_ord = 40,
_cid = t.client_num,
_segment = 'ABC',
_value =
concat(
'ABC*',
RIGHT(CONCAT('000000000000000', NEXT VALUE FOR dbo.ExportValues),15)
'**',
'20*',
'1*','~'
)
from #temp1 t
You'd have to tweak how many zeros there are for the padding and how many digits to trim it to for your requirements. If duplicate values are ok, you could have the SEQUENCE reset periodically. See the documentation for more on that. It's just another line in the CREATE statement.

You can use row_number() -- made a little more complicated because you are zero-padding it:
select _row_ord = 40, _cid = t.client_num, _segment = 'ABC',
_value = concat('ABC*',
right('00' + convert(varchar(255), row_number() over (order by ?)), 2),
'XX**', --Hierarchical ID number-this field should be sequentially generated
'20*',
'1*','~'
)
from #temp1 t;
Note that the ? is for the column that specifies the ordering. If you don't care about the ordering of the numbers, use (select null) in place of the ?.

Related

How to generate random string for all rows in postgres

I have foo table and would like to set bar column to a random string. I've got the following query:
update foo
set bar = select string_agg(substring('0123456789bcdfghjkmnpqrstvwxyz', round(random() * 30)::integer, 1), '')
from generate_series(1, 9);
But it generates the random string once and reuse it for all rows. How can I make it to generate one random string for each row?
I know I can make it a function like this:
create function generate_bar() returns text language sql as $$
select string_agg(substring('0123456789bcdfghjkmnpqrstvwxyz', round(random() * 30)::integer, 1), '')
from generate_series(1, 9)
$$;
and then call the function in the update query. But I'd prefer to do it without a function.
The problem is that the Postgres optimizer is just too smart and deciding that it can execute the subquery only once for all rows. Well -- it is really missing something obvious -- the random() function makes the subquery volatile so this is not appropriate behavior.
One way to get around this is to use a correlated subquery. Here is an example:
update foo
set bar = array_to_string(array(select string_agg(substring('0123456789bcdfghjkmnpqrstvwxyz', round(random() * 30)::integer, 1), '')
from generate_series(1, 9)
where foo.bar is distinct from 'something'
), '');
Here is a db<>fiddle.
For a random mixed-case numeric-inclusive string containing up to 32 characters use:
UPDATE "foo" SET "bar"= substr(md5(random()::text), 0, XXX);
and replace XXX with the length of desired string plus one.
To replace all with length 32 strings, Example:
UPDATE "foo" SET "bar"= substr(md5(random()::text), 0, 33);
14235ccd21a408149cfbab0a8db19fb2 might be a value that fills one of the rows. Each row will have a random string but not guaranteed to be unique.
For generating strings with more than 32 characters
Just combine the above with a CONCAT

SQL Query to use column as a formula to calculate value

I am working on a request that i need to calculate value based on formula specified in another column
Below is my table:
I need to write the query to get value which will be based on FORMULA column. eg I need result as
As formula could be anything consisting of my columns PRICE and SIZE, how do i write the query to achieve this?
Dynamic query is the (only) way to go and it's not that complicated:
DECLARE #query NVARCHAR(MAX) = '';
SELECT #query = #query + '
UNION
SELECT ItemID, Price, Size, Formula, ' + Formula + ' AS CalcValue FROM YourTable WHERE Formula = ''' + Formula + ''' '
FROM YourTable;
SET #query = STUFF(#query,1,8,'');
PRINT #query;
EXEC (#query);
SQLFiddle DEMO
But you must be aware how prone to errors this is. If value of Formula column is not valid formula query breaks.
edit: going with UNION instead of UNION ALL because of same formula appearing in multiple rows
edit2: Plan B - Instead of running bunch of same select queries and making distinct of results, better to make distinct formulas at beginning:
DECLARE #query NVARCHAR(MAX) = '';
WITH CTE_DistinctFormulas AS
(
SELECT DISTINCT Formula FROM YourTable
)
SELECT #query = #query + '
UNION ALL
SELECT ItemID, Price, Size, Formula, ' + Formula + ' AS CalcValue FROM YourTable WHERE Formula = ''' + Formula + ''' '
FROM CTE_DistinctFormulas;
SET #query = STUFF(#query,1,12,'');
PRINT #query;
EXEC (#query);
SQLFiddle DEMO 2 - added few more rows
Another alternative which is relatively easy to do is with a CLR. You can take advantage of the Compute Method of DataTable to give a simple one line code in C#.
[Microsoft.SqlServer.Server.SqlFunction]
public static double Evaluate(SqlString expression)
{
return double.Parse((new DataTable()).Compute(expression.ToString(), "").ToString());
}
Then add the assembly to SQL Server and create the wrapper function:
CREATE FUNCTION [dbo].[Evaluate](#expression [nvarchar](4000))
RETURNS [float] WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [YourAssemblyName].[YourClassName].[Evaluate]
GO
Now you can call the function as part of a simple select statement:
SELECT itemid, price, size, formula,
dbo.Evaluate(REPLACE(REPLACE(formula, 'PRICE', FORMAT(price,'0.00')),
'SIZE', FORMAT(size, '0'))) as calcvalue FROM YourTable
I did something similar to this and if you play in a smaller pool of operations it is not that hard. I went with a series where I had X and Y columns and an operator. Then I just did a large case when statement on identifying the operator and performing logic based on that. You may have to change your structure slightly. The core of the problem is that SQL is a result set based engine so anything where you have to do an operation to determine dynamic is going to be slower. EG:
DECLARE #Formula TABLE
(
FormulaId INT IDENTITY
, FormulaName VARCHAR(128)
, Operator VARCHAR(4)
);
DECLARE #Values TABLE
(
ValueId INT IDENTITY
, FormulaId INT
, Price MONEY
, Size INT
)
INSERT INTO #Formula (FormulaName, Operator)
VALUES ('Simple Addition', '+'), ( 'Simple Subtraction', '-'), ('Simple Multiplication', '*'), ('Simple Division', '/'), ('Squared', '^2'), ('Grow by 20 percent then Multiply', '20%*')
INSERT INTO #Values (FormulaId, Price, Size)
VALUES (1, 10, 5),(2, 10, 5),(3, 10, 5),(4, 10, 5),(5, 10, 5),(6, 10, 5),(1, 16, 12),(6, 124, 254);
Select *
From #Values
SELECT
f.FormulaId
, f.FormulaName
, v.ValueId
, Price
, Operator
, Size
, CASE WHEN Operator = '+' THEN Price + Size
WHEN Operator = '-' THEN Price - Size
WHEN Operator = '*' THEN Price * Size
WHEN Operator = '/' THEN Price / Size
WHEN Operator = '^2' THEN Price * Price
WHEN OPerator = '20%*' THEN (Price * 1.20) * Size
END AS Output
FROM #Values v
INNER JOIN #Formula f ON f.FormulaId = v.FormulaId
With this method my operation is really just a pointer reference to another table that has an operator that is really for all intents and purposes just a token I use for my case statement. You can even compound this potentially and if you wanted to do multiple passed you could add a 'Group' column and a 'Sequence' and do one after the other. It depends how difficult your 'formulas' become. Because if you get into more than 3 or 4 variables that change frequently with frequent operator changes, then you probably would want to do dynamic sql. But if they are just a few things, that should not be that hard. Just keep in mind the downside of this approach is that it is hard coded at a certain level, yet flexible that the parameters put into it can apply this formula over and over.

how to merge query results in one file adding a header and footer from other two queries

I'm doing a query that merge three different queries.
My query looks like this...
HEADER ( a query with only one line of information).
BODY ( a query with many rows).
FOOTER ( a query with only one line of information)
I have the three queries independently, but I have to merge them in only one result to be send to a txt file.
I do not have any idea how to do this because in theory the first query must be the first line of the result and the last query must be the foot....
Everything must be in continuous text... the problem is adding header and footer to the result..
You're going to have a tough time doing this in a single query unless you return each row as a single value. For example:
;WITH header AS
(
SELECT rn = 1, val = 'col1,col2,col3'
),
query AS
(
SELECT rn = 2, val = CONVERT(VARCHAR(12), col1)
+ ',' + col2 + ',' + CONVERT(CHAR(10), col3, 112)
FROM dbo.sometable
),
footer AS
(
SELECT rn = 3, val = 'footer ----'
)
SELECT val FROM header
UNION ALL
SELECT val FROM query
UNION ALL
SELECT val FROM footer
ORDER BY rn;
This really helped me in adding header and footer in my extracted dataset. In order to keep equal number of columns in header, footer and extracted dataset we add n number of extra columns using following code:
select a, b,'' as ' '
'' as ' ' can add any number of required columns to keep the consistency and would not affect the output.

sql FInding strings with duplicate characters

I have a list of strings:
HEAWAMFWSP
TLHHHAFWSP
AWAMFWHHAW
AUAWAMHHHA
Each of these strings represent 5 pairs of 2 character combinations (i.e. HE AW AM FW SP)
What I am looking to do in SQL is to display all strings that have duplication in the pairs.
Take string number 3 from above; AW AM FW HH AW. I need to display this record because it has a duplicate pair (AW).
Is this possible?
Thanks!
Given current requirements, yes this is dooable. Here's a version which uses a recursive CTE (text may need to be adjusted for vendor idiosyncracies), written and tested on DB2. Please note that this will return multiple rows if there is more than 2 instances of a pair in a string, or more than 1 set of duplicates.
WITH RECURSIVE Pair (rowid, start, pair, text) as (
SELECT id, 1, SUBSTR(text, 1, 2), text
FROM SourceTable
UNION ALL
SELECT rowid, start + 2, SUBSTR(text, start + 2, 2), text
FROM Pair
WHERE start < LENGTH(text) - 1)
SELECT Pair.rowid, Pair.pair, Pair.start, Duplicate.start, Pair.text
FROM Pair
JOIN Pair as Duplicate
ON Duplicate.rowid = Pair.rowid
AND Duplicate.pair = Pair.pair
AND Duplicate.start > Pair.start
Here's a not very elegant solution, but it works and only returns the row once no matter how many duplicate matches. The substring function is for SQLServer, not sure what it is for Oracle.
select ID, Value
from MyTable
where (substring(Value,1,2) = substring(Value,3,4)
or substring(Value,1,2) = substring(Value,5,6)
or substring(Value,1,2) = substring(Value,7,8)
or substring(Value,1,2) = substring(Value,9,10)
or substring(Value,3,4) = substring(Value,5,6)
or substring(Value,3,4) = substring(Value,7,8)
or substring(Value,3,4) = substring(Value,9,10)
or substring(Value,5,6) = substring(Value,7,8)
or substring(Value,5,6) = substring(Value,9,10)
or substring(Value,7,8) = substring(Value,9,10))

Uppercase first two characters in a column in a db table

I've got a column in a database table (SQL Server 2005) that contains data like this:
TQ7394
SZ910284
T r1534
su8472
I would like to update this column so that the first two characters are uppercase. I would also like to remove any spaces between the first two characters. So T q1234 would become TQ1234.
The solution should be able to cope with multiple spaces between the first two characters.
Is this possible in T-SQL? How about in ANSI-92? I'm always interested in seeing how this is done in other db's too, so feel free to post answers for PostgreSQL, MySQL, et al.
Here is a solution:
EDIT: Updated to support replacement of multiple spaces between the first and the second non-space characters
/* TEST TABLE */
DECLARE #T AS TABLE(code Varchar(20))
INSERT INTO #T SELECT 'ab1234x1' UNION SELECT ' ab1234x2'
UNION SELECT ' ab1234x3' UNION SELECT 'a b1234x4'
UNION SELECT 'a b1234x5' UNION SELECT 'a b1234x6'
UNION SELECT 'ab 1234x7' UNION SELECT 'ab 1234x8'
SELECT * FROM #T
/* INPUT
code
--------------------
ab1234x3
ab1234x2
a b1234x6
a b1234x5
a b1234x4
ab 1234x8
ab 1234x7
ab1234x1
*/
/* START PROCESSING SECTION */
DECLARE #s Varchar(20)
DECLARE #firstChar INT
DECLARE #secondChar INT
UPDATE #T SET
#firstChar = PATINDEX('%[^ ]%',code)
,#secondChar = #firstChar + PATINDEX('%[^ ]%', STUFF(code,1, #firstChar,'' ) )
,#s = STUFF(
code,
1,
#secondChar,
REPLACE(LEFT(code,
#secondChar
),' ','')
)
,#s = STUFF(
#s,
1,
2,
UPPER(LEFT(#s,2))
)
,code = #s
/* END PROCESSING SECTION */
SELECT * FROM #T
/* OUTPUT
code
--------------------
AB1234x3
AB1234x2
AB1234x6
AB1234x5
AB1234x4
AB 1234x8
AB 1234x7
AB1234x1
*/
UPDATE YourTable
SET YourColumn = UPPER(
SUBSTRING(
REPLACE(YourColumn, ' ', ''), 1, 2
)
)
+
SUBSTRING(YourColumn, 3, LEN(YourColumn))
UPPER isn't going to hurt any numbers, so if the examples you gave are completely representative, there's not really any harm in doing:
UPDATE tbl
SET col = REPLACE(UPPER(col), ' ', '')
The sample data only has spaces and lowercase letters at the start. If this holds true for the real data then simply:
UPPER(REPLACE(YourColumn, ' ', ''))
For a more specific answer I'd politely ask you to expand on your spec, otherwise I'd have to code around all the other possibilities (e.g. values of less than three characters) without knowing if I was overengineering my solution to handle data that wouldn't actually arise in reality :)
As ever, once you've fixed the data, put in a database constraint to ensure the bad data does not reoccur e.g.
ALTER TABLE YourTable ADD
CONSTRAINT YourColumn__char_pos_1_uppercase_letter
CHECK (ASCII(SUBSTRING(YourColumn, 1, 1)) BETWEEN ASCII('A') AND ASCII('Z'));
ALTER TABLE YourTable ADD
CONSTRAINT YourColumn__char_pos_2_uppercase_letter
CHECK (ASCII(SUBSTRING(YourColumn, 2, 1)) BETWEEN ASCII('A') AND ASCII('Z'));
#huo73: yours doesn't work for me on SQL Server 2008: I get 'TRr1534' instead of 'TR1534'.
update Table set Column = case when len(rtrim(substring (Column , 1 , 2))) < 2
then UPPER(substring (Column , 1 , 1) + substring (Column , 3 , 1)) + substring(Column , 4, len(Column)
else UPPER(substring (Column , 1 , 2)) + substring(Column , 3, len(Column) end
This works on the fact that if there is a space then the trim of that part of string would yield length less than 2 so we split the string in three and use upper on the 1st and 3rd char. In all other cases we can split the string in 2 parts and use upper to make the first two chars to upper case.
If you are doing an UPDATE, I would do it in 2 steps; first get rid of the space (RTRIM on a SUBSTRING), and second do the UPPER on the first 2 chars:
// uses a fixed column length - 20-odd in this case
UPDATE FOO
SET bar = RTRIM(SUBSTRING(bar, 1, 2)) + SUBSTRING(bar, 3, 20)
UPDATE FOO
SET bar = UPPER(SUBSTRING(bar, 1, 2)) + SUBSTRING(bar, 3, 20)
If you need it in a SELECT (i.e. inline), then I'd be tempted to write a scalar UDF