Reverse a number and sum of digits in sql - sql

In sql database i have a table .In which I have column A is decimal(18,0) type.
A
34
123
345
879
I need column B and C as like this
B C
43 7
321 6
543 12
978 24

For Postgres you can use string_to_array() to split the number into digits:
with data (a) as (
values
(34),
(123),
(345),
(879)
)
select a,
string_agg(t.d::text, '' order by t.idx desc) as b,
sum(t.d::int) as c
from data,
unnest(string_to_array(a::text,null)) with ordinality as t(d, idx)
group by a;
The above returns:
a | b | c
----+-----+---
34 | 43 | 7
123 | 321 | 6
345 | 543 | 12
879 | 978 | 24
To get the reversed number, you could also use reverse() in Postgres

In Oracle, it can be done as follows -
SELECT
VALUE, REVERSE_VALUE, SUM(SUM_TOT) AS SUM
FROM (
SELECT
DISTINCT A AS VALUE, REVERSE(TO_CHAR(A)) AS REVERSE_VALUE, SUBSTR(TO_CHAR(A), LEVEL, 1) AS SUM_TOT
FROM (
SELECT 34 AS A FROM DUAL
UNION
SELECT 123 FROM DUAL
UNION
SELECT 345 FROM DUAL
UNION
SELECT 879 FROM DUAL
)
CONNECT BY LEVEL <= LENGTH(TO_CHAR(A))
ORDER BY 1
)
GROUP BY
VALUE, REVERSE_VALUE
;
Output -
VALUE|REVERSE_VALUE|SUM
34|43|7
345|543|12
123|321|6
879|978|24

Related

SQL, label user based on the similarity

Is below case possible in SQL?
Let say I have a table like this:
user_id
product_id
1
123
1
122
1
121
2
124
2
125
2
121
3
123
3
122
3
122
4
123
4
212
4
222
5
124
5
125
5
121
I want to label the user if they have same product_id, regardless the order, so the output looks like this:
user_id
product_id
label
1
123
a
1
122
a
1
121
a
2
124
b
2
125
b
2
121
b
3
123
a
3
121
a
3
122
a
4
123
c
4
212
c
4
222
c
5
124
b
5
125
b
5
121
b
Please advise
You can use the string_agg function to get the list of product_ids for each user (as a single string), then use the dense_rank function on that string to get unique labels for each product_ids list.
select T.user_id, T.product_id, D.label
from table_name T join
(
select user_id,
chr(dense_rank() over (order by user_products) + 96) label
from
(
select user_id,
string_agg(cast(product_id as string), ',' order by product_id) user_products
from table_name
group by user_id
) lbl
) D
on T.user_id = D.user_id
order by T.user_id

pivot table with two rows into columns

