SQL: Test if Number Contains Digits of Another Number - sql

I'm interested in using SQL to test if one number, stored in column DOW, contains the digits of another number, also stored in DOW, regardless of separation by other digits. Here are the current numbers with which I'm working, although I may have more to deal with in the future:
23
236
1234
12346
123456
67
If the query checks 123456 against 236, it needs to return true. Vice versa, 236 against 123456 returns false. Another example is 1234 returns true when checked against 23, but 67 returns false when checked against 12346. If I've not provided enough information in this question, please ask for clarification.
Simplified Version of my Query:
SELECT t1.DOW, t2.DOW
FROM table t1, table t2
WHERE /* t2.DOW contains all digits regardless of separation in t1.DOW */
Thanks!

I assume DOW is Day of Week and only has numbers 1-7
SELECT T1.DOW,T2.DOW
FROM T1,T2
WHERE NOT EXISTS(SELECT *
FROM (VALUES('%1%'),
('%2%'),
('%3%'),
('%4%'),
('%5%'),
('%6%'),
('%7%')) Nums(N)
WHERE (T2.DOW LIKE N AND T1.DOW NOT LIKE N))
SQL Server 2008 answer though AFAIK this is standard SQL.

This can be handled as a stored procedure which turns a value into a string and inspects the string for each of the digits in the pattern value.
The guts of the stored procedure would have something like
function has_digits (pattern, value)
string s = string (value)
count = 0
for each char in pattern
if instr (s, char) > 0 // for mysql
count = count + 1
return count == pattern.length()
(The specifics of this greatly depend on which SQL flavor is used.)
This would be used like
SELECT *
FROM sometable
WHERE has_digits (236, somefield);

First, I want to make sure everybody understands that I firmly believe this should be in a stored procedure. Also, that I will probably spend most of tomorrow trying to figure out a suitable punishment for what I'm about to write.
Let's assume we have two tables. The first table contains the digits we want to search for.
CREATE TABLE search_digits (
digit integer NOT NULL
);
INSERT INTO search_digits VALUES (2), (3);
We're going to search for the digits 2 and 3.
The second table contains the values we want to search within.
CREATE TABLE dow_digits (
dow integer NOT NULL,
digit integer NOT NULL,
CONSTRAINT dow_digits_pkey PRIMARY KEY (dow, digit),
);
INSERT INTO dow_digits VALUES (23, 2);
INSERT INTO dow_digits VALUES (23, 3);
INSERT INTO dow_digits VALUES (236, 2);
INSERT INTO dow_digits VALUES (236, 3);
INSERT INTO dow_digits VALUES (236, 6);
INSERT INTO dow_digits VALUES (1234, 1);
INSERT INTO dow_digits VALUES (1234, 2);
INSERT INTO dow_digits VALUES (1234, 3);
INSERT INTO dow_digits VALUES (1234, 4);
INSERT INTO dow_digits VALUES (12346, 1);
INSERT INTO dow_digits VALUES (12346, 2);
INSERT INTO dow_digits VALUES (12346, 3);
INSERT INTO dow_digits VALUES (12346, 4);
INSERT INTO dow_digits VALUES (12346, 6);
INSERT INTO dow_digits VALUES (123456, 1);
INSERT INTO dow_digits VALUES (123456, 2);
INSERT INTO dow_digits VALUES (123456, 3);
INSERT INTO dow_digits VALUES (123456, 4);
INSERT INTO dow_digits VALUES (123456, 5);
INSERT INTO dow_digits VALUES (123456, 6);
INSERT INTO dow_digits VALUES (67, 6);
INSERT INTO dow_digits VALUES (67, 7);
We can find at least some of the values for dow that contain the digits 2 and 3 with a simple query.
select d1.dow from dow_digits d1
inner join search_digits d2 on d1.digit = d2.digit
group by dow
having count(distinct d1.digit) = (select count(distinct digit)
from search_digits);
dow
--
23
236
1234
12346
123456
That seems to work. It's not clear what the OP expects if the search integer is 233, so I'm going to ignore that case for now. I want to finish this quickly, then step in front of a truck.
The next question is, can we build search_digits on the fly? In PostgreSQL, sort of.
SELECT UNNEST(ARRAY[2,3]) as digit;
digit
--
2
3
Drop the table search_digits, and wrap that in a CTE.
with search_digits as (
select unnest(array[2,3]) as digit
)
select d1.dow from dow_digits d1
inner join search_digits d2 on d1.digit = d2.digit
group by dow
having count(distinct d1.digit) = (select count(distinct digit)
from search_digits);
dow
--
23
236
1234
12346
123456
Next question. Can we build dow_digits on the fly? In PostgreSQL, sort of. Need to know how many digits in the longest number. Let's say no more than six.
select dow, digit
from (select dow, unnest(array[substring((dow)::text from 1 for 1),
substring((dow)::text from 2 for 1),
substring((dow)::text from 3 for 1),
substring((dow)::text from 4 for 1),
substring((dow)::text from 5 for 1),
substring((dow)::text from 6 for 1)]) digit
from dow ) d
where d.digit <> '';
dow digit
--
23 2
23 3
236 2
236 3
236 6
1234 1
1234 2
1234 3
1234 4
12346 1
12346 2
12346 3
12346 4
12346 6
123456 1
123456 2
123456 3
123456 4
123456 5
123456 6
67 6
67 7
233 2
233 3
233 3
Pulling all that together into a single statement . . .
with search_digits as (
select unnest(array[1,2,3,4,6]) digit
)
select dow
from (select dow, digit
from (select dow, unnest(array[substring((dow)::text from 1 for 1),
substring((dow)::text from 2 for 1),
substring((dow)::text from 3 for 1),
substring((dow)::text from 4 for 1),
substring((dow)::text from 5 for 1),
substring((dow)::text from 6 for 1)]) digit
from dow
) arr
where arr.digit <> ''
) d
inner join (select distinct digit from search_digits) sd
on sd.digit = d.digit::integer
group by dow
having count(distinct d.digit) = (select count(distinct digit)
from search_digits)
dow
--
12346
123456
Oh, I can feel karma points slipping away . . . where's that truck?

