Oracle SQL: Merge two results in 1 row - sql

I'm currently creating a SQL query to get the results of all records from two tables that are connected via ID. Is there anyway to return the results in 1 row if there are multiple records link to 1 id from a different table? Below are my SQL query, current result and what is the expected result of the query.
Current query:
SELECT
'A' AS "actionIndicator", 'A' AS "target",
crdExpt.CRD_PAN,
acnExpt.ACN_ATP_ID, acnExpt.ACN_ACCOUNT_NUMBER,
FROM
tbl1 crdExpt, tbl2 acnExpt, tbl3 crdAcnExpt
where tbl1 is the record for card numbers, tbl2 is the record for account numbers and tbl3 is where the linking of card and account numbers.
Current result is like this:
CRD_PAN | ACN_ATP_ID| ACN_ACCOUNT_NUMBER
123456789 | 23 | 99112345678
123456789 | 24 | 99012345678
What I'm trying to achieve is if there 2 account numbers linked to 1 card, the expected output is:
CRD_PAN | ACN_ATP_ID| ACN_ACCOUNT_NUMBER |ACN_ATP_ID2 | ACN_ACCOUNT_NUMBER2
123456789 | 23 | 99112345678 | 24 | 99012345678

By OP request in the comments:
I used the following example data (the result of your query) on this SQL Fiddle
CREATE TABLE test(
CRD_PAN VARCHAR(256),
ACN_ATP_ID VARCHAR(256),
ACN_ACCOUNT_NUMBER VARCHAR(256)
);
INSERT INTO test(CRD_PAN, ACN_ATP_ID, ACN_ACCOUNT_NUMBER)
SELECT '123456789', '23', '99112345678' FROM DUAL
UNION ALL
SELECT '123456789', '24', '99012345678' FROM DUAL
;
From there, I ran the following query:
SELECT
CRD_PAN,
LISTAGG(ACN_ATP_ID, ', ') WITHIN GROUP (ORDER BY CRD_PAN) AS ACN_ATP_ID,
LISTAGG(ACN_ACCOUNT_NUMBER, ',') WITHIN GROUP (ORDER BY CRD_PAN) AS ACN_ATP_ID
FROM
test
GROUP BY
CRD_PAN
Which gave me:
| CRD_PAN | ACN_ATP_ID | ACN_ATP_ID |
|:---------:|:----------:|:-----------------------:|
| 123456789 | 23, 24 | 99012345678,99112345678 |
So, I believe a solution could be:
WITH
test AS (
SELECT
'A' AS "actionIndicator", 'A' AS "target",
crdExpt.CRD_PAN,
acnExpt.ACN_ATP_ID, acnExpt.ACN_ACCOUNT_NUMBER,
FROM tbl1 crdExpt, tbl2 acnExpt, tbl3 crdAcnExpt
),
listdata AS (
SELECT
CRD_PAN,
LISTAGG(ACN_ATP_ID, ', ') WITHIN GROUP (ORDER BY CRD_PAN) AS ACN_ATP_ID,
LISTAGG(ACN_ACCOUNT_NUMBER, ',') WITHIN GROUP (ORDER BY CRD_PAN) AS ACN_ATP_ID
FROM
test
GROUP BY
CRD_PAN
)
SELECT * FROM listdata
The LISTAGG function allows you to move multiple rows into one, separated by some sort of character (I used ,), and a subquery was used to demonstrate capturing your data, aggregating it, and then returning it

Related

Sort each character in a string from a specific column in Snowflake SQL

