Natural sort with SQL Server - sql

I want to convert the order of the values with column name PTNT_VST_CSNO from the following :
VMIP1
VMIP10
VMIP11
VMIP2
VMIP20
VMIP21
VMIP3
VMIP31
VMIP32
VMIP5
VMIP6
VMIP7
VMIP8
VMIP9
VMOP10
VMOP11
VMOP12
VMOP3
VMOP30
VMOP31
VMOP32
VMOP4
VMOP40
VMOP41
VMOP42
VMOP43
VMOP7
VMOP70
VMOP71
VMOP8
VMOP9
to:
VMIP1
VMIP2
VMIP3
VMIP5
VMIP6
VMIP7
VMIP8
VMIP9
VMIP10
VMIP11
VMIP20
VMIP21
VMIP31
VMIP32
VMOP3
VMOP4
VMOP7
VMOP8
VMOP9
VMOP10
VMOP11
VMOP12
VMOP30
VMOP31
VMOP32
VMOP40
VMOP41
VMOP42
VMOP43
VMOP70
VMOP71
I want to sort the numeric part of 'vmip' first then that of 'vmop'.. I tried a lot but failed every time. kindly help me guys to sort out the sorting problem... thank you in advance

Not the fastest thing in the world, but it should get the job done:
ORDER BY CASE WHEN PTNT_VST_CSNO LIKE 'vmi%' THEN 0 ELSE 1 END
,CAST(replace(replace(PTNT_VST_CSNO, 'vmip', ''), 'vmop', '') as int)

After great trial, I succeeded to solve it with the following way..
SELECT ptnt_vst_csno
FROM table_name
ORDER BY Substring(ptnt_vst_csno, 1, Charindex('P', ptnt_vst_csno)),
CONVERT(INT, Substring(Substring(ptnt_vst_csno,
Charindex('P', ptnt_vst_csno),
Len(
ptnt_vst_csno)), 2, Len(
ptnt_vst_csno)))

the easiest way to accomplish this would be to change your numering to 3 digits.
i.e. 1 would become 001, 2 would become 002, 10 would become 010, and so on...
this would then allow you to order the data correctly.
you might want to do something like this:
SELECT
PTNT_VST_CSNO,
'VMIP' + CASE LEN(REPLACE(PTNT_VST_CSNO, 'VMIP', ''))
WHEN 1 THEN '00' + REPLACE(PTNT_VST_CSNO, 'VMIP', '')
WHEN 2 THEN '0' + REPLACE(PTNT_VST_CSNO, 'VMIP', '')
ELSE REPLACE(PTNT_VST_CSNO, 'VMIP', '')
END
FROM
TableName
WHERE
LEFT(PTNT_VST_CSNO, 4) = 'VMIP'
ORDER BY
2

Related

In memory queries using SQL Server