For MySQL only:
CREATE TABLE num --- a help table
( i INT PRIMARY KEY
) ;
INSERT INTO num
VALUES
(1), (2), (3), (4)
, (5), (6), (7), (8)
, (9),(10),(11),(12)
,(13),(14),(15),(16)
,(17),(18),(19),(20) ; --- maximum number of digits = 20
Then, you can use this (horrible) query, which first converts numbers to strings and add % between each digit (23 would be converted to '%2%3%') and then tests WHERE 23456 LIKE '%2%3% to match if 23456 contains 23:
SELECT t1.DOW
, t2.DOW
, (t1.DOW LIKE test) AS t1_contains_t2
FROM t1
JOIN
( SELECT t2.DOW
, CONCAT( '%'
, GROUP_CONCAT( SUBSTRING( t2.DOW, num.i, 1 )
ORDER BY num.i
SEPARATOR '%' )
, '%'
) AS test
FROM t2
JOIN num
ON (SUBSTRING(t2.DOW,num.i,1) != "" )
GROUP BY t2.DOW
) AS temp ;
The t1_contains_t2 column will have 1 or 0 depending on whether t1.DOW contains t2.DOW
Or you can use WHERE (t1.DOW LIKE test) in the query.

Related

calculation of points for each student

I want to calculate the remaining points.
in calculation formula : total points =TotalPoints - plenty + Bonus
total points 100
eg. 1
st_id
plenty
Bonus
remaining Points
1
5
0
95
1
3
1
93
1
2
0
91
1
3
2
90
2
1
0
99
2
3
1
96
2
2
0
94
first row calculation for st_id 1: 100 -5 +0= 95
second row calculation for st_id1: 95 - 3 +1=93 here i have to take the previous remaining points. and so on...
can i make a function or use cte to calculate remaining point.
or any other solution for this problem.
SQL tables represent unordered sets (technically "multisets"). There is no ordering unless a column specifies the ordering.
Your results depend on an ordering, so let me assume that you have such a column.
The solution is then cumulative sums:
select t.*,
100 - sum(plenty - bonus) over (partition by st_id order by <ordering col>) as remaining
from t;
I am bit confused, how did you get 100 for the first row in formula "first row calculation for st_id 1: 100 -5 +0= 95" and 95 for second formula "st_id1: 95 - 3 +1=93". Finally, I anticipated that, you might have a column named TotalPoint in your table, so, I write a code. Please Check and let me know->
DECLARE #Points table(st_id int,plenty int,Bonus int,TotalPoints int);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 5,0,100);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 3,1,95);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 2,0,91);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 3,0,90);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (2, 1,0,99);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (2, 3,1,96);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (2, 2,0,94);
Select *,(TotalPoints-plenty-Bonus) AS RemainingPoint FROM #Points