I am trying to alphabetically sort each value in a column with Snowflake. For example I have:
| NAME |
| ---- |
| abc |
| bca |
| acb |
and want
| NAME |
| ---- |
| abc |
| abc |
| abc |
how would I go about doing that? I've tried using SPLIT and the ordering the rows, but that doesn't seem to work without a specific delimiter.
Using REGEXP_REPLACE to introduce separator between each character, STRTOK_SPLIT_TO_TABLE to get individual letters as rows and LISTAGG to combine again as sorted string:
SELECT tab.col, LISTAGG(s.value) WITHIN GROUP (ORDER BY s.value) AS result
FROM tab
, TABLE(STRTOK_SPLIT_TO_TABLE(REGEXP_REPLACE(tab.col, '(.)', '\\1~'), '~')) AS s
GROUP BY tab.col;
For sample data:
CREATE OR REPLACE TABLE tab
AS
SELECT 'abc' AS col UNION
SELECT 'bca' UNION
SELECT 'acb';
Output:
Similar implementation as Lukasz's, but using regexp_extract_all to extract individual characters in the form of an array that we later split to rows using flatten . The listagg then stitches it back in the order we specify in within group clause.
with cte (col) as
(select 'abc' union
select 'bca' union
select 'acb')
select col, listagg(b.value) within group (order by b.value) as col2
from cte, lateral flatten(regexp_extract_all(col,'.')) b
group by col;

Find difference between two consecutive rows from a result in SQL server 2008

I want to fetch the difference in "Data" column between two consecutive rows. For example, need Row2-Row1 ( 1902.4-1899.66) , Row 3-Row 2 and so on. The difference should be stored in a new column.
+----+-------+-----------+-------------------------+----+
| Name | Data |meter| Time |
+----+-------+-----------+-------------------------+----+
| Boiler-1 | 1899.66 | 1 | 5/16/2019 12:00:00 AM |
| Boiler-1 | 1902.4 | 1 | 5/16/2019 12:15:00 AM |
| Boiler-1 | 1908.1 | 1 | 5/16/2019 12:15:00 AM |
| Boiler-1 | 1911.7 | 6 | 5/16/2019 12:15:00 AM |
| Boiler-1 | 1926.4 | 6 | 5/16/2019 12:15:00 AM |
|
+----+-------+-----------+------------------------- +
Thing is the table structure that I have shown in the question, is actually obtained from two different tables. I mean, the above table is a result of a Select query to get data from two different tables. Goes like "select name, data, unitId, Timestamp from table t1 join table t2....." So is there anyway for me to calculate the difference in "data" column value between consecutive rows, without storing this above shown result into a table?
I use SQL 2008, so Lead/Lag functionality cannot be used.
The equivalent in SQL Server 2008 uses apply -- and it can be expensive:
with t as (
<your query here>
)
select t.*,
(t.data - tprev.data) as diff
from t outer apply
(select top (1) tprev.*
from t tprev
where tprev.name = t.name and
tprev.boiler = t.boiler and
tprev.time < t.time
order by tprev.time desc
) tprev;
This assumes that you want the previous row when the name and boiler are the same. You can adjust the correlation clause if you have different groupings in mind.
Not claiming that this is best, this is just another option in SQL SERVER < 2012. As from SQL Server 2012 its easy to do the same using LEAD and LAG default option added. Any way, for small and medium data set, you can consider this below script as well :)
Note: This is just an Idea for you.
WITH CTE(Name,Data)
AS
(
SELECT 'Boiler-1' ,1899.66 UNION ALL
SELECT 'Boiler-1',1902.4 UNION ALL
SELECT 'Boiler-1',1908.1 UNION ALL
SELECT 'Boiler-1',1911.7 UNION ALL
SELECT 'Boiler-1',1926.4
--Replace above select statement with your query
)
SELECT A.Name,A.Data,A.Data-ISNULL(B.Data,0) AS [Diff]
FROM
(
--Adding ROW_NUMBER Over (SELECT NULL) will keep the natural order
--of your data and will just add the row number.
SELECT *,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN FROM CTE
)A
LEFT JOIN
(
SELECT *,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN FROM CTE
) B
--Here the JOINING will take place on curent and next row for using ( = B.RN-1)
ON A.RN = B.RN-1

how to sum up database field value based on date in oracle

I have a table having two fields in it just like below given.
How to create a view that will sum TOT_HITS field's value till each date appeared in corresponding row in TODAY column like given below.
Use an analytic function to perform the query with only a single table scan:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE your_table( today, tot_hits ) As
SELECT DATE '2018-01-16', 5498 FROM DUAL UNION ALL
SELECT DATE '2018-01-17', 4235 FROM DUAL;
Query 1:
SELECT t.*,
SUM( tot_hits ) OVER ( ORDER BY today ) AS tot_hits_to_date
FROM your_table t
Results:
| TODAY | TOT_HITS | TOT_HITS_TO_DATE |
|----------------------|----------|------------------|
| 2018-01-16T00:00:00Z | 5498 | 5498 |
| 2018-01-17T00:00:00Z | 4235 | 9733 |
Just Try This
SELECT
Today,
Hits,
TillDate = Hits+NVL((SELECT SUM(Hits) FROM YourTable WHERE Today < T.Today),0)
FROM YourTable T