I'm afraid that even though I am using CTE's in my query, that maybe, behind the scenes, a lot of disk caching is going on -- so it may as well not be using CTE's.
The whole point of using CTE's was that my original query code was way too slow, and would eventually get a transport level error and crash.
Well, it's still too slow. Maybe even slower. I don't know yet.
Is there a way to tell SQL Server to go ahead and be resource hog for my query?
I am only guessing, but I think it is using disk space to cache memory results. When I look at task manager memory utilization, I see SSMS at 161 MB. SSMS is where I am running the query from.
Here is my code - you don't have to read it in detail, but in brief, the source table contains about a million rows.
I need a solution, so alternative ideas are welcome...
WITH MetEdFliers AS
(
SELECT DISTINCT
[CustomerName1], [Mailing_Address], [Mailing_Address2], [Mailing_Zip]
FROM
[dbo].[_MetEd_Detail]
WHERE
RunId = (SELECT RunId FROM LastLoadRuns WHERE UtilityId = 9)
AND [Profitable] = 1 -- and not low income, should flag exist
),
MetEdLookUpFirst AS
(
-- same as [dbo].[VW_MetEd_Master_Profitable_ExcludeBadAddress]
SELECT
IIF (DET.IncalculableMailAddress = 1,
IIF (AA.Address1 IS NULL, 'Bad Address Undefined Fix -- Source Address Provided', 'Fixed Bad Source Address Via Lookup'), '') AS AddressStatus,
DET.ACCT_NO,
(CAST(DET.Monthly1 as Decimal) +
CAST(DET.Monthly2 as Decimal) +
CAST(DET.Monthly3 as Decimal) +
CAST(DET.Monthly4 as Decimal) +
CAST(DET.Monthly5 as Decimal) +
CAST(DET.Monthly6 as Decimal) +
CAST(DET.Monthly7 as Decimal) +
CAST(DET.Monthly8 as Decimal) +
CAST(DET.Monthly9 as Decimal) +
CAST(DET.Monthly10 as Decimal) +
CAST(DET.Monthly11 as Decimal) +
CAST(DET.Monthly12 as Decimal)) AS BilledKWHTotal,
DET.Polar, DET.CustomerName1,
REPLACE (IIF (DET.IncalculableMailAddress = 1,
IIF (AA.Address1 IS NULL, DET.Mailing_Address, AA.Address1), DET.Mailing_Address), ',', ';') AS Address1,
REPLACE (IIF (DET.IncalculableMailAddress = 1,
IIF (AA.Address2 IS NULL, DET.Mailing_Address2, AA.Address2), DET.Mailing_Address2), ',', ';') AS Address2,
REPLACE (IIF (DET.IncalculableMailAddress = 1,
IIF (AA.City IS NULL, DET.Mailing_City, AA.City), DET.Mailing_City), ',', ';') AS City,
IIF (DET.IncalculableMailAddress = 1,
IIF (AA.[State] IS NULL, DET.Mailing_State, AA.[State]), DET.Mailing_State) AS [State],
IIF (DET.IncalculableMailAddress = 1,
IIF (AA.Zip IS NULL, DET.Mailing_Zip, AA.Zip), DET.Mailing_Zip) AS ZIP,
IIF (DET.IncalculableMailAddress = 1, '', DET.Mailing_Zip4) AS ZIP4,
REPLACE (DET.Address, ',', ';') AS ServiceAddress,
REPLACE (DET.City, ',', ';') AS ServiceAddressCity,
DET.State ASs ServiceAddressState,
DET.Zip AS ServiceAddressZip,
DET.Zip4 AS ServiceAddressZip4,
DET.ProfitAnnualPotential AS [Potential Annual Profit]
FROM
_MetEd_DETAIL DET
LEFT JOIN
AccountAddress AA ON (DET.ACCT_NO = AA.ACCT_NO AND AA.UtilityId = 9)
WHERE
RunId = (SELECT RunId FROM LastLoadRuns WHERE UtilityId = 9)
AND DET.Profitable = 1 --AND det.CAP_CUSTOMER = 0
AND (DET.IncalculableMailAddress = 0 OR (AA.Address1 IS NOT NULL))
)
SELECT X.*
FROM MetEdFliers Fliers
OUTER APPLY
(SELECT TOP 1 *
FROM MetEdLookUpFirst LU
WHERE LU.CustomerName1 = Fliers.CustomerName1
AND LU.Address1 = Fliers.Mailing_Address
AND LU.Address2 = Fliers.Mailing_Address2
AND LU.Zip = Fliers.Mailing_Zip) X
It looks to be difficult.
I am going to handle it in my source program that generates input files for the sql server database ( i real the cvs into a table using import ).
To handle this problem, I am going to use a technology called dictionary, with key value pair, in c#.
I will be able to tell if the key had been added before, and if so, I replace the key value pair with the new key value pair and the annual profit potential field with the sum from both records....
Note: Prior method, I did not have sum (enhancement).

Parse data, single to multiple

