Query to Calculate totalcost based on description - sql

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;

Related

max DISTINCT returns multiple rows

I am working on an sql script which is executed by a .bat daily and outputs a list of IDs, the date of access, and their level.
While it returns what I want, mostly, I noticed that some of the outputted rows are duplicates.
Could someone please help me modify my script so that it outputs only one date (the latest) for each ID?
Thank you very much.
SELECT T.ID
+ ';' + substring(convert(char, convert(date , T.QDATE ) ), 1, 10)
+ ';' + A.[LEVEL]
FROM
(SELECT CID AS 'ID',
MAX (DISTINCT EDATE) QDATE
FROM [XXXXXXXXXXXXXXXXXXXXXXXX].[XXX].[XXXXXXXXXXXXXXX]
GROUP BY CID
) T ,
[XXXXXXXXXXXXXXXXXXXXXXXX].[XXX].[XXXXXXXXXXXXXXX] A
WHERE
T.ID = A.CID
AND T.QDATE = A.EDATE
ORDER BY A.[CID]
EDIT: I've added a bit of sample data from table A
| QID | CID | LEVEL | EDATE | OP | STATUS |
|-----|-----|-------|------------|----|--------|
| 1 |00001| LOW | 2021-07-16 | 01 | CLOSED |
| 2 |00001| LOW | 2021-07-16 | 01 | CLOSED |
| 3 |00002| MEDIUM| 2021-07-16 | 01 | CLOSED |
| 4 |00003| LOW | 2021-07-16 | 01 | CLOSED |
In this bit of data, my output contains both rows for CID 00001. Looking for a way to delete the duplicate rows from the output and not make any modifications to the db itself.
Your data is showing only a date portion context of your EDate field. Is is really a date or date/time. It would suggest date/time due to your call to CONVERT( Date, T.QDate) in the query. Your sample data SHOULD show context of time, such as to the second. I would not suspect there are multiple records with the same time-stamp to the second, but its your data.
The DISTINCT should not be at the inner query, but the OUTER query, but IF you have multiple entries for the same CID AT the exact same time AND there are multiple values for Leve, OP, and Status, then you will get multiple.
However, if the values are the same across-the-board as in your sample data, you SHOULD be good with
SELECT DISTINCT
T.ID + ';'
+ substring(convert(char, convert(date , T.QDATE ) ), 1, 10)
+ ';' + A.[LEVEL]
FROM
( SELECT
CID AS 'ID',
MAX (EDATE) QDATE
FROM
[XXXXXXXXXXXXXXXXXXXXXXXX].[XXX].[XXXXXXXXXXXXXXX]
GROUP BY
CID ) T
JOIN [XXXXXXXXXXXXXXXXXXXXXXXX].[XXX].[XXXXXXXXXXXXXXX] A
ON T.ID = A.CID
AND T.QDATE = A.EDATE
ORDER BY
A.CID
The distinct keyword in this context means only give me 1 unique record per each combination of all columns. So in your sample data, you would only have 1 record result for the CID = '00001'.

"Transpose" of a table in Oracle

I'm having quite a bit of trouble figuring out exactly how to rearrange a table. I have a large table that looks something like this:
+--------+-----------+
| NAME | ACCOUNT # |
+--------+-----------+
| Nike | 87 |
| Nike | 12 |
| Adidas | 80 |
| Adidas | 21 |
+--------+-----------+
And I want to rearrange it to look like this:
+------+--------+
| Nike | Adidas |
+------+--------+
| 87 | 80 |
| 12 | 21 |
+------+--------+
But I can't seem to figure out how. I tried using PIVOT, but that only works with aggregate functions. I tried using a FOR LOOP as well, but couldn't get it work just right.
You can do this in several ways, but all being by enumerating the rows. Here is an example using conditional aggregation:
select max(case when name = 'Nike' then account end) as Nike,
max(case when name = 'Adidas' then account end) as Adidas
from (select t.*,
row_number() over (partition by name order by account desc) as seqnum
from t
) t
group by seqnum;
Consider again a pivot solution but first adding a rownumber for rolling Name group counts. Below assumes an autonumber ID field:
SELECT * FROM
(
SELECT Name, "Account #",
(ROW_NUMBER() OVER(PARTITION BY Name ORDER BY ID)) GrpRowNum
/* ALT: (SELECT Count(*) FROM Table1 sub
* WHERE sub.Name = Table1.Name AND sub.ID <= Table1.ID) GrpRowNum */
FROM Table1
)
PIVOT
(
SUM("Account #")
FOR Name IN ('Nike', 'Adidas')
)
ORDER BY RowNum;
However, for your ~200 items, you cannot easily render the Pivot's IN clause without various workarounds including PIVOT XML output or stored procedures with PL/SQL. Similarly, you could use general purpose coding (Java, PHP, Python, R) to retreive SELECT DISTINCT Name FROM Table1 resultset in vector/array, joining element values (collapsing or imploding arrays) with quotes and comma separators, and dropping the entire list in IN clause.

