SQL - display from table groups of rows as columns - sql

I am trying to take range of codes in one column and display their results under one column per category.
Example of my table:
ID | TEST_NUM | RESULT |
---+----------+--------+
1 | R1 | 33 |
1 | R2 | 31 |
1 | C1 | 24 |
1 | C2 | 19 |
by query from table above i am trying to get the next table:
ID | TEST_NUM OF R ONLY | R RESULTS | C RESULTS
---+--------------------+-----------+----------
1 | R1 | 33 | 24
1 | R2 | 31 | 19

String operations can vary by database. I will assume yours supports left() and right() (which are easily represented by similar functions).
select id, max(test_num) as test_num,
max(case when test_num like 'R%' then result end) as r_result,
max(case when test_num like 'C%' then result end) as c_result
from t
group by id, right(test_num, 1)

Related

Combining a variable number of records into a single record

The gist of the problem is that I need to combine multiple records from Table A into a single record, based on a shared Id and insert that into Table B. Each Id can have a maximum of three records associated with it with a minimum of 1, being the preferred destinations for the Id. If the record has less than the maximum number of preferences, I want to set those columns to NULL in Table B.
Here's an example:
Table A
ID | Preference| Destination
--------------------------
10 | 1 | Building A
10 | 2 | Building B
10 | 3 | Building C
23 | 1 | Building B
23 | 2 | Building A
45 | 1 | Building C
Table B
ID | Destination1 | Destination2 | Destination3
-----------------------------------------------
| | |
I want to combine the records in Table A so that it displays like so in Table B
ID | Destination1 | Destination2 | Destination3
-----------------------------------------------
10 | Building A | Building B | Building C
23 | Building B | Building A | NULL
45 | Building C | NULL | NULL
Any help is greatly appreciated!
You can use conditional aggregation:
select id,
max(case when preference = 1 then destination end) as destination1,
max(case when preference = 2 then destination end) as destination2,
max(case when preference = 3 then destination end) as destination3
from t
group by id;

SQL Server - Return different values based on row count

I have two tables, table 1 is the target table, I’ve provided the required values in idCode1- idCode3.
Table 2 is the source, each idBill will have one or more idCode. If there were two rows to represent 2 unique idCode, then I want to insert to idCode 1 and 2 respectively.
I was thinking a case statement where I could test for the number of idCode and then insert first value to 1, second value to 2 etc. When I tried a bunch of case, when, exists, count etc it would always return 2 rows if there were 2 idCode values, and the idCode would only insert to idCode1. The end result must be a single row in table1 for each idBill and however many idCode for that idBill inserted to 1, 2, 3.
Sorry I couldn’t post the picture as I don’t have enough points. Here is a rough pipe delimited example of it:
| idTable1 | idBill | idCode1 | idCode2 | idCode3 |
| 1 | 1234 | A1 | A2 | |
| 2 | 1235 | E3 | E2 | A1 |
| idTable2 | idBill | codeId |
| 10 | 1234 | A1 |
| 20 | 1234 | A2 |
| 30 | 1235 | E3 |
| 40 | 1235 | E2 |
| 50 | 1235 | A1 |
Hopefully this makes sense. Thanks so much!
You can use conditional aggregation:
select s.idbill,
max(case when seqnum = 1 then s.codeid end) as codeid1,
max(case when seqnum = 2 then s.codeid end) as codeid2,
max(case when seqnum = 3 then s.codeid end) as codeid3
into target
from (select s.*, row_number() over (partition by idbill order by idtable2) as seqnum
from source s
) s
group by s.idbill;

How do I do an Oracle SQL update from select in a specific order?

