SQL Getting multiple rows from a single row - sql

I need to accomplish the following :
I have a table with multiple column (c1, c2, c3, c4 ... cn).
I want a query that would return multiple rows in the following fashion (r1 r2 .. rx are the rows in the original table) :
r1c1 r1c2 r1c3
r1c4 r1c5 r1c6
...
r1cn-2 r1cn-1 r1cn
r2c1 r2c2 r2c3
r2c4 r2c5 r2c6
...
r2cn-2 r2cn-1 r2cn
...
rxc1 rxc2 rxc3
rxc4 rxc5 rxc6
...
rxcn-2 rxn-1 rxcn
I know I can use unions and repeat basically the same query n times, but I need to use that query in a web based reporting system that I have no control over and the query is to big for the maximum number of characters allowed in queries.
Any suggestions ?
Thank you !
EDIT : FYI I'm building a report in a report tool I can't change using a database I can't change. So using custom functions/procedures is not a solution. It has to be a PL-SQL query.
To be more specific, i need to have multiple rows from the original row, lets say row 1 is
a b c d e f h i j
and row 2 is
1 2 3 4 5 6 7 8 9
then I would get the following table with 3 columns :
a b c
d e f
h i j
1 2 3
4 5 6
7 8 9

So number 1, if you have a set number of columns in the original table that is divisible by 3, you can just do that many UNION ALL.
Your only other options are pivot or a pointer, neither of which is going to be any better.

The simple case when n is divisible by 3 just use rownum and union:
WITH T1 AS
(
SELECT rownum as rn,1 as tNum, c1 as s1,c2 as s2,c3 as s3 FROM T
UNION ALL
SELECT rownum as rn,2 as tNum, c4 as s1,c5 as s2,c6 as s3 FROM T
UNION ALL
SELECT rownum as rn,3 as tNum, c7 as s1,c8 as s2,c9 as s3 FROM T
)
SELECT s1,s2,s3 FROM T1
ORDER BY rn,tNum
SQLFiddle demo

Related

SAS EG (SQL) deleting rows where max value in one column

I need to delete all the rows with a max value of duty_perd_id where the rotn_prng_nbr and empl_nbr are the same (not the same to each other, but the max where of all of the rows where those two remain constant). From the table below it should delete rows 3,7 and 9.
rotn_prng_nbr
empl_nbr
duty_perd_id
B93
12
1
B93
12
2
B93
12
3
B21
12
1
B21
12
2
B21
12
3
B21
12
4
B21
18
1
B21
18
2
using SAS EG. Right now all have is below:
Option 1:
create table middle_legs as
select t.*
from actual_flt_leg as t
where t.duty_perd_id < (select max(t2.duty_perd_id)
from actual_flt_leg as t2
where t2.rotn_prng_nbr = t.rotn_prng_nbr and
t2.empl_nbr = t.empl_nbr
);
this works exactly as intended, but is incredibly slow. The other thought that I had but couldnt quite finish was as follows.
Option 2:
create table last_duty_day as
Select * from actual_flt_leg
inner join (
select actual_flt_leg.Rotn_Prng_Nbr,actual_flt_leg.empl_nbr, max(duty_perd_id) as last_duty
from actual_flt_leg
group by actual_flt_leg.Rotn_Prng_Nbr, actual_flt_leg.empl_nbr
) maxtable on
actual_flt_leg.Rotn_Prng_Nbr = maxtable.Rotn_Prng_Nbr
and actual_flt_leg.empl_Nbr = maxtable.empl_Nbr
and actual_flt_leg.duty_perd_id = maxtable.last_duty;
option 2 finds all the highest duty_perd_id for the given pair, and I was wondering if there was any "reverse join" that could only show the rows from the original table that do not match this new table i created in option 2.
If there is a way to make option 1 faster, finish option 2, or anything else i cant think of id appreciate it. Thanks!
You are almost there. You just want <:
Select *
from actual_flt_leg inner join
(select actual_flt_leg.Rotn_Prng_Nbr,actual_flt_leg.empl_nbr, max(duty_perd_id) as last_duty
from actual_flt_leg
group by actual_flt_leg.Rotn_Prng_Nbr, actual_flt_leg.empl_nbr
) maxtable
on actual_flt_leg.Rotn_Prng_Nbr = maxtable.Rotn_Prng_Nbr and
actual_flt_leg.empl_Nbr = maxtable.empl_Nbr and
actual_flt_leg.duty_perd_id < maxtable.last_duty;
In SAS SQL, this is pretty easy:
data have;
input rotn_prng_nbr $ empl_nbr duty_perd_id;
datalines;
B93 12 1
B93 12 2
B93 12 3
B21 12 1
B21 12 2
B21 12 3
B21 12 4
B21 18 1
B21 18 2
;;;;
run;
proc sql;
select *
from have
group by rotn_prng_nbr, empl_nbr
having duty_perd_id lt max(duty_perd_id);
quit;
This isn't legit SQL in any other system that I've ever seen, but it works in SAS. You can group by a set of variables while still using select for all of the variables including ones not on group by; SAS just does two queries and merges them behind the scenes for you.
NOTE: The query requires remerging summary statistics back with the original data.
As far as I understand the actual under the hood result is exactly identical to the more "compatible" version Gordon suggests; just a matter of whether you prefer typing less or more compatible SQL code.