SQL Server - break down a payment correctly

This is the problem I am having
Example:
Total Value: £1550.00
Monthly Payments 12
This calculates at 129.16666666666666667, however you wouldn't show a money value like this, it would display as £129.17, which equals £1550.04, which is wrong
Question
Is it possible to remove the float/decimal value from 11 of the 12 installments , and only display it in the first or final payment?
Example Result
PaymentNumber Value
1 £129.00
2 £129.00
3 £129.00
4 £129.00
5 £129.00
6 £129.00
7 £129.00
8 £129.00
9 £129.00
10 £129.00
11 £129.00
12 £131.00
thanks for any help, and any suggestions of another way I could do this would be very grateful..
I have included the code for a table i am using to test this... and also added in the data below. for now using an update statement will be acceptable and i will try to make the code more efficient at a later date. I do have a procedure running that inserts the monthly breakdowns programmatically.
Table info I am using
CREATE TABLE CustomerFinance (CustomerFinanceID int identity (1,1) not null,
TotalValueOwed decimal(12,4), --1550.00
LengthOfContract int) --LENGTGH IN MONTHS, i.e. 12
CREATE TABLE CustomerFinanceLine (CustomerFinanceLineID int identity (1,1) not null,
CustomerFinanceID int, --FOREIGN KEY LINK
PaymentNumber int, --1, 2, 3 AND SO ON
PaymentValue decimal(12,4)) --THE MONTHLY BREAKDOWN COSTS
--KEYS
alter table CustomerFinance add constraint CustomerFinanceID_PK PRIMARY KEY (CustomerFinanceID)
alter table CustomerFinanceLine add constraint CustomerFinanceLineID_PK PRIMARY KEY (CustomerFinanceLineID)
alter table CustomerFinanceLine add constraint CustomerFinanceID_FK FOREIGN KEY (CustomerFinanceID) REFERENCES CustomerFinance(CustomerFinanceID)
--PaymentNumber COUNTER (RUNS IN A PROCEDURE)
CREATE PROCEDURE FinanceCounter AS
;WITH MyCTE AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY CustomerFinanceID ORDER BY CustomerFinanceLineID) AS NewVariation
FROM CustomerFinanceLine
)
UPDATE MyCTE
SET PaymentNumber = NewVariation
WHERE PaymentNumber IS NULL
Data
--inserted PaymentValue as null for now, ideally i will
-- have a procedure to do this and insert the breakdowns programmatically
--for now an update statement will do fine unless its easier to insert it
INSERT INTO CustomerFinance VALUES (1550.00, 12)
INSERT INTO CustomerFinanceLine VALUES (1, 1, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 2, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 3, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 4, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 5, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 6, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 7, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 8, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 9, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 10, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 11, NULL)
INSERT INTO CustomerFinanceLine VALUES (1, 12, NULL)
You just need to do something like
WITH T(PaymentNumber) AS
(
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9 UNION ALL
SELECT 10 UNION ALL
SELECT 11 UNION ALL
SELECT 12
)
SELECT CASE WHEN PaymentNumber < 12 THEN FLOOR(1550.00/12)
WHEN PaymentNumber = 12 THEN 1550.00 - 11*FLOOR(1550.00/12)
END
FROM T
Let me assume you have a list of numbers. Second, in my experience, earlier payments would be rounded up, with the final being less than that payment (your title is doing this "correctly"):
select n.n as PaymentNumber,
(case when n.n < #NumPayments then ceiling(#Amount / #NumPayments)
else #Amount - #NumPayments * ceiling(#Amount / #NumPayments)
end) as MonthlyAmount
from numbers n
where n.n <= #NumPayments;
Of course, you can do the same thing with FLOOR(). Note: this might need some adjustment for small amounts.
EDIT:
n.n is just the numbers. You can use a CTE. Here is an example:
with numbers as (
select 1 as n
union all
select n + 1
from numbers
where n < 20
)

How to generate batch number in SQL Server

