how to custom sort data in sql server - sql

I am using Sql server 2008 r2, I need to order by the following data:
CardNo
R-1
R-2
R-12
R-1A
R-3
R-2B
Result should look like this
CardNo
R-1
R-1A
R-2
R-2B
R-3
R-12
I have tried different combinations in order by clause but of no use like:
select * from [Coll2012-13] where
SUBSTRING(CardNo, 1, 1) IN ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z')
AND SUBSTRING(CardNo, 2, 1) IN ('-')
AND SUBSTRING(CardNo, 3, 1) IN ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0')
and Landmark='Anandbagh' order by LEN(CardNo),CardNo ASC

Assumption: Values are always of the format 'Letter-Alphanumeric string'
Try this:
select card_no
from [Coll2012-13]
order by left(card_no,1),
case
when isnumeric(substring(card_no,3, len(card_no))) = 1
then cast(substring(card_no,3, len(card_no)) as int)
else cast(substring(card_no,3, patindex('%[A-Z]%',substring(card_no,3, len(card_no)))-1) as int)
end,
case
when patindex('%[A-Z]%', substring(card_no,3,len(card_no))) > 0
then substring(card_no,patindex('%[A-Z]%', substring(card_no,3,len(card_no)))+2,1)
end
How this works: First check the starting letter. Next, check if the alphanumeric part is in fact only numeric. If so, get the integer value of that part. If it is not, get the numeric part of it and use that as the sort value. Finally, if the alphanumeric part does contain a letter, use that as another sort value.
Demo here.

You should replace all non digit chars with "" and convert the rest in a number. Then sort by the number.
You can try to use a function as described here SQL Server 2000: how do I return only the number from a phone number column
for this

A simple - but 'should be maintained' - solution is to create a sorting table and join that table to your result set. The table contains all CardNo values and associates a sort order to them.
EDIT:
CREATE TABLE CardNoOrderHelper (
CardNo VARCHAR(16)
, OrderRank INT CONSTRAINT DF_CardNoOrderHelper_OrderRank DEFAULT 0
, PRIMARY KEY CLUSTERED (
CardNo ASC
)
);
-- Fill your table with the expected sort order (dinstinct insert, than adjust the order ranks)
SELECT
*
FROM
[Coll2012-13] AS T
LEFT JOIN CardNoOrderHelper CH
ON T.CardNo = CH.CardNo
ORDER BY
T.OrderRank
As I said, you have to maintain this table. When your resultset is small this can be done manually.

Try this
with cte
AS
(
select
*
,substring(CardNo, 1, charindex('-')-1) RealRank1
,substring(CardNo, charindex('-')+ 1, 10) RealRank2
from
[Coll2012-13]
)
select
*
from
cte
order by
RealRank1
,RealRank2

I have the similar problem. I have used this query below.
To use this query u must know the ID (string) maximum length and I have adjusted the format in this query to use Mark-Number+Alphabet (example R-1,R-1A, R-11, R-11A, R-1AA,R-1B)
Query:
select
b.CardNo, b.separatorIndex
,b.Mark, b.Mark_length
,case b.isNumericMark1 + b.isNumericMark2 + b.isNumericMark3
when 1 then cast (b.Mark1 as int)
when 2 then cast (b.Mark1 + b.Mark2 as int)
when 3 then cast (b.Mark1 + b.Mark2 + b.Mark3 as int)
end as Mark1
from
(
select
a.CardNo
,charindex('-',a.cardNo,0) as separatorIndex
, len(a.cardNo) - charindex('-',a.cardNo,0) as Mark_length
, substring(a.CardNo,0,charindex('-',a.cardNo,0)) as Mark
, substring(a.CardNo,charIndex('-',a.cardNo,0)+1,1) as Mark1
, isnumeric(substring(a.CardNo,charIndex('-',a.cardNo,0)+1,1*1)) as isNumericMark1
, substring(a.CardNo,charIndex('-',a.cardNo,0)+2,1) as Mark2
, isnumeric(substring(a.CardNo,charIndex('-',a.cardNo,0)+2,1)) as isNumericMark2
, substring(a.CardNo,charIndex('-',a.cardNo,0)+3,1) as Mark3
, isnumeric(substring(a.CardNo,charIndex('-',a.cardNo,0)+3,1)) as isNumericMark3
from [Coll2012-13] a
) b
order by Mark,Mark1,Mark_length
Result:
CardNo separatorIndex Mark Mark_length Mark1
-------------------- -------------- -------------------- ----------- -----------
R-1 2 R 1 1
R-1A 2 R 2 1
R-2 2 R 1 2
R-2B 2 R 2 2
R-3 2 R 1 3
R-12 2 R 2 12
Hope this help.