Redshift does not support rollup(), grouping() functions

Trying to convert Teradata bteq SQL scripts to redshift SQL. My current redshift Postgres version is 8.0.2, redshift version is 1.0.1499. The current version of redshift does not support rollup(), grouping() functions. How to overcome and resolve this scenario. What are the equivalent redshift functions for them? Could anyone explain with some examples how to do?
Sample Teradata SQL-
select
PRODUCT_ID,CUST_ID,
GROUPING (PRODUCT_ID),
GROUPING (CUST_ID),
row_number over (order by PRODUCT_ID,CUST_ID) AS "ROW_OUTPUT_NUM"
from products
group by rollup(PRODUCT_ID,CUST_ID);
Need to convert above sql query to Redshift
Implement the ROLLUP by hand
Once Redshift does not currently recognize the ROLLUP clause, you must implement this grouping technique in a hard way.
ROLLUP with 1 argument
With ROLLUP Ex. PostgreSQL
SELECT column1, aggregate_function(*)
FROM some_table
GROUP BY ROLLUP(column1)
The equivalent implementation
-- First, the same GROUP BY without the ROLLUP
-- For efficiency, we will reuse this table
DROP TABLE IF EXISTS tmp_totals;
CREATE TEMP TABLE tmp_totals AS
SELECT column1, aggregate_function(*) AS total1
FROM some_table
GROUP BY column1;
-- Show the table 'tmp_totals'
SELECT * FROM tmp_totals
UNION ALL
-- The aggregation of 'tmp_totals'
SELECT null, aggregate_function(total1) FROM tmp_totals
ORDER BY 1
Example output
Country | Sales
-------- | -----
Poland | 2
Portugal | 4
Ukraine | 3
null | 9
ROLLUP with 2 argument
With ROLLUP Ex. PostgreSQL
SELECT column1, column2, aggregate_function(*)
FROM some_table
GROUP BY ROLLUP(column1, column2);
The equivalent implementation
-- First, the same GROUP BY without the ROLLUP
-- For efficiency, we will reuse this table
DROP TABLE IF EXISTS tmp_totals;
CREATE TEMP TABLE tmp_totals AS
SELECT column1, column2, aggregate_function(*) AS total1
FROM some_table
GROUP BY column1, column2;
-- Show the table 'tmp_totals'
SELECT * FROM tmp_totals
UNION ALL
-- The sub-totals of the first category
SELECT column1, null, sum(total1) FROM tmp_totals GROUP BY column1
UNION ALL
-- The full aggregation of 'tmp_totals'
SELECT null, null, sum(total1) FROM tmp_totals
ORDER BY 1, 2;
Example output
Country | Segment | Sales
-------- | -------- | -----
Poland | Premium | 0
Poland | Base | 2
Poland | null | 2 <- sub total
Portugal | Premium | 1
Portugal | Base | 3
Portugal | null | 4 <- sub total
Ukraine | Premium | 1
Ukraine | Base | 2
Ukraine | null | 3 <- sub total
null | null | 9 <- grand total
If you use the UNION technique that others have pointed to, you'll be scanning the underlying table multiple times.
If the fine-level GROUPing actually results in a significant reduction in the data size, a better solution may be:
create temp table summ1
as
select PRODUCT_ID,CUST_ID, ...
from products
group by PRODUCT_ID,CUST_ID;
create temp table summ2
as
select PRODUCT_ID,cast(NULL as INT) AS CUST_ID, ...
from products
group by PRODUCT_ID;
select * from summ1
union all
select * from summ2
union all
select cast(NULL as INT) AS PRODUCT_ID, cast(NULL as INT) AS CUST_ID, ...
from summ2