have a single column Varchar(2000).
Data looks like in a single column,
12:10:08: Dialing12:10:08: Connecting12:10:08: ABC: abc:9433769781$100.88.77.0:878712:10:08: ABCD: 000012:10:09: Agent Initializing12:10:25: On Call12:10:25: Assigned to operator12:10:25: Waiting for Supervisor12:10:30: Waiting for Manager12:11:30: Call Ended12:11:30: Call Not connected..
I want to parse it like,
12:10:08: Dialing
12:10:08: Connecting
12:10:08: ABC: abc:9433769782$100.88.77.0:8787
12:10:08: ABCD: 0000
12:10:25: Agent Initializing
12:10:18: On Call
12:10:25: Assigned to operator
12:10:30: Waiting for Supervisor
12:10:30: Waiting for Manager
12:11:30: Call Ended
12:11:30: Call Not connected
Any help. Searched the complete forum, but I am really unsure about this, particularly an absence of a specific identifier. Appreciate your help.
P/S- This is just an example of a single time,time is not constant.
Yuck. But, you can do this with a recursive CTE. Here is how:
with t as (
select '12:10:08: Dialing12:10:08: Connecting12:10:08: ABC: abc:9433769781$100.88.77.0:878712:10:08: ABCD: 000012:10:09: Agent Initializing12:10:25: On Call12:10:25: Assigned to operator12:10:25: Waiting for Supervisor12:10:30: Waiting for Manager12:11:30: Call Ended12:11:30: Call Not connected.. ' as col
),
cte as (
select left(t.col, 9 + patindex('%[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %', substring(t.col, 11, 1000))) as val,
substring(t.col, 10 + patindex('%[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %', substring(t.col, 11, 1000)), 1000) as rest
from t
where t.col like '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %'
union all
select (case when rest like '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %'
then left(rest, 9 + patindex('%[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %', substring(rest, 11, 1000)))
else rest
end) as val,
substring(rest, 10 + patindex('%[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %', substring(rest, 11, 1000)), 1000) as rest
from cte
where rest like '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]: %'
)
select val
from cte;
The SQL Fiddle is here.
Alternative;
DECLARE #string VARCHAR(1024) = '12:10:08: Dialing12:10:08: Connecting12:10:08: ABC: abc:9433769781$100.88.77.0:878712:10:08: ABCD: 000012:10:09: Agent Initializing12:10:25: On Call12:10:25: Assigned to operator12:10:25: Waiting for Supervisor12:10:30: Waiting for Manager12:11:30: Call Ended12:11:30: Call Not connected'
WITH T(last, pos) AS(
SELECT 0, 1
UNION ALL
SELECT pos, pos + PATINDEX('%[0-9][0-9]:[0-9][0-9]:[0-9][0-9]%', SUBSTRING(#string, pos + 1, LEN(#string)))
FROM T
WHERE pos != last
)
SELECT SUBSTRING(#string, last, CASE WHEN pos = last THEN len(#string) ELSE pos - last END)
FROM T
WHERE LAST > 0
For
(No column name)
12:10:08: Dialing
12:10:08: Connecting
12:10:08: ABC: abc:9433769781$100.88.77.0:8787
12:10:08: ABCD: 0000
12:10:09: Agent Initializing
12:10:25: On Call
12:10:25: Assigned to operator
12:10:25: Waiting for Supervisor
12:10:30: Waiting for Manager
12:11:30: Call Ended
12:11:30: Call Not connected
I know you are asking for an SQL solution but I am not sure that is possible without a while loop and extensive string manipulation which is quite inefficient.
If you are happy to bring the original varchar down to the BLL level, you can do it here using a regular expression. As I assume you want to do this in order to output to screen or log file then this should be possible.
For example:
Replace
/([0-9]{2}:[0-9]{2}:[0-9]{2}: ).*/gU
with
\n/1
Example:
http://regex101.com/r/eM4vD5/1

Conditional WHERE clause with CASE statement in Oracle

I'm brand-new to the Oracle world so this could be a softball. In working with an SSRS report, I'm passing in a string of states to a view. The twist is that the users could also pick a selection from the state list called "[ No Selection ]" ... (that part was not by doing and I'm stuck with implementing things this way)
If they choose the No Selection option, then I just want to return all states by default, otherwise return just the list of states that are in my comma-separated list.
This really seems like it should be easy but I'm stuck. Here is the code I have so far (just trying to get a sample to work) but my eyes have finally gone crossed trying to get it going.
Could anybody give me some direction on this one please?
begin
:stateCode:='MO,FL,TX';
--:stateCode:='[ No Selection ]';
end;
/
select count(*) as StateCount, :stateCode as SelectedVal
from hcp_state vw
where
(case
when (:stateCode = '') then (1)
when (:stateCode != '') then (vw.state_cd in (:stateCode))
(else 0)
end)
;
You can write the where clause as:
where (case when (:stateCode = '') then (1)
when (:stateCode != '') and (vw.state_cd in (:stateCode)) then 1
else 0)
end = 1;
Alternatively, remove the case entirely:
where (:stateCode = '') or
((:stateCode != '') and vw.state_cd in (:stateCode));
Or, even better:
where (:stateCode = '') or vw.state_cd in (:stateCode)
We can use a CASE statement in WHERE clause as:
SELECT employee_no, name, department_no
FROM emps
WHERE (CASE
WHEN :p_dept_no = 50 THEN 0
WHEN :p_dept_no = 70 THEN 0
ELSE -1
END) = 0;

How to create a concatenated string of row values based on flags in SQL Server

I am using SQL Server 2008r2.
Here is what I am trying to accomplish:
I have a table with the design:
Flag Text
________________________
0 'No Error'
1 'Bad Data'
2 'Bad Header'
4 'Unknown error'
My second table is designed:
ID Flags
_______________________
500 0
501 3
502 4
504 6
550 0
The flags in the second table represent a bitwise combination of the flags in the first table (e.g. Flags = 3 is 'Bad Data' AND 'Bad Header', Flags = 6 is 'Bad Header' AND 'Unknown error').
I want a query that will produce the following:
ID ConcatText
____________________________
500 'No Error'
501 'Bad Data, Bad Header'
502 'Unknown error'
504 'Bad Header, Unknown error'
550 'No Error'
What is the best way to achieve this without the use of user-defined functions, or user-defined stored procedures?
Thanks for any help.
This article explains exactly how to accomplish this. It puts it together step by step so that you understand what is going on, too. It basically combines the bitwise operators in SQL, and then the rest is accomplished similar to what hkf posted. Hopefully, this is helpful to you :)
I believe this will translate out to be:
SELECT a.id,
REPLACE(REPLACE(REPLACE(
(
SELECT TEXT
FROM FlagTable AS b
WHERE a.flags & b.flag <> 0
ORDER BY b.text FOR XML Raw
)
, '"/><row value="', ', '), '<row value="', ''), '"/>', '')
AS 'attributes'
FROM FlagMappingTable AS a
ORDER BY a.id;
You need a combination of CROSS APPLY and FOR_XML_PATH()
See Simulating group_concat MySQL function in Microsoft SQL Server 2005?
Oh I LOVE bitwise, truely, not sarcasm. I think this is the simplest. You've got CTE's available to you, I say use 'em!
Try this. I borrowed from Concatenate many rows into a single text string? with my own flavor of a join for bitwise.
*I apologize for mistakes, this is untested and written in Notepad.
WITH lines AS
(
SELECT
row_number() over(order by ID) lineid,
FlagMap.ID
, Flag.Text AS ConcatText
FROM
FlagMap
LEFT JOIN
Flags
ON FlagMap.Flags & Flags.Flag = Flags.Flag
OR (FlagMap.Flags = 0 AND Flag.Flag = 0)
),
result_lines AS
(
SELECT
lineid,
cast(ConcatText as nvarchar(max)) ConcatText
FROM
lines
WHERE
lineid = 1
UNION ALL
SELECT
l.lineid,
cast(r.ConcatText + N', ' + l.ConcatText AS nvarchar(max))
FROM
lines l
INNER JOIN
result_lines r
on
l.lineid = r.lineid + 1
)
SELECT
ID
, ConcatText
FROM
result_lines
ORDER BY
ID DESC

Splitting a Path Enumeration Model PathString in MySQL

I'm trying to implement a Path Enumeration model as per Joe Celko's book (page 38). The relevant attributes of my table (and the support table that just contains sequential integers) look like this:
Contribution
------------
ContributionID
PathString
_IntegerSeries
--------------
IntegerID
_IntegerSeries contains integers 1 to n where n is bigger than I'll ever need. Contribution contains three records:
1 1
2 12
3 123
... and I use a modified version of Joe's query:
SELECT SUBSTRING( c1.PathString
FROM (s1.IntegerID * CHAR_LENGTH(c1.ContributionID))
FOR CHAR_LENGTH(c1.ContributionID)) AS ContID
FROM
Contribution c1, _IntegerSeries s1
WHERE
c1.ContributionID = 3
AND s1.IntegerID <= CHAR_LENGTH(c1.PathString)/CHAR_LENGTH(c1.ContributionID);
... to successfully return a result set containing all of ContributionID 3's superiors in the hierarchy. Now, in this example, the PathString column holds plain integer values and obviously we run into trouble once we hit ContributionID 10. So we modify the PathString column to include separators:
1 1.
2 1.2.
3 1.2.3.
Now... the book doesn't give an example of getting superiors when the PathString uses delimiters... so I'll have to figure that out later. But it does give an example for how to split up a PathString (which I'm guessing is going to help me do superior searches). The MySQL version of the example code to do this is:
SELECT SUBSTRING( '.' || c1.PathString || '.'
FROM s1.IntegerID + 1
FOR LOCATE('.', '.' || c1.PathString || '.', s1.IntegerID + 1) - s1.IntegerID - 1) AS Node
FROM _IntegerSeries s1, Contribution c1
WHERE
SUBSTRING('.' || c1.PathString || '.' FROM s1.IntegerID FOR 1) = '.'
AND IntegerID < CHAR_LENGTH('.' || c1.PathString || '.');
... but this code returns an empty result set. I'm doing something wrong, but I'm not sure what. Figured I'd put this out to the stackoverflow community prior to bothering Joe with an email. Anyone have any thoughts?
UPDATE
Quassnoi's query... slightly modified a bit after testing, but exactly the same as his original functionally. Very nice. Much cleaner than what I was using. Big thanks.
SET #contributionID = 3;
SELECT ca.*
FROM
Contribution c INNER JOIN _IntegerSeries s
ON s.IntegerID < #contributionID AND SUBSTRING_INDEX(c.PathString, '.', s.IntegerID) <> SUBSTRING_INDEX(c.PathString, '.', s.IntegerID + 1)
INNER JOIN Contribution ca
ON ca.PathString = CONCAT(SUBSTRING_INDEX(c.PathString, '.', s.IntegerID), '.')
WHERE c.ContributionID = #contributionID;
This is because || in MySQL is boolean OR, not string concatenation.
To find all ancestors of a given Contribution, use:
SELECT ca.*
FROM Contribution с
JOIN IntegerSeries s
ON IntegerID < CHAR_LENGTH(c.path)
AND SUBSTRING_INDEX(c.path, '.', IntegerID) <> SUBSTRING_INDEX(c.path, '.', IntegerID + 1)
JOIN Contribution ca
ON ca.path = CONCAT(SUBSTRING_INDEX(c.path, '.', IntegerID), '.')
WHERE c.ContributionID = 3