Related

log file with insert - how to easly combine column name with inserted value

Has anybody a good solution how to combine column name with inserted value from prepared INSERT SQL?
I have log file. In this log file I have INSERT query. This query contains over 100 columns for example:
INSERT INTO tab
(col_001, col_002, col_003, col_004, col_005, col_006, col_007, col_008, col_009, col_010)
VALUES ('a', 'b', 'c,,,', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
Do you have any ideas how to easly combine column name to value like below:
col_001 = 'a'
col_002 = 'b'
col_003 = 'c,,,'
col_004 = 'd'
col_005 = 'e'
col_006 = 'f'
col_007 = 'g'
col_008 = 'h'
col_009 = 'i'
col_010 = 'j
Lets imagine that I need to find what value will be inserted in column col_067.
Thanks.
Use unpivot for convert column to row
select colname,
colvalue
from tab
unpivot
(
colvalue
for colname in (col_001, col_002, col_003, col_004, col_005, col_006, col_007, col_008, col_009, col_010)
) unpiv;
Demo in db<>fiddle

SQL Server : how to convert binary back to int

Admin of our IS stored 8 flags like '00010000' (only true and false) to SQL Server as binary(2). In this format data has values like '0x1000'.
Is possible to convert this binary back to '00010000' ?
Convert, Cast, Substring don't work.
Query returns hexadecimal number (0x... - it is hexadecimal) as its bit mask
CREATE TABLE #Temp(
Test VARBINARY(2)
)
INSERT #Temp
VALUES
(0x1001),
(0x3001),
(0x5000),
(0x6000),
(0xf000),
(0xf250)
SELECT *, REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
CONVERT(VARCHAR(32), Test, 2)
, '0', '0000')
, '1', '0001')
, '2', '0010')
, '3', '0011')
, '4', '0100')
, '5', '0101')
, '6', '0110')
, '7', '0111')
, '8', '1000')
, '9', '1001')
, 'a', '1010')
, 'b', '1011')
, 'c', '1100')
, 'd', '1101')
, 'e', '1110')
, 'f', '1111')
FROM #Temp
DROP TABLE #Temp
Your original data is most likely lost. When you convert a number to binary in SQL there is only a limited amount of "space" to store that data, the space is determined by the byte size of the field, in this case 2.
The issue occurs when the data is saved to the database, using a binary(2) means that data is truncated when saved, meaning that you have lost at least the 4 first "flags" in your data. (the 4 numbers at the start of your binary number).
for example, the below shows what SQL stores for the number 10010010 in binary 6, 4 and 2.
Binary(6) - 0x00000098BD9A
Binary(4) - 0x0098BD9A
Binary(2) - 0xBD9A
as you can see using binary 2 means you lose data from your number. That is gone and cannot be retrieved using the DB (unless you save transaction logs, which, given you have a DBA storing boolean values as an 8 bit block using a binary(2) field I doubt you have). So sorry to say this but you simply cannot do what you need to, and it's whoever thought using binary(2) for an 8 digit number was a good idea and then didn't test their decision that is to blame.
Yes you can
BINARY(2) means 2 bytes, so 16 bits
0x3000 = '0011000000000000'
declare #v int = 0x3000
;WITH
T AS ( SELECT NULL N UNION ALL SELECT NULL ),
N as (
SELECT ROW_NUMBER() OVER (ORDER BY T2.N) N
FROM T T2, T T4, T T8, T T16
),
V AS (
select N, POWER(2,N-1) P, CAST(#v as binary(2)) b2, #v V
from N
),
B AS (
SELECT N, B2, CAST((V/P) % 2 AS char(1)) B
from V
)
SELECT B2, [16]+[15]+[14]+[13]+[12]+[11]+[10]+[9]+[8]+[7]+[6]+[5]+[4]+[3]+[2]+[1] BASE2
FROM B
PIVOT ( MIN(B) FOR N IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]) ) P
output
B2 BASE2
0x3000 0011000000000000