How to do the sum for each row of the values of a column?

I have a SQL table containing a column of list of int, like this
Index Column to sum
1 1:5:13:3:6:7:11:2:4:1:2:5
2 1:7:2:1:1
3 19:05:05
4 2:1:1:5:1:4:64:177:86:75:2:83:2:57:1
5 43
6 1:1:1:3:10:6:1
7 2:11:4:3:1
8 1:5:2:3:34:2:2
9 4:3:1:2
10 4:1:1:4
I would like to calcul in SQL the sum for each row of the Column to sum value.
It would give something like this:
1 60
2 12
...
10 10
Any idea?
Thanks
Your first effort should go into fixing your data model, as commented by Gordon Linoff. The numbers should be stored in a separate table, with each value on a separate row.
In Postgres, you can split the values to rows using regexp_split_to_table(), then aggregate:
select t.id, sum(x.val::int) result
from mytable t
cross join lateral regexp_split_to_table(t.column_to_sum, ':') as x(val)
group by t.id

Aggregation over order-dependent partition?

I have a source data set like this (simplified to be more clear):
Key F1 F2
1 X 4
2 X 5
3 Y 6
4 X 9
5 X 7
6 X 8
7 Y 9
8 X 6
9 X 5
10 Y 3
The data is sorted by the Key field. Now, I want to compute an aggregate of the F2 field over partitions that are defined by the F1 field: A partition starts at the first X value and ends with the first subsequent Y value.
So, for example, I might want wo compute the MIN() over the partitions defined as described above. Then the result set would look like this:
rownum MIN(F2)
1 4
2 7
3 3
I have tried a number of resources (incl. our own intranet community and of course stackoverflow) but found nothing for my case. Usually partitioning only works with a field that can be used to identify the partitions. Here, the partitions are defined by a change in a field's content with respect to a given order.
Although I am aware that I may have to resort to writing a procedural solution I would prefer to solve this in pure SQL.
Any ideas how such a partitioning could be achieved with a SQL select statement?
Thanks and regards
Kai.
A little bit shorter solution: http://sqlfiddle.com/#!12/7390d/24
Query:
select min(f2)
from t t1
group by (select max(key)
from t t2
where t2.f1='Y' and
t1.key > t2.key)
Result:
| MIN |
-------
| 4 |
| 7 |
| 3 |
The idea is to find the key of preceding 'Y' for each row and group by it. Should work with any SQL engine.
You didn't specify engine or dialect or version so I assumed SQL Server 2012.
Example that you can run to see the solution: http://sqlfiddle.com/#!6/f5d38/21
You solve it by creating correct partitions in your set. Code looks like this.
WITH groupLimits as
(
SELECT
[Key] AS groupend
,COALESCE(LAG([Key]) OVER (order by [Key]),0)+1 AS groupstart
FROM sourceData
WHERE F1 = 'Y'
)
SELECT
MIN(sourceData.F2)
FROM groupLimits
INNER JOIN sourceData
ON sourceData.[Key] BETWEEN groupLimits.groupstart and groupLimits.groupend
GROUP BY groupLimits.groupstart
ORDER BY groupLimits.groupstart

Delete duplicates when the duplicates are not in the same column