i have the next result for my table:
our_date | number_people
------------------------
23/09/19 | 26
24/09/19 | 26
ALWAYS will be just two rows
and i want pivot this result and get this:
our_date_1 | number_people_1 | our_date_2 | number_people_2
-----------------------------------------------------------------
23/09/19 | 26 | 24/09/19 | 26
to get the differences between number_people_1 and number_people_2
i try with:
select *
from table_1
pivot(
count(number_people)
for our_date in (:P_TODAY, :P_YESTERDAY)
)
and this is my actual error:
ORA-56900: la variable de enlace no está soportada en la operación PIVOT|UNPIVOT
56900. 0000 - "bind variable is not supported inside pivot|unpivot operation"
*Cause: Attempted to use bind variables inside pivot|unpivot operation.
*Action: This is not supported.
what's is wrong? how can i use dynamic values inside for clause ?
Best regards
Error says that this:
for fecha in (our_date)
can't have our_date (column name) as list of values; it (the list) has to contain constants, e.g.
for our_date in (date '2019-09-23', date '2019-09-24')
Once you fix that, query might look like this:
SQL> with table_1 (our_date, number_people) as
2 (select date '2019-09-23', 26 from dual union all
3 select date '2019-09-24', 26 from dual
4 )
5 select *
6 from table_1
7 pivot (max(number_people)
8 for our_date in (date '2019-09-23', date '2019-09-24')
9 );
TO_DATE(' 2019-09-23 00:00:00' TO_DATE(' 2019-09-24 00:00:00'
------------------------------ ------------------------------
26 26
SQL>
But, that's not exactly what you wanted.
What if there are 3, 4 or more rows in that table? Is it possible, or will there always be only 2 rows?
If it is always only 2 rows, self-join can do the job. For example:
SQL> with table_1 (our_date, number_people) as
2 (select date '2019-09-23', 26 from dual union all
3 select date '2019-09-24', 22 from dual
4 ),
5 temp as
6 (select our_date, number_people,
7 row_number() over (order by our_date) rn
8 from table_1
9 )
10 select
11 a.our_date our_date_1,
12 a.number_people number_people_1,
13 --
14 b.our_date our_date_2,
15 b.number_people number_people_2
16 from temp a cross join temp b
17 where a.rn = 1
18 and b.rn = 2;
OUR_DATE_1 NUMBER_PEOPLE_1 OUR_DATE_2 NUMBER_PEOPLE_2
---------- --------------- ---------- ---------------
23.09.2019 26 24.09.2019 22
SQL>

Select row of data based on specific conditions from other columns

I have this data:
ID_OWN ID_PET KEY NAME
123 1 11 JOY
123 1 11 JOY
123 2 12 JOY
123 2 12 JOY
456 1 13 HELLO
456 1 13 MAMA
456 2 14 HELLO
456 2 14 MAMA
SCENARIO IS:
If I SELECT DISTINCT, ID_OWN 123 will return 2 rows of data.
But for ID_OWN 456, it will still return 4 rows of data, because column NAME has all different values.
What I want is, if NAME has different values under the same ID_OWN, then I want those NAME values to be sticked together, so it will return 2 rows of data too (or N rows of data based on no. of ID_PET under the same ID_OWN, in this case, they are 1 and 2)
Below is expected return data:
ID_OWN ID_PET KEY NAME
123 1 11 JOY
123 2 12 JOY
456 1 13 HELLO MAMA
456 2 14 HELLO MAMA
In SQL Server, You can achieve it in this way, demo on db<>fiddle
;WITH cte_TempTable AS (
SELECT DISTINCT ID_OWN, ID_PET, Key_Number, Name
FROM TempTable
)
SELECT DISTINCT ID_OWN, ID_PET, Key_Number, SUBSTRING(
(
SELECT ' ' + Name
FROM cte_TempTable C1
WHERE C1.ID_OWN = C2.ID_OWN AND C1.ID_PET = C2.ID_PET AND C1.Key_Number = C2.Key_Number
FOR XML PATH ('')
), 2, 1000) AS "nAME"
FROM cte_TempTable C2
Output
ID_OWN ID_PET Key_Number nAME
123 1 11 JOY
123 2 12 JOY
456 1 13 HELLO MAMA
456 2 14 HELLO MAMA
If it is MySQL, you can use GROUP_CONCAT as below-
DEMO HERE
SELECT ID_OWN,ID_PET,`Key`, group_CONCAT(NAME)
FROM (
SELECT DISTINCT ID_OWN,ID_PET,`Key`, NAME FROM your_table
)A
GROUP BY ID_OWN,ID_PET,`Key`
Use XML in combo with stuff
select distinct ID_OWN , ID_PET, [KEY],
STUFF((Select ' '+ NAME
from yourtable T1
where T1.ID_OWN =T2.ID_OWN and T1.ID_PET =T2.ID_PET and T1.[KEY] =T2.[KEY]
FOR XML PATH('')),1,1,'') as stickedName from yourtable T2
Output is:
ID_OWN ID_PET KEY stickedName
123 1 11 JOY
123 2 12 JOY
456 1 13 HELLO,MAMA
456 2 14 HELLO,MAMA

How to generate a dynamic sequence in Oracle

I have a table A which represents a valid sequence of numbers, which looks something like this:
| id | start | end | step |
|----|-------|-------|------|
| 1 | 4000 | 4999 | 4 |
| 2 | 3 | 20000 | 1 |
A[1] thus represents the sequence [4000, 4004, 4008, ...4996]
and another B of "occupied" numbers that looks like this:
| id | number | ... |
|-----|--------|-----|
| 1 | 4000 | ... |
| 2 | 4003 | ... |
| ... | ... | ... |
I want to construct a query which using A and B, finds the first unoccupied number for a particular sequence.
I have been trying – and failing – to do, is to generate a list of valid numbers from a row in A and then left outer join table B on B.number = valid_number where B.id is null from which result I could then select min(...).
How about this?
I simplified your test case (END value isn't that high) in order to save space (otherwise, I'd have to use smaller font :)).
What does it do?
CTEs A and B are your sample data
FULL_ASEQ creates a sequence of numbers from table A
if you want what it returns, remove everything from line #17 and - instead of it - run select * from full_aseq
the final query returns the first available sequence number, i.e. the one that hasn't been used yet (lines #19 - 23).
Here you go:
SQL> with
2 a (id, cstart, cend, step) as
3 (select 1, 4000, 4032, 4 from dual union all
4 select 2, 3, 20, 1 from dual
5 ),
6 b (id, cnumber) as
7 (select 1, 4000 from dual union all
8 select 1, 4004 from dual union all
9 select 2, 4003 from dual
10 ),
11 full_aseq as
12 (select a.id, a.cstart + column_value * a.step seq_val
13 from a cross join table(cast(multiset(select level from dual
14 connect by level <= (a.cend - a.cstart) / a.step
15 ) as sys.odcinumberlist))
16 )
17 select f.id, min(f.seq_val) min_seq_val
18 from full_aseq f
19 where not exists (select null
20 from b
21 where b.id = f.id
22 and b.cnumber = f.seq_val
23 )
24 group by f.id;
ID MIN_SEQ_VAL
---------- -----------
1 4008
2 4
SQL>
You can use LEAD to compute the difference between ordered rows in table B. Any row having a difference (to the next row) that exceeds the step value for that sequence is a gap.
Here's that concept, implemented (below). I threw in a sequence ID "3" that has no values in table B, to illustrate that it generates the proper first value.
with
a (id, cstart, cend, step) as
(select 1, 4000, 4032, 4 from dual union all
select 2, 3, 20000, 1 from dual union all
select 3, 100, 200, 3 from dual
),
b (id, cnumber) as
(select 1, 4000 from dual union all
select 1, 4004 from dual union all
select 1, 4012 from dual union all
select 2, 4003 from dual
),
work1 as (
select a.id,
b.cnumber cnumber,
lead(b.cnumber,1) over ( partition by b.id order by b.cnumber ) - b.cnumber diff,
a.step,
a.cstart,
a.cend
from a left join b on b.id = a.id )
select w1.id,
CASE WHEN min(w1.cnumber) is null THEN w1.cstart
WHEN min(w1.cnumber)+w1.step < w1.cend THEN min(w1.cnumber)+w1.step
ELSE null END next_cnumber
from work1 w1
where ( diff is null or diff > w1.step )
group by w1.id, w1.step, w1.cstart, w1.cend
order by w1.id
+----+--------------+
| ID | NEXT_CNUMBER |
+----+--------------+
| 1 | 4008 |
| 2 | 4004 |
| 3 | 100 |
+----+--------------+
You can further improve the results by excluding rows in table B that are impossible for the sequence. E.g., exclude a row for ID #1 having a value of, say, 4007.
I'll ask the obvious and suggest why not use an actual sequence?
SQL> set timing on
SQL> CREATE SEQUENCE SEQ_TEST_A
START WITH 4000
INCREMENT BY 4
MINVALUE 4000
MAXVALUE 4999
NOCACHE
NOCYCLE
ORDER
Sequence created.
Elapsed: 00:00:01.09
SQL> CREATE SEQUENCE SEQ_TEST_B
START WITH 3
INCREMENT BY 1
MINVALUE 3
MAXVALUE 20000
NOCACHE
NOCYCLE
ORDER
Sequence created.
Elapsed: 00:00:00.07
SQL> -- get nexvals from A
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4000
1 row selected.
Elapsed: 00:00:00.09
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4004
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4008
1 row selected.
Elapsed: 00:00:00.08
SQL> -- get nextvals from B
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
3
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
4
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
5
1 row selected.
Elapsed: 00:00:00.08

Get count of numbers from all columns in a large table

I have the following table
ID A1 A2 A3 A4 A5 A6
1 324 243 3432 23423 342 342
2 342 242 4345 23423 324 342
How do I write a query that will give me the no.of times a number is appearing in any of the above columns. For example, this is the output I am looking for -
324 2
243 1
3432 1
23423 1
342 3
242 1
4345 1
23423 1
There are a number of ways to do this, but my first thought is to use unnest:
rnubel=# CREATE TABLE mv (a int, b int, c int);
CREATE TABLE
rnubel=# INSERT INTO mv (a, b, c) VALUES (1, 1, 1), (2, 2, 2), (3, 4, 5);
INSERT 0 3
rnubel=# SELECT unnest(array[a, b, c]) as value, COUNT(*) from mv GROUP BY 1;
value | count
-------+-------
5 | 1
4 | 1
2 | 3
1 | 3
3 | 1
(5 rows)
unnest is a handy function that turns an array into a set of rows, so it expands the array of column values into one row per column value. Then you just group and count as usual.
Brute force method:
SELECT Value
,COUNT(1) AS ValueCount
FROM (
SELECT A1 AS Value
FROM t
UNION ALL
SELECT A2
FROM t
UNION ALL
SELECT A3
FROM t
UNION ALL
SELECT A4
FROM t
UNION ALL
SELECT A5
FROM t
UNION ALL
SELECT A6
FROM t
) x
GROUP BY Value
In Postgres, you can use lateral joins to unpivot values. I find this more direct than using an array or union all:
select v.a, count(*)
from t cross join lateral
(values (a1), (a2), (a3), (a4), (a5), (a6)
) v(a)
group by v.a;
Here is a db<>fiddle.