Any idea on how to generate batch numbers into a batch_number column in SQL Server table from 1 to 3 and repeat it again as shown below in the script?
Thanks in advance
Declare #mTestTable table
(
id int,
city varchar(50),
batch_number int
)
--Insert some sample data
Insert into #mTestTable values (1, 'London')
Insert into #mTestTable values (2, 'Karachi')
Insert into #mTestTable values (3, 'New York')
Insert into #mTestTable values (4, 'Melbourne')
Insert into #mTestTable values (5, 'Beijing')
Insert into #mTestTable values (6, 'Tokyo')
Insert into #mTestTable values (7, 'Moscow')
I need to generate batch number from 1 to 3 and repeat it again.
Id City Batch_Number
1 London 1
2 Karachi 2
3 New York 3
4 Melbourne 1
5 Beijing 2
6 Tokyo 3
7 Moscow 1
Use row_number() and some arithmetic:
with toupdate as (
select t.*, 1 + ((row_number() over (order by id) - 1) % 3) as new_batch_no
from testtable
)
update toupdate
set batch_number = new_batch_number;
update #mTestTable
set batch_number = (id-1) % 3 + 1
If the IDs won't be sequential, then one way to do it is using a cursor passing through it. Simply count the rows one by one and update the appropriate batch number.

Recursive Teradata Query

I'm trying to query the below table into a consolidated and sorted list, such as:
Beginning list:
GROUP_ID MY_RANK EMP_NAME
1 1 Dan
1 2 Bob
1 4 Chris
1 3 Steve
1 5 Cal
2 1 Britt
2 2 Babs
2 3 Beth
3 1 Vlad
3 3 Eric
3 2 Mike
Query Result:
1 Dan, Bob, Steve, Chris, Cal
2 Britt, Babs, Beth
3 Vlad, Mike, Eric
It needs to use a recursive query because the list is much longer. Also, I have to sort by my_rank to get them in sequential order. Thanks in advance. I've tried about 10 examples found on different forums, but I'm stuck. Also, don't worry about truncating the any trailing/leading commas.
CREATE TABLE MY_TEST (GROUP_ID INTEGER NOT NULL, MY_RANK INTEGER NOT NULL, EMP_NAME VARCHAR(18) NOT NULL);
INSERT INTO MY_TEST VALUES (1, 1, 'Dan');
INSERT INTO MY_TEST VALUES (1, 2, 'Bob');
INSERT INTO MY_TEST VALUES (1, 4, 'Chris');
INSERT INTO MY_TEST VALUES (1, 3, 'Steve');
INSERT INTO MY_TEST VALUES (1, 5, 'Cal');
INSERT INTO MY_TEST VALUES (2, 1, 'Britt');
INSERT INTO MY_TEST VALUES (2, 2, 'Babs');
INSERT INTO MY_TEST VALUES (2, 3, 'Beth');
INSERT INTO MY_TEST VALUES (3, 1, 'Vlad');
INSERT INTO MY_TEST VALUES (3, 3, 'Eric');
INSERT INTO MY_TEST VALUES (3, 2, 'Mike');
What's your Teradata release? Are XML-Services installed?
SELECT * FROM dbc.FunctionsV
WHERE FunctionName = 'XMLAGG';
If this function exists you can avoid recursion (which is not very efficient anyway):
SELECT GROUP_ID,
TRIM(TRAILING ',' FROM
CAST(XMLAGG(EMP_NAME || ',' ORDER BY MY_RANK) AS VARCHAR(10000)))
FROM MY_TEST
GROUP BY 1
Something like this should work:
WITH RECURSIVE employees(Group_ID , Employees, MY_RANK) AS
(
--Recursive Seed
SELECT
GROUP_ID,
--Cast as a big fat varchar so it can hold all of the employees in a list.
CAST(EMP_NAME as VARCHAR(5000)),
--We need to include MY_RANK so we can choose the next highest rank in the recursive term below.
MY_RANK
FROM
MY_TEST
WHERE
--filter for only my_rank = 1 because that's where we want to start
MY_RANK = 1
UNION ALL
--Recursive Term
SELECT
employees.GROUP_ID,
employees.EMP_NAME || ', ' || MY_TEST.EMP_NAME,
MY_TEST.MY_RANK
FROM
employees
INNER JOIN MY_TEST ON
--Joining on Group_id and Rank+1
employees.GROUP_ID = MY_TEST.GROUP_ID AND
employees.MY_RANK + 1 = MY_TEST.MY_RANK
)
SELECT GROUP_ID, Employees FROM employees;