Query to Calculate totalcost based on description

I have question regarding sql script. I have a custom view, below is the data
================================================================================
ql_siteid | ql_rfqnum | ql_vendor | ql_itemnum | totalcost_option | description
================================================================================
SGCT | 1002 | VND001 | ITEM002 | 12500 |
SGCT | 1002 | VND001 | ITEM001 | 1350 |
SGCT | 1002 | VND002 | ITEM002 | 11700 |
SGCT | 1002 | VND002 | ITEM001 | 1470 | Nikon
SGCT | 1002 | VND002 | ITEM001 | 1370 | Asus
================================================================================
And i want the result like below table:
VND001 = 13850
VND002 = Asus 13070, Nikon 13170
where 13850 is come from 12500+1350, 13070 is come from 11700+1370 and 13170 is come from 11700+1470. All the cost is calculated from totalcost_option and will be group based on vendor
So please give me some advise
To get the exact output you required use the following statement: (where test_table is your table name):
SELECT ql_vendor || ' = ' ||
LISTAGG( LTRIM(description||' ')||totalcost, ', ')
WITHIN GROUP (ORDER BY description)
FROM (
WITH base_cost AS (
SELECT ql_vendor, SUM(totalcost_option) sumcost
FROM test_table WHERE description IS NULL
GROUP BY ql_vendor
),
individual_cost AS (
SELECT ql_vendor, totalcost_option icost, description
FROM test_table WHERE description IS NOT NULL
)
SELECT ql_vendor, sumcost + NVL(icost,0) totalcost, description
FROM base_cost LEFT OUTER JOIN individual_cost USING (ql_vendor)
)
GROUP BY ql_vendor;
Details:
The Outer select just takes the individual rows and combines them to the String-representation. Just remove it and you will get a single row for each vendor/description combination.
The inner select joins two sub-select. The first one gets the base_cost for each vendor by summing up all rows without a description. The second gets the individual cost for each row with a description.
The join combines them - and left outer joins displays the base_cost for vendors which don't have a matching row with description.
Assuming you have a version of Oracle 11g or later, using ListAgg will do the combination of the comma separated tuples for you. The rest of the string is generated by simply concatenating the components together from an intermediate table - I've used a derived table (X) here, but you could also use a CTE.
Edit
As pointed out in the comments, there's a whole bunch more logic missing around the Null description items I missed in my original answer.
The following rather messy query does project the required result, but I believe this may be indicative that a table design rethink is necessary. The FULL OUTER JOIN should ensure that rows are returned even if there are no base / descriptionless cost items for the vendor.
WITH NullDescriptions AS
(
SELECT "ql_vendor", SUM("totalcost_option") AS "totalcost_option"
FROM MyTable
WHERE "description" IS NULL
GROUP BY "ql_vendor"
),
NonNulls AS
(
SELECT COALESCE(nd."ql_vendor", mt."ql_vendor") AS "ql_vendor",
NVL(mt."description", '') || ' '
|| CAST(NVL(mt."totalcost_option", 0)
+ nd."totalcost_option" AS VARCHAR2(30)) AS Combined
FROM NullDescriptions nd
FULL OUTER JOIN MyTable mt
ON mt."ql_vendor" = nd."ql_vendor"
AND mt."description" IS NOT NULL
)
SELECT x."ql_vendor" || ' = ' || ListAgg(x.Combined, ', ')
WITHIN GROUP (ORDER BY x.Combined)
FROM NonNulls x
WHERE x.Combined <> ' '
GROUP BY x."ql_vendor";
Updated SqlFiddle here
Your logic seems to be: If description is always NULL for a vendor then you want that as the total cost. Otherwise, you want the NULL value of description added to all the other values. The following query implements this logic. The output is in a different format from your answer -- this format is more consistent with a SQL result set:
select ql_vendor,
(sum(totalcost_option) +
(case when description is not null then max(totalcost_null) else 0 end)
)
from (select v.*, max(description) over (partition by ql_vendor) as maxdescription,
sum(case when description is null then totalcost_option else 0 end) over (partition by ql_vendor) as totalcost_null
from view v
) t
where maxdescription is null or description is not null
group by ql_vendor, description;