Displaying records in table after join and group by a particular column

I have two tables which contain some data :
Table1 : abc
||entity_id| entity_title| cust_code| cust_acc_no ||
----------------------------------------------------------
|| AB101Z | IND | 101Z | 1234 ||
|| AB101Z | PAK | 101Z | 1357 ||
|| CD101Y | IND | 101Y | 2468 ||
|| EF101X | IND | 101X | 3579 ||
|| JK201N | LKO | 201N | 5678 ||
Table2 : def
||entity_title| in_count| out_quant||
---------------------------------------------
|| IND | 10 | 7 ||
|| LKO | 7 | 7 ||
|| PAK | 5 | 2 ||
joined Table : abcdef
||entity_id| entity_title| cust_code ||
--------------------------------------------------
|| AB101Z | INDPAK | 101Z ||
|| CD101Y | INDPAK | 101Y ||
|| EF101X | INDPAK | 101X ||
I want to join tables abc and def which would be resultant in table abcdef.
While joining both tables and records would be grouped by entity_title. The joining condition would be such that in_count!=out_count. For example, in such situation LKO as entity_title would not be part of resultant table.
I need to replace the entity_title records with matching condition by a third record signifying a matched record, for ex, INDPAK is the replacement for all the records whether those are for IND and PAK both or either of those two.
I tried to come up with a solution but was not able to form a single query.Thanks in advance for any solution suggested.
This solution avoids hard-coding. It comprises three steps:
The first sub-query identifies common (ENTITY_ID, CUST_CODE) combinations for ENTITY_TITLEs with different counts.
The second sub-query identifies the ENTITY_TITLEs which own those combos and derives a compound ENTITY_TITLE for them. (It uses LISTAGG, which is an 11gR2 thing, but there are workarounds for string concatenation in earlier versions of the database).
The outer query produces the required output, substituting the compound ENTITY_TITLE for the original ENTITY_TITLE.
Here is the whole thing. I admit I don't like its reliance on DISTINCT clauses to get the required output but the joining rules produce unwanted duplicates.
with prs as
( select abc.entity_id
, abc.cust_code
from abc
join def
on abc.entity_title = def.entity_title
where def.in_count != def.out_quant
group by abc.entity_id, abc.cust_code having count(*) > 1
)
, ent as
( select distinct abc.entity_title
, listagg(abc.entity_title)
within group (order by abc.entity_title)
over (partition by prs.entity_id, prs.cust_code) as cmp_entity_title
from abc
join prs
on abc.entity_id = prs.entity_id
and abc.cust_code = prs.cust_code
)
select distinct abc.entity_id
, ent.cmp_entity_title
, abc.cust_code
from ent
join abc
on abc.entity_title = ent.entity_title
order by abc.entity_id
, abc.cust_code
/
Please note that the output is strongly dependent on the initial condition of the data. If you look at my inevitable SQL Fiddle, you will see I have included additional rows into the set-up.
The first commented out ABC record ...
/* insert into abc values ('AB101Z','BAN','101Z', 5151 ); */
.. creates a matched triple, BANINDPAK, which replaces all occurrences of BAN, IND or PAK. This is a logical outcome of your rules and one which I assume you expect.
The other commented out ABC record ...
/* insert into abc values ('JK101X','TIB','101K', 3434 ); */
... creates a second matched pair, PAKTIB, whose presence generates multiple results for the PAK entity's records. This is also a logical outcome of your rules but perhaps a less-expected one.
here is something that might help you :
select * from
(
select t1.entity_id, case when t1.entity_title in('IND','PAK') then 'INDPAK' else t1.entity_title end as entity_title, t1.cust_code
from abc t1 join def t2
on t1.entity_title = t2.entity_title
where t2.in_count <> t2.out_count
) t
group by t.entity_id, t.entity_title, t.cust_code

