Retrieve only a portion of a string - sql-server-2008

I want to retrieve only the first part of a VARCHAR(15) string. Which string operation is fastest? My data is like this:
80:0:0:0
100:00:00:00
0:00:25:60
I'd like the results to be:
80
100
0

DECLARE #str TABLE(x VARCHAR(15));
INSERT #str VALUES ('80:0:0:0'), ('100:00:00:00'), ('0:00:25:60');
SELECT FirstPart = SUBSTRING(x, 1, CHARINDEX(':', x)-1) FROM #str;
Results:
FirstPart
---------
80
100
0
If you need to show the whole string even if it doesn't contain : then you can do this instead:
SELECT SUBSTRING(x, 1, COALESCE(NULLIF(CHARINDEX(':', x), 0), 15)) FROM #str;

Related

Add a digit to the end of an SQL result

I want the result to add number in the end in specific cases.
Like: if result is between 1 and 50, add 1.
If result is between 51 and 99, add 2 to the end.
If result is between 100 and 200, add 3 to the end.
Like:
Result = 25, do it 251.
Result 67, do it 672.
Result is 150, do it 1503.
I have created a table but the cases don't seem to work. How would I add a digit in specific cases?
CREATE TABLE Numbers(
Num INT
);
INSERT Numbers VALUES('12');
INSERT Numbers VALUES('112');
INSERT Numbers VALUES('12');
INSERT Numbers VALUES('122');
INSERT Numbers VALUES('1');
INSERT Numbers VALUES('2');
INSERT Numbers VALUES('12345678');
INSERT Numbers VALUES('12345');
SELECT * FROM Numbers;
SELECT RIGHT('15'+ CONVERT(VARCHAR,Num),6) AS NUM FROM Numbers;
SELECT LEFT(REPLICATE('0', 10) + CONVERT(VARCHAR, Num), 6) AS NUM FROM Numbers;
SELECT RIGHT('0' + CAST(Num AS VARCHAR(2)), 2) FROM Numbers
SELECT
CASE
WHEN Num BETWEEN 1 AND 99
THEN LEFT ('00' + CAST(Num AS VARCHAR(2)), 2)
ELSE
CAST(Num AS VARCHAR(10))
END
FROM Numbers
Since you're already using varchar on these values, I'd use concat - which simply mergs strings together. In this case you simply select what you want to merge, with what. Documentation on Concat() here.
Fiddle: https://www.db-fiddle.com/f/at2fqinuEao3b8coRSydTD/1
SELECT
CASE WHEN Num BETWEEN 1 AND 50
THEN concat(Num, '1')
WHEN Num BETWEEN 51 AND 99
THEN concat(Num, '2')
WHEN Num BETWEEN 100 AND 199
THEN concat(Num, '3')
ELSE Num END AS Num
FROM Numbers
In the examples of your 25,67 and 150 - this is the result:
Num
251
672
1503
You're working with numbers, so you can do Num*10 + 1 etc. Like this. fiddle
SELECT CASE WHEN Num BETWEEN 1 AND 50 THEN Num*10 + 1
WHEN Num BETWEEN 51 AND 99 THEN Num*10 + 2
WHEN Num BETWEEN 100 AND 199 THEN Num*10 + 3
ELSE Num END AS Num
FROM Numbers
That seems like it might be easier than string-casting and concatenating.
But you could do this if you really want strings. fiddle.
SELECT CASE WHEN Num BETWEEN 1 AND 50 THEN CONCAT(Num, '1')
WHEN Num BETWEEN 51 AND 99 THEN CONCAT(Num, '2')
WHEN Num BETWEEN 100 AND 199 THEN CONCAT(Num, '3')
ELSE CONVERT(Num, CHAR) END AS Num
FROM Numbers

How to auto increment a string with sql query

