Related
I have a query (several CTEs) that get data from different sources. The output has a column name, but I would like to map this nameg to a more user-friendly name.
Id
name
1
buy
2
send
3
paid
I would like to hard code somewhere in the query (in another CTE?) a mapping table. Don't want to create a separate table for it, just plain text.
name_map=[('buy', 'Item purchased'),('send', 'Parcel in transit'), ('paid', 'Charge processed')]
So output table would be:
Id
name
1
Item purchased
2
Parcel in transit
3
Charge processed
In Trino I see the function map_from_entries and element_at, but don't know if they could work in this case.
I know "case when" might work, but if possible, a mapping table would be more convenient.
Thanks
As a simpler alternative to the other answer, you don't actually need to create an intermediate map using map_from_entries and look up values using element_at. You can just create an inline mapping table with VALUES and use a regular JOIN to do the lookups:
WITH mapping(name, description) AS (
VALUES
('buy', 'Item purchased'),
('send', 'Parcel in transit'),
('paid', 'Charge processed')
)
SELECT description
FROM t JOIN mapping ON t.name = mapping.name
(The query assumes your data is in a table named t that contains a column named name to use for the lookup)
Super interesting idea, and I think I got it working:
with tmp as (
SELECT *
FROM (VALUES ('1', 'buy'),
('2', 'send'),
('3', 'paid')) as t(id, name)
)
SELECT element_at(name_map, name) as name
FROM tmp
JOIN (VALUES map_from_entries(
ARRAY[('buy', 'Item purchased'),
('send', 'Parcel in transit'),
('paid', 'Charge processed')])) as t(name_map) ON TRUE
Output:
name
Item purchased
Parcel in transit
Charge processed
To see a bit more of what's happening, we can look at:
SELECT *, element_at(name_map, name) as name
id
name
name_map
name
1
buy
{buy=Item purchased, paid=Charge processed, send=Parcel in transit}
Item purchased
2
send
{buy=Item purchased, paid=Charge processed, send=Parcel in transit}
Parcel in transit
3
paid
{buy=Item purchased, paid=Charge processed, send=Parcel in transit}
Charge processed
I'm not sure how efficient this is, but it's certainly an interesting idea.
OK, I'm quite new to SQL and didn't get to much training!
I'm using SSMS to create stored procedures and open them in Excel.
The code below work just fine, but I need to add a drill down to get more info on some lines.
We need to follow what was invoice and paid on a batch of contracts for our project. Each contract have multiple lines with a description and a couple of other fields(reference, line #, G/L # etc). Plus we have the value of the line, the amount that was invoice for this line and the amount that was paid.
Main table 'CSCOMVTL' have the basic infos including the base value and the invoice amount, but not the paid amount.
'JRAPRVTL' is the list of all invoices with; invoice no., invoice date, invoiced amount and paid amount that we may need to see.
So for each base line, we need a +/- button to show/hide details of the invoice.
Invoice amount and paid amount could be from a rollup, but the number and date won't be on the parent line. If they could be in the same column as other field not needed it would be great, but I could live with 2 extra columns.
Thanks!
ALTER PROCEDURE [dbo].[marpt_qmd_AccPmt_DetailsST]
#contrat varchar(30), #projet varchar(30)
AS
BEGIN
CREATE TABLE #RPT
(
Ligne INT,
Lien INT,
Act VARCHAR (10),
Descr VARCHAR (90),
MntBase DECIMAL (20,2),
MntFact DECIMAL (20,2),
Modif VARCHAR (40),
Descr3 VARCHAR (90),
Lien2 INT,
MntPy DECIMAL (20,2) default '0',
)
INSERT INTO #RPT (Ligne, Lien, Act, Descr, MntBase, MntFact)
SELECT ROW, DETAILCHANGEORDERCOU, ACTIVITY, DESCRIPTION, AMOUNT, INVOICE
FROM cscomvtl
WHERE PROJECTNUMBER = #projet
and LTRIM(RTRIM(PONUMBER)) = #contrat
UPDATE #RPT
SET Modif=m.CHANGEORDERNUMBER, Descr3=m.DESCRIPTION, Lien2=m.CHANGEORDERCOUNTER
FROM cscomac m, #RPT r
where m.COUNTER=r.Lien
UPDATE #RPT
SET MntPy=payment
FROM #RPT r, (select POLINE, sum(payment) payment from jraprvtl where PROJECTNO=#projet
and LTRIM(RTRIM(PURCHASEORDER))=#contrat group by POLINE) d
where r.Ligne=d. POLINE
SELECT
Ligne as 'Ligne',
Act as 'Act.',
Descr as 'Description 1',
MntBase as '$ Base',
MntFact as '$ Invoiced',
Modif as 'Num. Modif.',
Descr3 as 'Description 2',
MntPy as '$ Paid'
FROM #RPT
Order by Ligne
Drop table #RPT
First off, take the time & learn SQL. It's an invaluable tool in your toolkit!
Okay, enough of the lecture. In looking through your code, you don't seem to really need the temp table #rpt, you just need to understand JOINs. Hopefully this SQL will get you what you are looking for:
SELECT vtl.ROW AS Ligne, vtl.DETAILCHANGEORDERCOU AS Lein, vtl.ACTIVITY AS Act,
vtl.DESCRIPTION AS Descr, vtl.AMOUNT AS MntBase, vtl.INVOICE AS MntFact,
mac.CHANGEORDERNUMBER AS Modif, mac.DESCRIPTION AS Descr3, mac.CHANGEORDERCOUNTER AS Lien2,
sum(jrap.payment) AS MntPy
FROM cscomvtl AS vtl
LEFT OUTER JOIN cscomac AS mac
ON vtl.detailchangeordercou = mac.counter
LEFT OUTER JOIN jraprvtl AS jrap
ON vtl.row = jrap.poline
WHERE projectnumber = #projet AND LTRIM(RTRIM(ponumber)) = #contrat
GROUP BY vtl.row, vtl.detailchangeordercou, vtl.activity, vtl.description, vtl.amount,
vtl.invoice, mac.changeordernumber, mac.description, mac.changeordercounter
You will likely have to tweak it to fit what you're trying to do in Excel, since you really didn't give much to go on there.
I asked this question few days ago, but it involves a bit deeper answer so it was suggested I create a brand new one, so here it goes...
Disclaimer: I cannot create any custom DB objects (functions, SP's, views etc.), so everything needs to be in-line inside a SQL query.
I'm querying Audit table which for the simplicity of this question has following fields:
AttributeMask
ChangedData
CreatedOn
ObjectId
Each record in a DB may have multiple Audit records associated with it. Every time a change is made to a DB record, it will create a record in the Audit table with specific ObjectID that will point to the source record, CreatedOn that will have a DateTime of the change, AttributeMask with list of AttributeId's that have been changed when SAVE was executed (note, there may be multiple fields changed at once) and ChangedData will actually have the data that's been changed (pre-changed values). One field can of course be changed multiple times and if it's the case, multiple Audit records for this field will exist (different CreatedOn values). I need to find what some (not all) fields from the source record looked like at a specific date.
I can run query below:
select a1.ChangeData as ChangedData1, a1.AttributeMask as AttributeMask2, a2.ChangeData as ChangedData2, a2.AttributeMask as AttributeMask2
from Table1 t
join audit a1 on a1.AuditId =
(select top 1 a.auditid from audit a where a.objecttypecode = 3
and a.objectid = T.ObjectId
and a.AttributeMask like '%,10192,%'
and a.CreatedOn <= '8-16-2018'
order by a1.CreatedOn desc)
join audit a2 on a2.AuditId =
(select top 1 a.auditid from audit a where a.objecttypecode = 3
and a.objectid = T.ObjectId
and a.AttributeMask like '%,10501,%'
and a.CreatedOn <= '8-16-2018'
order by a1.CreatedOn desc)
where t.ObjectID = SomeGuidValue
This query is looking for the latest change to 2 fields (10192 and 10501) which happened before 8-16-2018. It returns the following data (I added 3rd record to illustrate all possible cases):
ChangeData1 AttributeMask1 ChangeData2 AttributeMask2
NULL NULL True~~True~1904~~~15.8700000000~4760~30000~590~12000~0~390~1904~False~200~ ,10499,10604,10501,10436,10491,10490,10459,10099,10319,10253,10433,10031,10091,10020,10265,10008,10509,
~True~5.56~~House~~200000~ ,10030,10432,10435,197,10099,10192,198, False~1170~600~0~Complete~True~1770~ ,10501,10091,10008,10020,10570,10499,10253,10715,
~~~~200001~ ,10432,10435,197,10099,10192,198, True~2~True~~0~~~100.0000000000~1~business,96838c4f-e63c-e011-9a14-78e7d1644f78~~0~~~~0~False~~1~ ,10499,10509,10501,10203,10436,10491,10490,10459,10099,10157,10253,10433,10715,10031,10091,10020,10265,10008,10319,10699,
This means that 1st record has change to field 10501 only, 2nd record has change to 10192 only and 3rd record has changes to both 10192 and 10501 fields.
AttributeMask field has comma delimited list of all FieldID's that have been changed (note that it starts and ends with comma).
ChangedData field has tilde (~) delimited list of data that's been changed. Each entry in AttributeMask corresponds to entry in ChangedData. For example, if I wanted to see what data was in 10501 field in 1st record, I would need to determine what entry # 10501 is in AttributeMask field (it's #3 in the list) and then I would need to find out what data is in entry #3 in ChangedData field (it's TRUE) and if I wanted to see what was in 2nd record for Field 10192 I'd see what index it has in AttributeMask (it's #6) and its corresponding value in ChangedData field is 2000000.
I need to somehow extract this data in the same query. I was helped with some samples on how this could be done, but I failed to ask the right question in the beginning (thought it would be simpler than explaining all this).
What I need this query to return is something like this:
ChangeData1 AttributeMask1 ChangeData2 AttributeMask2
NULL NULL TRUE 10501
200000 10192 FALSE 10501
200001 10192 TRUE 10501
I hope this is clear now.
As told in my comments you are better off to deal with a set, then working with a broader and broader list with name-numbered columns.
Try to provide your initial input set in the format like the following mockup table:
There is a running ID, your ObjectID, the code you are looking for and the both strings. I inserted the data as provided by you, but not side-by-side:
DECLARE #tbl TABLE(ID INT IDENTITY, CodeId INT,ObjectId INT, ChangeData VARCHAR(1000), AttributeMask VARCHAR(1000));
INSERT INTO #tbl VALUES
(10192,1,NULL,NULL)
,(10501,1,'True~~True~1904~~~15.8700000000~4760~30000~590~12000~0~390~1904~False~200~',',10499,10604,10501,10436,10491,10490,10459,10099,10319,10253,10433,10031,10091,10020,10265,10008,10509,')
,(10192,2,'~True~5.56~~House~~200000~',',10030,10432,10435,197,10099,10192,198,')
,(10501,2,'False~1170~600~0~Complete~True~1770~',',10501,10091,10008,10020,10570,10499,10253,10715,')
,(10192,3, '~~~~200001~',',10432,10435,197,10099,10192,198,')
,(10501,3,'True~2~True~~0~~~100.0000000000~1~business,96838c4f-e63c-e011-9a14-78e7d1644f78~~0~~~~0~False~~1~',',10499,10509,10501,10203,10436,10491,10490,10459,10099,10157,10253,10433,10715,10031,10091,10020,10265,10008,10319,10699,');
--The query will cast the strings to XML in order to grab into it by their position index
--Then all codes are taken and numbered as derived list.
--According to the found position the corresponding value is taken
SELECT t.ID
,t.ObjectId
,t.CodeId
,t.ChangeData
,t.AttributeMask
,Casted.ValueXml.value('/x[sql:column("PartIndex")][1]','nvarchar(max)') ValueAtCode
FROM #tbl t
CROSS APPLY
(
SELECT CAST('<x>' + REPLACE(t.AttributeMask,',','</x><x>') + '</x>' AS XML).query('/x[text()]') AS CodeXml
,CAST('<x>' + REPLACE(t.ChangeData,'~','</x><x>') + '</x>' AS XML) AS ValueXml
) Casted
CROSS APPLY(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
,x.value('text()[1]','nvarchar(max)') AS CodePart
FROM Casted.CodeXml.nodes('/x') A(x)
) CodeDerived
WHERE CodeDerived.CodePart=t.CodeId;
the result
ID ObjectId CodeId ValueAtCode
2 1 10501 True
3 2 10192
4 2 10501 False
5 3 10192 200001
6 3 10501 True
But this will be slooooow...
UPDATE
Your whole approach is not set-based. The following is completely untested, I don't have your database, but will point to a set-based solution.
DECLARE #Codes TABLE(CodeID INT);
INSERT INTO #Codes VALUES(10192),(10501);
select t.SomeIdOfYourMainTable
,c.CodeID
,a1.ChangeData
,a1.AttributeMask
from Table1 t
CROSS JOIN #Codes c --will repeat the result for each value in #Codes
CROSS APPLY
(
select top 1 a.ChangeData
,a.AttributeMask
from [audit] a
where a.objecttypecode = 3
and a.objectid = t.ObjectId
and a.AttributeMask like CONCAT('%,',c.CodeID,',%')
and a.CreatedOn <= '20180816' --use culture independant format!!!
order by a.CreatedOn desc
) a1;
This allows you to insert as many codes as you want (no need to repeat any join) and it will return a set similar to my example above.
If you need further help with this: Please close this question and start a new question with a fully working, stand-alone MCVE to reproduce your case.
Let's say I have a free text field called 'Note' and contains "ABC:5/52 , *back, orders received"
How do I extract '5/52' and 'back' and place them in two separate columns?
Here's what I wanted to achieve
QUERY:-
SELECT *, SUBSTRING(Note, CHARINDEX(':', Note)+1, 4) as ABC,
SUBSTRING(Note, CHARINDEX('*', Note)+1, 4) as Ret_Stat
, CHARINDEX(':', Note) AS [Colon Index]
FROM [AdventureWorks2012].[Sales].[Comments]
RESULT:-
Note ABC Ret_Stat
ABC:3/52, To give more explanation, *back 3/52 back
ABC:3wks, To debrief, *back, r/v 3wks back
ABC:13/09/16, see cm, *back, new referral 13/0 back
My issue is i wanted to extract 3/52, 3wks, and 13/09/16 but my end result's only 13/10.
I'd like to ask how to achieve this? as the condition of extraction may vary from 4 to 8 characters after ABC: and the table contains thousands of rows of data
Need advice. THank you.
Here's an example of what you want to do. You may have to modify this a little as I don't have a lot of sample data to go on.
Test Data
IF OBJECT_ID('tempdb..#TempData') IS NOT NULL DROP TABLE #TempData
GO
CREATE TABLE #TempData (Notes varchar(100))
INSERT INTO #TempData (Notes)
VALUES
('This is the first "ABC:5/52 string *back, orders received')
,('*back, orders receivedThis"ABC:5/52 string is the second one')
,('You guessed it, this *back, orders received is the third "ABC:5/52 string')
Query
SELECT
CHARINDEX('*',Notes) AsteriskLocation
,SUBSTRING(Notes,CHARINDEX('*',Notes)+1,4) AfterAsterisk
,CHARINDEX(':',Notes) ColonLocation
,SUBSTRING(Notes,CHARINDEX(':',Notes)+1,4) AfterColon
FROM #TempData
Result
AsteriskLocation AfterAsterisk ColonLocation AfterColon
36 back 23 5/52
1 back 31 5/52
22 back 62 5/52
I've left the locations separately so that you can see how they're used in the query. You could search for strings too using the same method.
SELECT DISTINCT Invoice.InvNo, Invoice.OrderNo, Part.PartNo,
orders.orddate AS Order_Date, Invoice.InvDate AS Bill_Date,
MiscChg.Descr, MiscChg.RegFee, Invoice.InvAmt,
Orders.ClaimNo, Firm.FirmName AS Ordering_Firm,
**oppatty.attyid(WHERE oppatty.attyfor = 13)**, Location.Name1 AS Location
The bolded section is the part I'm having trouble with. I know what I have isn't right, but it demonstrates what I would like to accomplish. In the oppatty table, there could be several items listed. I want it to only display "AttyID for the entry that has an ATTYFOR = 13".
Hope this make sense, thanks
Jack
You need to add a CASE WHEN to the select statement.
SELECT DISTINCT
Invoice.InvNo,
Invoice.OrderNo,
Part.PartNo,
orders.orddate AS Order_Date,
Invoice.InvDate AS Bill_Date,
MiscChg.Descr,
MiscChg.RegFee,
Invoice.InvAmt,
Orders.ClaimNo,
Firm.FirmName AS Ordering_Firm,
CASE WHEN oppatty.AttyFor = 13
THEN oppatty.AttyId
ELSE '' END AS attyfor,
Location.Name1 AS Location
FROM
.........
This will display the AttyId field when the row's AttyFor field is equal to 13 and show an empty string when it's not.
Your query has no from or where clause and your question is a bit jumbled, but even so, I think I understand what you want to do. Assuming it's acceptable to fill the "AttyID" values with null where "AttyFor" isn't equal to 13, then you could just use a case statement. Try something like this
select
stuff.things,
case
where oppatty.attyfor <> 13 then null
else oppatty.attyid
end as attyid,
stuff.others
from
oppatty
join stuff on oppatty.ID = stuff.ID
If that's not your desired result, and you'd rather entirely exclude rows where "AttyFor" isnt equal to 13, then just use a where clause.
select
stuff.things,
oppatty.attyid,
stuff.others
from
oppatty
join stuff on oppatty.ID = stuff.ID
where
oppatty.attyfor = 13