SQL concat rows with same name value

Let say I got table like that
Name | Stage | Date
-------------------
A | 1st | 03092014
A | 2nd | 04092014
A | 3rd | 05092014
B | 1st | 06092014
B | 2nd | 08092014
C | 1st | 03092014
I wonder how to write SQL code wich would concat rows with same names and I will get something like that
Name | Stage | Date
----------------------+-----------------------------
A | 1st , 2nd, 3rd | 03092014 04092014 05092014
B | 1st, 2nd | 06092014 08092014
C | 1st | 03092014
Do I need to run through table with for cycle or is there better way to do that?
UPD:
I found out that I need to use this queries in Excel
You can use GROUP_CONCAT for this:
SELECT Name
, GROUP_CONCAT(Stage) AS Stages
, GROUP_CONCAT(Date) AS Dates
FROM my_table
GROUP BY Name;
With respect to your question - I am assuming you are using MS SQL Server 2008 or higher to get he desired output
I would suggest to use CROSS APPLY here to concat the data -
Assumed Your Table name - temptable
SELECT distinct tblMain.Name, substring(stages, 1, len(stages)-1) as [Stage],substring(dates, 1, len(dates)-1) as [Date]
FROM temptable tblMain
CROSS APPLY (
SELECT LTRIM(RTRIM(Stage)) + ','
FROM temptable tblDup1 WITH(NOLOCK)
WHERE tblDup1.Name= tblMain.Name
FOR XML PATH('')
) t1 (stages)
CROSS APPLY (
SELECT LTRIM(RTRIM(Date)) + ' '
FROM temptable tblDup2 WITH(NOLOCK)
WHERE tblDup2.Name= tblMain.Name
FOR XML PATH('')
) t2 (dates)
Working FIDDLE OUTPUT

Access query to grab +5 or more duplicates

i have a little problem with an Access query ( dont ask me why but i cannot use a true SGBD but Access )
i have a huge table with like 920k records
i have to loop through all those data and grab the ref that occur more than 5 time on the same date
table = myTable
--------------------------------------------------------------
| id | ref | date | C_ERR_ANO |
--------------------------------------------|-----------------
| 1 | A12345678 | 2012/02/24 | A 4565 |
| 2 | D52245708 | 2011/05/02 | E 5246 |
| ... | ......... | ..../../.. | . .... |
--------------------------------------------------------------
so to resume it a bit, i have like 900000+ records
there is duplicates on the SAME DATE ( oh by the way there is another collumn i forgot to add that have C_ERR_ANO as name)
so i have to loop through all those row, grab each ref based on date AND errorNumber
and if there is MORE than 5 time with the same errorNumber i have to grab them and display it in the result
i ended up using this query:
SELECT DISTINCT Centre.REFERENCE, Centre.DATESE, Centre.C_ERR_ANO
FROM Centre INNER JOIN (SELECT
Centre.[REFERENCE],
COUNT(*) AS `toto`,
Centre.DATESE
FROM Centre
GROUP BY REFERENCE
HAVING COUNT(*) > 5) AS Centre_1
ON Centre.REFERENCE = Centre_1.REFERENCE
AND Centre.DATESE <> Centre_1.DATESE;
but this query isent good
i tried then
SELECT DATESE, REFERENCE, C_ERR_ANO, COUNT(REFERENCE) AS TOTAL
FROM (
SELECT *
FROM Centre
WHERE (((Centre.[REFERENCE]) NOT IN (SELECT [REFERENCE]
FROM [Centre] AS Tmp
GROUP BY [REFERENCE],[DATESE],[C_ERR_ANO]
HAVING Count(*)>1 AND [DATESE] = [Centre].[DATESE]
AND [C_ERR_ANO] = [Centre].[C_ERR_ANO]
AND [LIBELLE] = [Centre].[LIBELLE])))
ORDER BY Centre.[REFERENCE], Centre.[DATESE], Centre.[C_ERR_ANO])
GROUP BY REFERENCE, DATESE, C_ERR_ANO
still , not working
i'm struggeling
Your group by clause needs to include all of the items in your select. Why not use:
select Centre.DATESE, Centre.C_ERR_ANO, Count (*)
Group by Centre.DATESE, Centre.C_ERR_ANO
HAVING COUNT (*) > 5
If you need other fields then you can add them, as long as you ensure the same fields appear in the select as the group by.
No idea what is going on with the formatting here!