SQL Query to return rows where a list of numbers is between start and end values

There is a table in Oracle with the columns:
id | start_number | end_number
---+--------------+------------
1 | 100 | 200
2 | 151 | 200
3 | 25 | 49
4 | 98 | 99
5 | 49 | 100
There is a list of numbers (50, 99, 150).
I want an sql statement that returns all the ids where any of the numbers in the list of numbers is found equal to or between the start_number and the end_number.
Using the above example; 1, 4 and 5 should be returned.
1 - 150 is between or equal to 100 and 200
2 - none of the numbers are between or equal to 151 and 200
3 - none of the numbers are between or equal to 25 and 49
4 - 99 is between or equal to 98 and 99
5 - 50 and 99 are between or equal to 49 and 100
drop table TEMP_TABLE;
create table TEMP_TABLE(
THE_ID number,
THE_START number,
THE_END number
);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (1, 100, 200);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (2, 151, 200);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (3, 25, 49);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (4, 98, 99);
insert into TEMP_TABLE(the_id, the_start, the_end) values (5, 49, 100);
The following is the solution I came up with based on the comments and answers below plus some additional research:
SELECT
*
from
TEMP_TABLE
where
EXISTS (select * from(
select column_value as id
from table(SYS.DBMS_DEBUG_VC2COLL(50,99,150))
)
where id
BETWEEN TEMP_TABLE.the_start AND TEMP_TABLE.the_end
)
This works too:
SELECT
*
from
TEMP_TABLE
where
EXISTS (select * from(
select column_value as id
from table(sys.ku$_vcnt(50,99,150))
)
where id
BETWEEN TEMP_TABLE.the_start AND TEMP_TABLE.the_end
)
Here is a full example:
create table #list (
number int
)
create table #table (
id int,
start_number int,
end_number int
)
insert into #list values(50)
insert into #list values(99)
insert into #list values(150)
insert into #table values(1,100,200)
insert into #table values(2,151,200)
insert into #table values(3,25,49)
insert into #table values(4,98,99)
insert into #table values(5,49,100)
select distinct a.* from #table a
inner join #list l --your list of numbers
on l.number between a.start_number and a.end_number
drop table #list
drop table #table
You'll simply need to remove the code about #table (create, insert and drop) and put your table in the select.
It partly depends on how your are storing your list of numbers. I'll assume that they're in another table for now, as even then you have many options.
SELECT
*
FROM
yourTable
WHERE
EXISTS (SELECT * FROM yourList WHERE number BETWEEN yourTable.start_number AND yourTable.end_number)
Or...
SELECT
*
FROM
yourTable
INNER JOIN
yourList
ON yourList.number BETWEEN yourTable.start_number AND yourTable.end_number
Both of those are the simplest expressions, and work well for small data sets. If your list of numbers is relatively small, and your original data is relatively large, however, this may not scale well. This is because both of the above scan the whole of yourTable and then check each record against yourList.
What may be preferable is to scan the list, and then attempt to use indexes to check against the original data. This would require you to be able to reverse the BETWEEN statement to yourTable.start_number BETWEEN x and y
This can only be done if you know the maximum gap between start_number and end_number.
SELECT
*
FROM
yourList
INNER JOIN
yourTable
ON yourTable.end_number >= yourList.number
AND yourTable.start_number <= yourList.number
AND yourTable.start_number >= yourList.number - max_gap
To achieve this I would store the value of max_gap in another table, and update it as the values in yourTable change.
You will want to create a temporary table to hold your numbers, if the numbers aren't already in one. Then it becomes relatively simple:
SELECT DISTINCT mt.ID FROM MyTable mt
INNER JOIN TempTable tt --your list of numbers
ON tt.number Between mt.start_number and mt.end_number
To create the table based on an array of passed values, you can use table definitions in your procedure. I'm light on Oracle syntax and don't have TOAD handy, but you should be able to get something like this to work:
CREATE OR REPLACE PROCEDURE FindIdsFromList
AS
DECLARE
TYPE NumberRecord IS RECORD (Number int NOT NULL)
TYPE NumberList IS TABLE OF NumberRecord;
NumberList myNumberList;
BEGIN
myNumberList := (50,99,150);
SELECT DISTINCT mt.ID FROM MyTable mt
INNER JOIN myNumberList nt --your list of numbers
ON nt.Number Between mt.start_number and mt.end_number
END