Common Table Expression to traverse down hierarchy

The Structure
I have 2 tables that link to each other. One is a set of values and a nullable foreign key that points to the Id of the other table, which contains 2 foreign keys back to the other table.
HierarchicalTable
Id LeftId RightId SomeValue
1 1 2 some value
2 3 4 top level in tree
3 5 6 incorrect hierarchy 1
4 7 8 incorrect result top level
IntermediateTable
Id SomeValue HierarchicalTableId
1 some value NULL
2 value NULL
3 NULL 1
4 value NULL
5 incorrect result 1 NULL
6 incorrect result 3 NULL
7 incorrect result 3 NULL
8 NULL 3
Each table points down the hierarchy. Here is this structure graphed out for the Hierarchical Table records 1 & 2 and their IntermediateTable values:
(H : HierarchicalTable, I : IntermediateTable)
H-2
/ \
I-3 I-4
/
H-1
/ \
I-1 I-2
The Problem
I need to be able to send in an Id for a given HierarchicalTable and get all the HierarchicalTable records below it. So, for the structure above, if I pass 1 into a query, I should just get H-1 (and from that, I can load the related IntermediateTable values). If I pass 2, I should get H-2 and H-1 (and, again, use those to load the relevant IntermediateTable values).
The Attempts
I've tried using a CTE, but there are a few main things that are different from the examples I've seen:
In my structure, the objects point down to their children, instead of up to their parent
I have the Id of the top object, not the Id of the bottom object.
My hierarchy is split across 2 tables. This shouldn't be a big issue once I understand the algorithm to find the results I need, but this could be causing additional confusion for me.
If I run this query:
declare #TargetId bigint = 2
;
with test as (
select h.*
from dbo.hierarchicaltable h
inner join dbo.intermediatetable i
on (h.leftid = i.id or h.rightid = i.id)
union all
select h.*
from dbo.hierarchicaltable h
where h.id = #TargetId
)
select distinct *
from test
I get all 4 records in the HierarchicalTable, instead of just records 1 & 2. I'm not sure if what I want is possible to do with a CTE.
Try this:
I'm build entire tree with both tables, then filter (only hierarchicaltable records).
DECLARE #HierarchicalTable TABLE(
Id INT,
LeftId INT,
RightId INT,
SomeValue VARCHAR(MAX)
)
INSERT INTO #HierarchicalTable
VALUES
(1, 1, 2, 'some value '),
(2, 3, 4, 'top level in tree '),
(3, 5, 6, 'incorrect hierarchy 1 '),
(4, 7, 8, 'incorrect result top level')
DECLARE #IntermediateTable TABLE(
Id INT,
SomeValue VARCHAR(MAX),
HierarchicalTableId INT
)
INSERT INTO #IntermediateTable
VALUES
(1, 'some value' ,NULL ),
(2, 'value ' ,NULL ),
(3, NULL ,1 ),
(4, 'value ' ,NULL ),
(5, 'incorrect result 1' ,NULL ),
(6, 'incorrect result 3' ,NULL ),
(7, 'incorrect result 3' ,NULL ),
(8, NULL ,3 )
DECLARE #TargetId INT = 2;
WITH CTE AS (
SELECT Id AS ResultId, LeftId, RightId, NULL AS HierarchicalTableId
FROM #HierarchicalTable
WHERE Id = #TargetId
UNION ALL
SELECT C.Id AS ResultId, C.LeftId, C.RightId, NULL AS HierarchicalTableId
FROM #HierarchicalTable C
INNER JOIN CTE P ON P.HierarchicalTableId = C.Id
UNION ALL
SELECT NULL AS ResultId, NULL AS LeftId, NULL AS RightId, C.HierarchicalTableId
FROM #IntermediateTable C
INNER JOIN CTE P ON P.LeftId = C.Id OR P.RightId = C.Id
)
SELECT *
FROM CTE
WHERE ResultId IS NOT NULL