I am stuck at a point where i have to increment a string, and my strings are of type C001,SC001,B001
in my data base they are defined like
what i am trying to do do is write a query which check the previous highest code present into my db and the incriment it to +1
for example C001 -> C002,C009->C010,C099`->C100 and so on
Similarly for SC001->SC002,SC009->SC010,SC099->SC100 and so on
Similarly fro B001 -> B002,B009->B010,B099`->B100 and so on
I have a query which my friend has suggested me to use but that query only incriminating AAAA->AAAA01 , AAAA09->AAAA10
query is
SELECT id AS PrevID, CONCAT(
SUBSTRING(id, 1, 4),
IF(CAST(SUBSTRING(id, 5) AS UNSIGNED) <= 9, '0', ''),
CAST(SUBSTRING(id, 5) AS UNSIGNED) + 1
) AS NextID
FROM (
-- since you allow strings such as AAAA20 and AAAA100 you can no longer use MAX
SELECT id
FROM t
ORDER BY SUBSTRING(id, 1, 4) DESC, CAST(SUBSTRING(id, 5) AS UNSIGNED) DESC
LIMIT 1
) x
when i am replacing ID with CategoryCode it is giving me PrevID-C004 NextID-C00401 which is not my requirement i want PrevID-C004 and NextID->C005
NOTE i am using my sqlServer 5.1
Just try this one ,
SELECT
CategoryCode,CAST(CONCAT(LPAD(CategoryCode,1,0),LPAD(MAX(RIGHT(CategoryCode,
3)) + 1, 3, 0) ) AS CHAR),
FROM test
SELECT
SubCategoryCode,CAST(CONCAT(LPAD(SubCategoryCode,2,0),
LPAD(MAX(RIGHT(CategoryCode, 3)) + 1, 3, 0) ) AS CHAR),
FROM test
SELECT
BrandCode,CAST(CONCAT(LPAD(BrandCode,1,0), LPAD(MAX(RIGHT(BrandCode, 3)) +
1, 3, 0)) AS CHAR) FROM test

MySQL Select multiple columns group by sorted columns values

I have this table columns structure:
id - n1 - n2 - n3
And here it is with some dummy data:
id - n1 - n2 - n3
1 - 3 - 2 - 1
2 - 6 - 5 - 7
3 - 2 - 3 - 1
4 - 1 - 6 - 5
5 - 5 - 6 - 7
6 - 3 - 5 - 6
And the idea is to Select and count each unique distinct group of n1, n2 and n3 in sequence.
So, for example, we could get this result:
total - n1s - n2s - n3s
2 - 1 - 2 - 3
2 - 5 - 6 - 7
1 - 1 - 5 - 6
1 - 3 - 5 - 6
Can you help me set the state to achieve that??
I am trying to attempt that without multiple selects and PHP array sorting...
Thanks.
Consider the following - a normalised dataset...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,n INT NOT NULL
,val INT NOT NULL
,PRIMARY KEY(id,n)
);
INSERT INTO my_table VALUES
(1, 1, 3),
(1, 2, 2),
(1, 3, 1),
(2, 1, 6),
(2, 2, 5),
(2, 3, 7),
(3, 1, 2),
(3, 2, 3),
(3, 3, 1),
(4, 1, 1),
(4, 2, 6),
(4, 3, 5),
(5, 1, 5),
(5, 2, 6),
(5, 3, 7),
(6, 1, 3),
(6, 2, 5),
(6, 3, 6);
Here's a quick (to write) and dirty solution. Faster / more elegant solutions are available...
SELECT vals
, COUNT(*) total
FROM
( SELECT id
, GROUP_CONCAT(val ORDER BY val) vals
FROM my_table
GROUP
BY id
) x
GROUP
BY vals;
+-------+-------+
| vals | total |
+-------+-------+
| 1,2,3 | 2 |
| 1,5,6 | 1 |
| 3,5,6 | 1 |
| 5,6,7 | 2 |
+-------+-------+
We just need expressions to "sort" the values in columns n1, n2 and n3. If we have that, then we can do a simple GROUP BY and COUNT.
SELECT COUNT(1) AS total
, IF(t.n1<=t.n2,IF(t.n1<=t.n3,t.n1,t.n3),IF(t.n2<=t.n3,t.n2,t.n3)) AS n1s
, IF(t.n1<=t.n2,IF(t.n2<=t.n3,t.n2,IF(t.n1<=t.n3,t.n3,t.n1)),IF(t.n1<=t.n3,t.n1,IF(t.n2<=t.n3,t.n3,t.n2 ))) AS n2s
, IF(t.n1<=t.n2,IF(t.n2<=t.n3,t.n3,t.n2),IF(t.n1<=t.n3,t.n3,t.n1)) AS n3s
FROM this_table_column_structure t
GROUP BY n1s,n2s,n3s
ORDER BY total DESC, n1s, n2s, n3s
will return
total n1s n2s n3s
----- ---- ---- ----
2 1 2 3
2 5 6 7
1 1 5 6
1 3 5 6
As a first approach (if time permits), you should really consider normalizing your table, as suggested in #Strawberry's answer
However, a second approach allowing any number of columns (although inefficient due to String operations and Bubble Sorting) is possible, utilizing User Defined Functions.
We basically need to create a function, which can sort the values inside a comma separated string. I found a working function, which can do the sorting. Reproducing code from here:
-- sort comma separated substrings with unoptimized bubble sort
DROP FUNCTION IF EXISTS sortString;
DELIMITER |
CREATE FUNCTION sortString(inString TEXT) RETURNS TEXT
BEGIN
DECLARE delim CHAR(1) DEFAULT ','; -- delimiter
DECLARE strings INT DEFAULT 0; -- number of substrings
DECLARE forward INT DEFAULT 1; -- index for traverse forward thru substrings
DECLARE backward INT; -- index for traverse backward thru substrings, position in calc. substrings
DECLARE remain TEXT; -- work area for calc. no of substrings
-- swap areas TEXT for string compare, INT for numeric compare
DECLARE swap1 TEXT; -- left substring to swap
DECLARE swap2 TEXT; -- right substring to swap
SET remain = inString;
SET backward = LOCATE(delim, remain);
WHILE backward != 0 DO
SET strings = strings + 1;
SET backward = LOCATE(delim, remain);
SET remain = SUBSTRING(remain, backward+1);
END WHILE;
IF strings < 2 THEN RETURN inString; END IF;
REPEAT
SET backward = strings;
REPEAT
SET swap1 = SUBSTRING_INDEX(SUBSTRING_INDEX(inString,delim,backward-1),delim,-1);
SET swap2 = SUBSTRING_INDEX(SUBSTRING_INDEX(inString,delim,backward),delim,-1);
IF swap1 > swap2 THEN
SET inString = TRIM(BOTH delim FROM CONCAT_WS(delim
,SUBSTRING_INDEX(inString,delim,backward-2)
,swap2,swap1
,SUBSTRING_INDEX(inString,delim,(backward-strings))));
END IF;
SET backward = backward - 1;
UNTIL backward < 2 END REPEAT;
SET forward = forward +1;
UNTIL forward + 1 > strings
END REPEAT;
RETURN inString;
END |
DELIMITER ;
You will need to run this code on your MySQL server, so that this function is available within a query, just like native built-in MySQL functions. Now, the querying part becomes simple. All you need to do is Concat_ws() all the number columns using comma. And, then apply sortString() function on the concatenated string. Eventually, use the "ordered" string in Group By clause, to get the desired result.
Try:
SELECT sortString(CONCAT_WS(',', n1, n2, n3)) AS n_sequence -- add more columns here
COUNT(id) AS total
FROM your_table
GROUP BY n_sequence
ORDER BY total DESC
Now I suggest that you can use your application code to change comma separated n_sequence back to tabular column display.