I have a table with old values (some null) and new values for various attributes, all inserted at different add times throughout the months. I'm trying to update a second table with records with business month end dates. Right now, these records only contain the most recent new values for all month end dates. The goal is to create historical data by updating the previous month end values with the old values from the first table. I am a beginner and was able to come up with a query to update on one object where there was one entry from the first table. Now I am trying to expand the query to include multiple objects, with possible, multiple old values within the same month. I tried to use "order by" (since I need to make updates for a month in ascending order so it gets the latest value) but read that doesn't work with update statements without a subquery. So I tried my hand at making a more complicated query, without success. I am getting the following error: single-row subquery returns more than one row. Thanks!
TableA:
| ID | TYPE | OLD_VALUE | NEW_VALUE | ADD_TIME|
-----------------------------------------------
| 1 | A | 2 | 3 | 1/11/2019 8:00:00am |
| 1 | B | 3 | 4 | 12/10/2018 8:00:00am|
| 1 | B | 4 | 5 | 12/11/2018 8:00:00am|
| 2 | A | 5 | 1 | 12/5/2018 08:00:00am|
| 2 | A | 1 | 2 | 12/5/2019 09:00:00am|
| 2 | A | 2 | 3 | 12/5/2019 10:00:00am|
| 2 | B | 1 | 2 | 12/5/2019 10:00:00am|
TableB
| ID | MONTH_END | TYPE_A | TYPE_B |
-----------------------------------
| 1 | 1/31/19 | 3 | 5 |
| 1 | 12/31/18 | 3 | 5 |
| 1 | 11/30/18 | 3 | 5 |
| 2 | 12/31/18 | 3 | 2 |
| 2 | 11/30/18 | 3 | 2 |
Desired Output for TableB
| ID | MONTH_END | TYPE_A | TYPE_B |
-----------------------------------
| 1 | 1/31/19 | 3 | 5 |
| 1 | 12/31/18 | 2 | 5 |
| 1 | 11/30/18 | 2 | 3 |
| 2 | 12/31/18 | 3 | 2 |
| 2 | 11/30/18 | 5 | 2 |
My Query for Type A (Which I plan to adapt for Type B and execute as well for the desired output)
update TableB B
set b.type_a =
(
with aa as
(
select id, nvl(old_value, new_value) typea, add_time
from TableA
where type = 'A'
order by id, add_time ascending
)
select typea
from aa
where aa.id = b.id
and b.month_end <= aa.add_tm
)
where exists
(
with aa as
(
select id, nvl(old_value, new_value) typea, add_time
from TableA
where type = 'A'
order by id, add_time ascending
)
select typea
from aa
where aa.id = b.id
and b.month_end <= aa.add_tm
)
Kudo's for giving example input data and desired output. I found your question a bit confusing so let me rephrase to "Provide the last type a value from table a that is in the same month as the month end.
By matching on type and date of entry, we can get your answer. The "ROWNUM=1" is to limit result set to a single entry in case there is more than one row with the same add_time. This SQL is still a mess, maybe someone else can come up with a better one.
UPDATE tableb b
SET b.typea =
(SELECT old_value
FROM tablea a
WHERE LAST_DAY( TRUNC( a.add_time ) ) = b.month_end
AND TYPE = 'A'
AND add_time =
(SELECT MAX( add_time )
FROM tablea
WHERE TYPE = 'A' AND LAST_DAY( TRUNC( a.add_time ) ) = b.month_end)
AND ROWNUM = 1)
WHERE EXISTS
(SELECT old_value
FROM tablea a
WHERE LAST_DAY( TRUNC( a.add_time ) ) = b.month_end AND TYPE = 'A');

Calculating consecutive range of dates with a value in Hive

I want to know if it is possible to calculate the consecutive ranges of a specific value for a group of Id's and return the calculated value(s) of each one.
Given the following data:
+----+----------+--------+
| ID | DATE_KEY | CREDIT |
+----+----------+--------+
| 1 | 8091 | 0.9 |
| 1 | 8092 | 20 |
| 1 | 8095 | 0.22 |
| 1 | 8096 | 0.23 |
| 1 | 8098 | 0.23 |
| 2 | 8095 | 12 |
| 2 | 8096 | 18 |
| 2 | 8097 | 3 |
| 2 | 8098 | 0.25 |
+----+----------+--------+
I want the following output:
+----+-------------------------------+
| ID | RANGE_DAYS_CREDIT_LESS_THAN_1 |
+----+-------------------------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 1 |
| 2 | 1 |
+----+-------------------------------+
In this case, the ranges are the consecutive days with credit less than 1. If there is a gap between date_key column, then the range won't have to take the next value, like in ID 1 between 8096 and 8098 date key.
Is it possible to do this with windowing functions in Hive?
Thanks in advance!
You can do this with a running sum classifying rows into groups, incrementing by 1 every time a credit<1 row is found(in the date_key order). Thereafter it is just a group by.
select id,count(*) as range_days_credit_lt_1
from (select t.*
,sum(case when credit<1 then 0 else 1 end) over(partition by id order by date_key) as grp
from tbl t
) t
where credit<1
group by id
The key is to collapse all the consecutive sequence and compute their length, I struggled to achieve this in a relatively clumsy way:
with t_test as
(
select num,row_number()over(order by num) as rn
from
(
select explode(array(1,3,4,5,6,9,10,15)) as num
)
)
select length(sign)+1 from
(
select explode(continue_sign) as sign
from
(
select split(concat_ws('',collect_list(if(d>1,'v',d))), 'v') as continue_sign
from
(
select t0.num-t1.num as d from t_test t0
join t_test t1 on t0.rn=t1.rn+1
)
)
)
Get the previous number b in the seq for each original a;
Check if a-b == 1, which shows if there is a "gap", marked as 'v';
Merge all a-b to a string, and then split using 'v', and compute length.
To get the ID column out, another string which encode id should be considered.