Here is a sample of my data (n>3000) that ties two numbers together:
id a b
1 7028344 7181310
2 7030342 7030344
3 7030354 7030353
4 7030343 7030345
5 7030344 7030342
6 7030364 7008059
7 7030659 7066051
8 7030345 7030343
9 7031815 7045692
10 7032644 7102337
Now, the problem is that id=2 is a duplicate of id=5 and id=4 is a duplicate of id=8. So, when I tried to write if-then statements to map column a to column b, basically the numbers just get swapped. There are many cases like this in my full data.
So, my question is to identify the duplicate(s) and somehow delete one of the duplicates (either id=2 or id=5). And I preferably want to do this in Excel but I could work with SQL Server or SAS, too.
Thank you in advance. Please comment if my question is not clear.
What I want:
id a b
1 7028344 7181310
2 7030342 7030344
3 7030354 7030353
4 7030343 7030345
6 7030364 7008059
7 7030659 7066051
9 7031815 7045692
10 7032644 7102337
All sorts of ways to do this.
In SAS or SQL, this is simple (for SQL Server, the SQL portion should be identical or nearly so):
data have;
input id a b;
datalines;
1 7028344 7181310
2 7030342 7030344
3 7030354 7030353
4 7030343 7030345
5 7030344 7030342
6 7030364 7008059
7 7030659 7066051
8 7030345 7030343
9 7031815 7045692
10 7032644 7102337
;;;;
run;
proc sql undopolicy=none;
delete from have H where exists (
select 1 from have V where V.id < H.id
and (V.a=H.a and V.b=H.b) or (V.a=H.b and V.b=H.a)
);
quit;
The excel solution would require creating an additional column I believe with the concatenation of the two strings, in order (any order will do) and then a lookup to see if that is the first row with that value or not. I don't think you can do it without creating an additional column (or using VBA, which if you can use that will have a fairly simple solution as well).
Edit:
Actually, the excel solution IS possible without creating a new column (well, you need to put this formula somewhere, but without ANOTHER additional column).
=IF(OR(AND(COUNTIF(B$1:B1,B2),COUNTIF(C$1:C1,C2)),AND(COUNTIF(B$1:B1,C2),COUNTIF(C$1:C1,B2))),"DUPLICATE","")
Assuming ID is in A, B and C contain the values (and there is no header row). That formula goes in the second row (ie, B2/C2 values) and then is extended to further rows (so row 36 will have the arrays be B1:B35 and C1:C35 etc.). That puts DUPLICATE in the rows which are duplicates of something above and blank in rows that are unique.
I haven't tested this out but here is some food for thought, you could join the table against itself and get the ID's that have duplicates
SELECT
id, a, b
FROM
[myTable]
INNER JOIN ( SELECT id, a, b FROM [myTable] ) tbl2
ON [myTable].a = [tbl2].b
OR [myTable].b = tbl2.a

Finding contiguous regions in a sorted MS Access query

I am a long time fan of Stack Overflow but I've come across a problem that I haven't found addressed yet and need some expert help.
I have a query that is sorted chronologically with a date-time compound key (unique, never deleted) and several pieces of data. What I want to know is if there is a way to find the start (or end) of a region where a value changes? I.E.
DateTime someVal1 someVal2 someVal3 target
1 3 4 A
1 2 4 A
1 3 4 A
1 2 4 B
1 2 5 B
1 2 5 A
and my query returns rows 1, 4 and 6. It finds the change in col 5 from A to B and then from B back to A? I have tried the find duplicates method and using min and max in the totals property however it gives me the first and last overall instead of the local max and min? Any similar problems?
I didn't see any purpose for the someVal1, someVal2, and someVal3 fields, so I left them out. I used an autonumber as the primary key instead of your date/time field; but this approach should also work with your date/time primary key. This is the data in my version of your table.
pkey_field target
1 A
2 A
3 A
4 B
5 B
6 A
I used a correlated subquery to find the previous pkey_field value for each row.
SELECT
m.pkey_field,
m.target,
(SELECT Max(pkey_field)
FROM YourTable
WHERE pkey_field < m.pkey_field)
AS prev_pkey_field
FROM YourTable AS m;
Then put that in a subquery which I joined to another copy of the base table.
SELECT
sub.pkey_field,
sub.target,
sub.prev_pkey_field,
prev.target AS prev_target
FROM
(SELECT
m.pkey_field,
m.target,
(SELECT Max(pkey_field)
FROM YourTable
WHERE pkey_field < m.pkey_field)
AS prev_pkey_field
FROM YourTable AS m) AS sub
LEFT JOIN YourTable AS prev
ON sub.prev_pkey_field = prev.pkey_field
WHERE
sub.prev_pkey_field Is Null
OR prev.target <> sub.target;
This is the output from that final query.
pkey_field target prev_pkey_field prev_target
1 A
4 B 3 A
6 A 5 B
Here is a first attempt,
SELECT t1.Row, t1.target
FROM t1 WHERE (((t1.target)<>NZ((SELECT TOP 1 t2.target FROM t1 AS t2 WHERE t2.DateTimeId<t1.DateTimeId ORDER BY t2.DateTimeId DESC),"X")));