PostgreSQL function with a loop

I'm not good at postgres functions. Could you help me out?
Say, I have this db:
name | round |position | val
-----------------------------------
A | 1 | 1 | 0.5
A | 1 | 2 | 3.4
A | 1 | 3 | 2.2
A | 1 | 4 | 3.8
A | 2 | 1 | 0.5
A | 2 | 2 | 32.3
A | 2 | 3 | 2.21
A | 2 | 4 | 0.8
I want to write a Postgres function that can loop from position=1 to position=4 and calculate the corresponding value. I could do this in python with psycopg2:
import psycopg2
import psycopg2.extras
conn = psycopg2.connect("host='localhost' dbname='mydb' user='user' password='pass'")
CURSOR = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cmd = """SELECT name, round, position, val from mytable"""
CURSOR.execute(cmd)
rows = CURSOR.fetchall()
dict = {}
for row in rows:
indx = row['round']
try:
dict[indx] *= (1-row['val']/100)
except:
dict[indx] = (1-row['val']/100)
if row['position'] == 4:
if indx == 1:
result1 = dict[indx]
elif indx == 2:
result2 = dict[indx]
print result1, result2
How can I do the same thing directly in Postgres so that it returns a table of (name, result1, result2)
UPDATE:
#a_horse_with_no_name, the expected value would be:
result1 = (1 - 0.5/100) * (1 - 3.4/100) * (1 - 2.2/100) * (1 - 3.8/100) = 0.9043
result2 = (1 - 0.5/100) * (1 - 32.3/100) * (1 - 2.21/100) * (1 - 0.8/100) = 0.6535
#Glenn gave you a very elegant solution with an aggregate function. But to answer your question, a plpgsql function could look like this:
Test setup:
CREATE TEMP TABLE mytable (
name text
, round int
, position int
, val double precision
);
INSERT INTO mytable VALUES
('A', 1, 1, 0.5)
, ('A', 1, 2, 3.4)
, ('A', 1, 3, 2.2)
, ('A', 1, 4, 3.8)
, ('A', 2, 1, 0.5)
, ('A', 2, 2, 32.3)
, ('A', 2, 3, 2.21)
, ('A', 2, 4, 0.8)
;
Generic function
CREATE OR REPLACE FUNCTION f_grp_prod()
RETURNS TABLE (name text
, round int
, result double precision)
LANGUAGE plpgsql STABLE AS
$func$
DECLARE
r mytable%ROWTYPE;
BEGIN
-- init vars
name := 'A'; -- we happen to know initial value
round := 1; -- we happen to know initial value
result := 1;
FOR r IN
SELECT *
FROM mytable m
ORDER BY m.name, m.round
LOOP
IF (r.name, r.round) <> (name, round) THEN -- return result before round
RETURN NEXT;
name := r.name;
round := r.round;
result := 1;
END IF;
result := result * (1 - r.val/100);
END LOOP;
RETURN NEXT; -- return final result
END
$func$;
Call:
SELECT * FROM f_grp_prod();
Result:
name | round | result
-----+-------+---------------
A | 1 | 0.90430333812
A | 2 | 0.653458283632
Specific function as per question
CREATE OR REPLACE FUNCTION f_grp_prod(text)
RETURNS TABLE (name text
, result1 double precision
, result2 double precision)
LANGUAGE plpgsql STABLE AS
$func$
DECLARE
r mytable%ROWTYPE;
_round integer;
BEGIN
-- init vars
name := $1;
result2 := 1; -- abuse result2 as temp var for convenience
FOR r IN
SELECT *
FROM mytable m
WHERE m.name = name
ORDER BY m.round
LOOP
IF r.round <> _round THEN -- save result1 before 2nd round
result1 := result2;
result2 := 1;
END IF;
result2 := result2 * (1 - r.val/100);
_round := r.round;
END LOOP;
RETURN NEXT;
END
$func$;
Call:
SELECT * FROM f_grp_prod('A');
Result:
name | result1 | result2
-----+---------------+---------------
A | 0.90430333812 | 0.653458283632
I guess you are looking for an aggregate "product" function. You can create your own aggregate functions in Postgresql and Oracle.
CREATE TABLE mytable(name varchar(32), round int, position int, val decimal);
INSERT INTO mytable VALUES('A', 1, 1, 0.5);
INSERT INTO mytable VALUES('A', 1, 2, 3.4);
INSERT INTO mytable VALUES('A', 1, 3, 2.2);
INSERT INTO mytable VALUES('A', 1, 4, 3.8);
INSERT INTO mytable VALUES('A', 2, 1, 0.5);
INSERT INTO mytable VALUES('A', 2, 2, 32.3);
INSERT INTO mytable VALUES('A', 2, 3, 2.21);
INSERT INTO mytable VALUES('A', 2, 4, 0.8);
CREATE AGGREGATE product(double precision) (SFUNC=float8mul, STYPE=double precision, INITCOND=1);
SELECT name, round, product(1-val/100) AS result
FROM mytable
GROUP BY name, round;
name | round | result
------+-------+----------------
A | 2 | 0.653458283632
A | 1 | 0.90430333812
(2 rows)
See "User-Defined Aggregates" in the Postgresql doc. The example above I borrowed from
here. There are other stackoverflow responses that show other methods to do this.

How to get an ordered list of rows within 0 value with sql?

For example:
SELECT * FROM atable ORDER BY num;
'atable' is:
num name
1 a
3 y
0 cc
2 fs
The result is:
num name
1 a
2 fs
3 y
0 cc
But I want it to be:
num name
0 cc
1 a
2 fs
3 y
I can't reproduce the result you are seeing. The query that you posted should work as you wish it to. Here's my steps to reproduce:
CREATE TABLE atable (num INT NOT NULL, name NVARCHAR(100) NOT NULL);
INSERT INTO atable (num, name) VALUES
(1, 'a'),
(3, 'y'),
(0, 'cc'),
(2, 'fs');
SELECT * FROM atable ORDER BY num;
Result:
0, 'cc'
1, 'a'
2, 'fs'
3, 'y'
Perhaps you could post your create scripts for your table and test data in your question so that we can reproduce your result?
Are you sure that the 0 isn't a null value being displayed as a 0? Nulls can sort either at the top or the bottom, depending on database setting.
SELECT * FROM atable
ORDER BY ISNULL(CAST(num as int), 0);