Select rows that end with the given parts in a join query?

I'm trying to create a complex SQL query (at least for me) but really don't know where to start.
Basically, I have a character object, and each character can be made of several parts. For example:
character table:
id | character
--------------
1 | 你
2 | 是
3  | 有
character_parts table:
id | character_id | part_id
---------------------------
1 | 1 | 4
2 | 1 | 9
3 | 1 | 5
4 | 2 | 2
5 | 2 | 34
6 | 2 | 43
7 | 3 | 21
8 | 3 | 16
9 | 3 | 41
10 | 3 | 43
So from that I know that:
Character 1 is made of parts 4, 9, 5
Character 2 is made of parts 2, 34, 43
Character 3 is made of parts 21, 16, 41, 43
Now what I would like to do is select all the characters that end by the specified parts.
For example, if I select all the characters that end by "16, 41, 43", I'll get Character 3. If I select all the characters that end by "43", I'll get Character 3 and 4.
I assume I need to build some query with subqueries, but not sure how to start, or if it can be done at all. So far I'm just selecting everything that include the required part IDs and then doing the comparison programmatically but this is too slow being I'm selecting way more than needed.
Any suggestion?
You could try group_concat function:
http://www.sqlite.org/lang_aggfunc.html
SELECT group_concat(part_id) FROM character_parts WHERE character_id=1
The query should return 4,9,5.
The problem is that the order used by group_concat is arbitrary:
Sqlite group_concat ordering
So, assuming you have a field position that defines the order of the parts, we can update the query like this:
SELECT group_concat(part_id) FROM (SELECT part_id FROM character_parts WHERE character_id=1 ORDER BY position ASC)
The query will now return the 4,9,5 parts exactly the defined order.
Now that we have this value, we can search though it like a regular string.
If we want to find all values ending with a certain string, we could use LIKE operator.
Finally the query would like like this:
SELECT character_id, parts_concat FROM (
SELECT character_id, group_concat(part_id) FROM (
SELECT character_id, part_id FROM character_parts WHERE ORDER BY position ASC
) GROUP BY character_id
) parts
WHERE parts_concat LIKE '%,9,5'
the query might be like this.
select * from character where id in (select character_id from character_parts where character_id = 'required no' AND character_id = 'required no' AND character_id = 'required no')
//required number is the part_id you want to specify.
Add another column, from_end that counts from the end, say:
from_end | id | character_id | part_id
--------------------------------------
2 | 1 | 1 | 4
1 | 2 | 1 | 9
0 | 3 | 1 | 5
2 | 4 | 2 | 2
1 | 5 | 2 | 34
0 | 6 | 2 | 43
3 | 7 | 3 | 21
2 | 8 | 3 | 16
1 | 9 | 3 | 41
0 | 10 | 3 | 43
Then you can do:
SELECT p0.character_id
FROM character_parts AS p0
JOIN character_parts AS p1 USING (character_id)
JOIN character_parts AS p2 USING (character_id)
WHERE p0.from_end = 0 AND p1.from_end = 1 AND p2.from_end = 2
AND p0.part_id = 43 AND p1.part_id = 41 AND p2.part_id = 16
SELECT c.character
FROM character_parts cp
JOIN character c
ON c.id = cp.character_id
WHERE cp.part_id = 43
AND cp.id =
(
SELECT id
FROM character_parts cpo
WHERE cpo.character_id = cp.character_id
ORDER BY
id DESC
)
Create the following indexes:
character_parts (part_id, character_id, id)
character_parts (character_id, id)
for this to work fast