value substitution/replacement in a string

I have a string x-y+z. The values for x, y and z will be stored in a table. Say
x 10
y 15
z 20
This string needs to be changed like 10-15+20.
Anyway I can achieve this using plsql or sql?
using simple Pivot we can do
DECLARE #Table1 TABLE
( name varchar(1), amount int )
;
INSERT INTO #Table1
( name , amount )
VALUES
('x', 10),
('y', 15),
('Z', 25);
Script
Select CAST([X] AS VARCHAR) +'-'+CAST([Y] AS VARCHAR)+'+'+CAST([Z] AS VARCHAR) from (
select * from #Table1)T
PIVOT (MAX(amount) FOR name in ([X],[y],[z]))p
An approach could be the following, assuming a table like this:
create table stringToNumbers(str varchar2(16), num number);
insert into stringToNumbers values ('x', 10);
insert into stringToNumbers values ('y', 20);
insert into stringToNumbers values ('zz', 30);
insert into stringToNumbers values ('w', 40);
First tokenize your input string with something like this:
SQL> with test as (select 'x+y-zz+w' as string from dual)
2 SELECT 'operand' as typ, level as lev, regexp_substr(string, '[+-]+', 1, level) as token
3 FROM test
4 CONNECT BY regexp_instr(string, '[a-z]+', 1, level+1) > 0
5 UNION ALL
6 SELECT 'value', level, regexp_substr(string, '[^+-]+', 1, level) as token
7 FROM test
8 CONNECT BY regexp_instr(string, '[+-]', 1, level - 1) > 0
9 order by lev asc, typ desc;
TYP LEV TOKEN
------- ---------- --------------------------------
value 1 x
operand 1 +
value 2 y
operand 2 -
value 3 zz
operand 3 +
value 4 w
In the example I used lowercase literals and only +/- signs; you can easily edit it to handle something more complex; also, I assume the input string is well-formed.
Then you can join your decoding table to the tokenized string, building the concatenation:
SQL> select listagg(nvl(to_char(num), token)) within group (order by lev asc, typ desc)
2 from (
3 with test as (select 'x+y-zz+w' as string from dual)
4 SELECT 'operand' as typ, level as lev, regexp_substr(string, '[+-]+', 1, level) as token
5 FROM test
6 CONNECT BY regexp_instr(string, '[a-z]+', 1, level+1) > 0
7 UNION ALL
8 SELECT 'value', level, regexp_substr(string, '[^+-]+', 1, level) as token
9 FROM test
10 CONNECT BY regexp_instr(string, '[+-]', 1, level - 1) > 0
11 order by lev asc, typ desc
12 ) tokens
13 LEFT OUTER JOIN stringToNumbers on (str = token);
LISTAGG(NVL(TO_CHAR(NUM),TOKEN))WITHINGROUP(ORDERBYLEVASC,TYPDESC)
--------------------------------------------------------------------------------
10+20-30+40
This assumes that every literal in you input string has a corrensponding value in table. You can even handle the case of strings with no corrensponding number, for example assigning 0:
SQL> select listagg(
2 case
3 when typ = 'operand' then token
4 else to_char(nvl(num, 0))
5 end
6 ) within group (order by lev asc, typ desc)
7 from (
8 with test as (select 'x+y-zz+w-UNKNOWN' as string from dual)
9 SELECT
.. ...
16 ) tokens
17 LEFT OUTER JOIN stringToNumbers on (str = token);
LISTAGG(CASEWHENTYP='OPERAND'THENTOKENELSETO_CHAR(NVL(NUM,0))END)WITHINGROUP(ORD
--------------------------------------------------------------------------------
10+20-30+40-0
Create a function like this:
create table ttt1
( name varchar(1), amount int )
;
INSERT INTO ttt1 VALUES ('x', 10);
INSERT INTO ttt1 VALUES ('y', 15);
INSERT INTO ttt1 VALUES ('z', 25);
CREATE OR REPLACE FUNCTION replace_vars (in_formula VARCHAR2)
RETURN VARCHAR2
IS
f VARCHAR2 (2000) := UPPER (in_formula);
BEGIN
FOR c1 IN ( SELECT UPPER (name) name, amount
FROM ttt1
ORDER BY name DESC)
LOOP
f := REPLACE (f, c1.name, c1.amount);
END LOOP;
return f;
END;
select replace_vars('x-y+z') from dual
Here's another way to approach the problem that attempts to do it all in SQL. While not necessarily the most flexible or fastest, maybe you can get some ideas from another way to approach the problem. It also shows a way to execute the final formula to get the answer. See the comments below.
Assumes all variables are present in the variable table.
-- First build the table that holds the values. You won't need to do
-- this if you already have them in a table.
with val_tbl(x, y, z) as (
select '10', '15', '20' from dual
),
-- Table to hold the formula.
formula_tbl(formula) as (
select 'x-y+z' from dual
),
-- This table is built from a query that reads the formula a character at a time.
-- When a variable is found using the case statement, it is queried in the value
-- table and it's value is returned. Otherwise the operator is returned. This
-- results in a row for each character in the formula.
new_formula_tbl(id, new_formula) as (
select level, case regexp_substr(formula, '(.|$)', 1, level, NULL, 1)
when 'x' then
(select x from val_tbl)
when 'y' then
(select y from val_tbl)
when 'z' then
(select z from val_tbl)
else regexp_substr(formula, '(.|$)', 1, level, NULL, 1)
end
from formula_tbl
connect by level <= regexp_count(formula, '.')
)
-- select id, new_formula from new_formula_tbl;
-- This puts the rows back into a single string. Order by id (level) to keep operands
-- and operators in the right order.
select listagg(new_formula) within group (order by id) formula
from new_formula_tbl;
FORMULA
----------
10-15+20
Additionally you can get the result of the formula by passing the listagg() call to the following xmlquery() function:
select xmlquery(replace( listagg(new_formula) within group (order by id), '/', ' div ')
returning content).getNumberVal() as result
from new_formula_tbl;
RESULT
----------
15

Sql Query With Select and IF

here is my table description:
Table Name : Orders
Columns : ID, NO, Quantity, Base, Code
First my query should check the value of Code, if value of Code is 'B' then OUTPUT VALUE should be Code+Base if it is not 'B' then OUTPUT VALUE should be Code+Quantity
the obtained rows again will be filtered by using where clause where ID='' and quantity=''
How can I form this complex query, any ideas ?...
Modified Answer
Something bothered me about my original post (see below, but do not use) so I went back to check. If Code is 'B' (therefore a varchar) and Quantity is an integer, then SQL will not let you add them for obvious reasons and will not do an implicit conversion for you (at least it did not do it for me). So I had to convert Quantity to a varchar to be able to use it.
Look at the following working code built on SQL Server 2008
DECLARE #MyTable TABLE
(
ID int identity (1, 1),
Num int,
Quantity int,
Base varchar (1),
Code varchar (1)
)
INSERT INTO #MyTable VALUES
(1, 1, 'a', 'A')
, (2, 2, 'b', 'B')
, (3, 3, 'c', 'C')
, (4, 4, 'd', 'D')
, (5, 5, 'e', 'E')
SELECT * FROM #MyTable
SELECT
CASE WHEN Code = 'B' THEN Code+Base
ELSE Code+CONVERT (VarChar, Quantity)
END AS OutputValue
FROM #MyTable
Original Answer (do not use)
try the following
SELECT CASE WHEN Code = 'B' THEN Code+Base ELSE Code+Quantity END AS OutputValue
FROM MyTable
WHERE ID = #Id
and Quantity = #Quantity
SELECT CASE Code WHEN 'B' THEN Code + Base ELSE Code + Quantity END As OutputValue
FROM Orders
WHERE ID = #id AND Quantity = #quantity
SELECT
(
CASE Code
WHEN CODE = 'B' THEN Code+Base
ELSE Code+Quantity
END
) AS OutputValue
FROM Orders
WHERE ID = '' AND Quantity=''