SQL concat rows with same name value - sql

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

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;

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'.

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

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;

Get previous and next row from rows selected with (WHERE) conditions

For example I have this statement:
my name is Joseph and my father's name is Brian
This statement is splitted by word, like this table:
------------------------------
| ID | word |
------------------------------
| 1 | my |
| 2 | name |
| 3 | is |
| 4 | Joseph |
| 5 | and |
| 6 | my |
| 7 | father's |
| 8 | name |
| 9 | is |
| 10 | Brian |
------------------------------
I want to get previous and next word of each word
For example I want to get previous and next word of "name":
--------------------------
| my | name | is |
--------------------------
| father's | name | is |
--------------------------
How could I get this result?
you didn't specify your DBMS, so the following is ANSI SQL:
select prev_word, word, next_word
from (
select id,
lag(word) over (order by id) as prev_word,
word,
lead(word) over (order by id) as next_word
from words
) as t
where word = 'name';
SQLFiddle: http://sqlfiddle.com/#!12/7639e/1
Why did no-body give the simple answer?
SELECT LAG(word) OVER ( ORDER BY ID ) AS PreviousWord ,
word ,
LEAD(word) OVER ( ORDER BY ID ) AS NextWord
FROM words;
Without subqueries:
SELECT a.word
FROM my_table AS a
JOIN my_table AS b
ON b.word = 'name' AND abs(a.id - b.id) <= 1
ORDER BY a.id
Use Join to get the expected result for SQL Server 2005 plus.
create table words (id integer, word varchar(20));
insert into words
values
(1 ,'my'),
(2 ,'name'),
(3 ,'is'),
(4 ,'joseph'),
(5 ,'and'),
(6 ,'my'),
(7 ,'father'),
(8 ,'name'),
(9 ,'is'),
(10,'brian');
SELECT A.Id , C.word AS PrevName ,
A.word AS CurName ,
B.word AS NxtName
FROM words AS A
LEFT JOIN words AS B ON A.Id = B.Id - 1
LEFT JOIN words AS C ON A.Id = C.Id + 1
WHERE A.Word = 'name'
Result:
Fiddler Demo
Try this
SELECT *
FROM tablename a
WHERE ID IN(SELECT ID - 1
FROM tablename
WHERE word = 'name') -- will fetch previous rows of word `name`
OR ID IN(SELECT ID + 1
FROM tablename
WHERE word = 'name') -- will fetch next rows of word `name`
OR word = 'name' -- to fetch the rows where word = `name`
Here's a different approach, if you want the selects to be fast. It takes a bit of preparation work.
Create a new column (e.g. "phrase") in the database that will contain the words
you want. (i.e. the previous, the current and next).
Write a trigger that on insert appends the new word to the previous
row's phrase and prepends the previous row's word to the new row's word and fills
phrase.
If the individual words can change, you'll need a trigger on update to keep the phrase in sync.
Then just select the phrase. You get much better speed, but at the cost of extra storage and slower insert and harder maintainability. Obviously you have to update the phrase column for the existing records, but you have the SQL to do that in the other answers.