T-SQL Populate Column Using While Loop - tsql

I have the following problem, and SQL isn't my strongest skill yet. I have the following procedure in SQL Server 2014 that works for returning the amount of money in each individual Quote for a Quoting System. Each Quote has Groups, which in turn have Parts in them.:
USE [My_DB_Name]
GO
DECLARE #QuoteNumberID int;
SELECT #QuoteNumberID = QuoteNumberId FROM [Quote].[Quote]
WHILE #QuoteNumberID IS NOT NULL
BEGIN
INSERT INTO [Quote].[ZQuoteBackupGL117] (QuoteAmount)(
SELECT ISNULL(SUM(ExtendedPrice) ,0) AS QuoteTotal
FROM(
SELECT (Quantity * ((UnitPrice - ISNULL(DollarDiscount, 0)) -
ROUND((((ISNULL(PercentDiscount,0)/100 + ISNULL(CustomerPercentDiscount,0)/100))) * UnitPrice, 2))) AS ExtendedPrice
FROM [Quote].[PartGroupPart] p
INNER JOIN [Quote].[QuotePartGroup] g ON p.PartGroupID = g.PartGroupID
INNER JOIN [Quote].[ZQuoteBackupGL117] q ON g.QuoteID = q.QuoteID
WHERE QuoteNumberId = #QuoteNumberID AND g.IsRecommended = 0 AND g.ExcludeFromTotal = 0 AND (q.GrandTotalValue IS NULL OR q.GrandTotalValue = 0)
)tmp)
SELECT #QuoteNumberID = MAX(QuoteNumberId) FROM [Quote].[Quote] WHERE #QuoteNumberId > QuoteNumberID
END
What I'm trying to do is get the value of QuoteTotal and put it in a new field in the [Quote].[ZQuoteBackupGL117] backup table named QuoteAmount(money, allow nulls) in each entry in the table. I will also run it on my production table later. Normally, I would avoid using a loop for this, but this query is only meant to run once to populate QuoteAmount for retroactive Quotes, of which there are thousands. I have looked into using INSERT INTO, but I'm not sure how I am supposed to structure it using this loop. Any help you guys can give me would be appreciated.

You probably want something like this.
UPDATE [Quote].[ZQuoteBackupGL117]
SET QuoteAmount = (
SELECT (Quantity * ((UnitPrice - ISNULL(DollarDiscount, 0)) - ROUND((((ISNULL(PercentDiscount,0)/100 + ISNULL(CustomerPercentDiscount,0)/100))) * UnitPrice, 2))) AS ExtendedPrice
FROM [Quote].[PartGroupPart] p
JOIN [Quote].[QuotePartGroup] g ON p.PartGroupID = g.PartGroupID
JOIN [Quote].[ZQuoteBackupGL117] q ON g.QuoteID = q.QuoteID
WHERE QuoteNumberId = #QuoteNumberID AND g.IsRecommended = 0 AND g.ExcludeFromTotal = 0 AND COALESCE(q.GrandTotalValue,0) = 0
)
WHERE QuoteNumberId = #QuoteNumberID
but I wouldn't bet any money on it.

Related

How can I update a table using the following CROSS APPLY?

UPDATE ItemDim_DEV
set ActiveFlag = 0,
EndUTCDate = GETUTCDATE()
from (select i.SKU, i.itemname, i.category, i.CategoryInternalId, i.itemtype, i.IsActive, i.assetaccount, i.InternalId,
ni.sku,
ni.itemname,
ni.class_name,
ni.productclass,
ni.ItemType,
ni.isactive,
ni.assetaccount,
i.CalculatedHash, ni.hashid
from (
SELECT i.*
FROM itemDim_Dev i
WHERE i.SourceSystem = 'NetSuite'
--and InternalId = '1692'
) i
CROSS APPLY (
SELECT (CONVERT([binary](64),hashbytes('SHA2_512',
concat (ISNULL(im.TargetSKU, ni.sku), ni.itemname, pc.class_name, ni.productclass, ni.ItemType,
CAST(CASE WHEN ni.IsInactive = 'T' THEN 0 ELSE 1 END AS BIT ), ni.assetaccount)))) hashid,
ISNULL(im.TargetSKU, ni.sku) sku,
ni.itemname,
pc.class_name,
ni.productclass,
ni.ItemType,
CAST(CASE WHEN ni.IsInactive = 'T' THEN 0 ELSE 1 END AS BIT ) isactive, ni.assetaccount
FROM NetSuiteInventory ni
INNER JOIN Product_Class pc ON ni.productclass = pc.class_id
LEFT JOIN ItemMapping im ON ni.sku = im.SourceSKU
WHERE ni.ItemInternalId = CAST(i.InternalId as bigint)
and
(CONVERT([binary](64),hashbytes('SHA2_512',
concat (ISNULL(im.TargetSKU, ni.sku), ni.itemname, pc.class_name, ni.productclass, ni.ItemType,
CAST(CASE WHEN ni.IsInactive = 'T' THEN 0 ELSE 1 END AS BIT ), ni.assetaccount)))) = i.CalculatedHash
) ni
I understand that I can put a select after the from of the update but in the CROSS APPLY that is in the query starts with a select as in the second one, can you help me please.
Make a join to the target table
UPDATE A
SET XXX = YYY
FROM ItemDim_DEV A
JOIN (
--huge SELECT that I recommend you try to shorten in the next questions
CROSS APPLY (any secrets...)
) B ON A.PK = B.PK

PostgreSQL: how to convert "PIVOT" from T-SQL to PLPGSQL procedure

I would like to convert a stored procedure from T-SQL to PL/PGSQL.
Here is the T-SQL code
SELECT NO_PTF, coalesce(1, 0) as RISK_1, coalesce(2, 0) as RISK_2, coalesce(3, 0) as RISK_3, coalesce(4, 0) as RISK_4
FROM (
SELECT D.NO_PTF, C.NB_DEG_RSQ, D.MT_DEM/100 as MT_DEM
FROM public.TB_Demande D
INNER JOIN
public.TB_Compartiment C ON D.ID_CPA = C.ID_CPA
AND D.MC_UTL = 'F3000'
AND C.IN_ACT = true
) Q
PIVOT (SUM(Q.MT_DEM) FOR Q.NB_DEG_RSQ IN (1, 2, 3, 4)) as PVT
)
I tried to replace PIVOT by crosstab. But i have an error. Here is my PL/PGSQL code
SELECT NO_PTF, coalesce(1, 0) as RISK_1, coalesce(2, 0) as RISK_2, coalesce(3, 0) as RISK_3, coalesce(4, 0) as RISK_4
FROM (
SELECT D.NO_PTF, C.NB_DEG_RSQ, D.MT_DEM/100 as MT_DEM
FROM public.TB_Demande D
INNER JOIN
public.TB_Compartiment C ON D.ID_CPA = C.ID_CPA
AND D.MC_UTL = 'F3000'
AND C.IN_ACT = true
) Q
crosstab (SUM(Q.MT_DEM) FOR Q.NB_DEG_RSQ IN (1, 2, 3, 4)) as PVT
Someone have an idea about the rigth way to use crosstab here?
I am not entirely sure I understand the PIVOT syntax, but typically this is way easier in Postgres using a filtered aggregation, not the somewhat clumsy crosstab() function.
I think this should be equivalent:
select no_ptf,
sum(mt_dem) filter (where NB_DEG_RSQ = 1) as risk_1,
sum(mt_dem) filter (where NB_DEG_RSQ = 2) as risk_2,
sum(mt_dem) filter (where NB_DEG_RSQ = 3) as risk_3,
sum(mt_dem) filter (where NB_DEG_RSQ = 4) as risk_4
from (
SELECT D.NO_PTF, C.NB_DEG_RSQ, D.MT_DEM/100 as MT_DEM
FROM public.TB_Demande D
INNER JOIN public.TB_Compartiment C
ON D.ID_CPA = C.ID_CPA
AND D.MC_UTL = 'F3000'
AND C.IN_ACT = true
)
GROUP BY no_ptf;

Getting all tables / columns and their datatype firebird in one query

I want to query the following: all tables, with their columns and data type
I got this so far from http://www.firebirdfaq.org/faq174/
select f.rdb$relation_name, f.rdb$field_name, f.rdb$field_source
from rdb$relation_fields f
join rdb$relations r on f.rdb$relation_name = r.rdb$relation_name
and r.rdb$view_blr is null
and (r.rdb$system_flag is null or r.rdb$system_flag = 0)
order by 1, f.rdb$field_position
and I just added the above code "f.rdb$field_source" to show me also that info on that table, that should allow me to find the following datatype of the columns in the this table RDB$FIELDS
SELECT * from RDB$FIELDS
I want to add in the above query something like this:
select f.rdb$relation_name, f.rdb$field_name, f.rdb$field_source
from rdb$relation_fields f
join rdb$relations r on f.rdb$relation_name = r.rdb$relation_name
and r.rdb$view_blr is null
and (r.rdb$system_flag is null or r.rdb$system_flag = 0)
order by 1, f.rdb$field_position
Plus then the following needs to happen:
select a.RDB$FIELD_TYPE from RDB$FIELDS
where RDB$FIELD_NAME is equal to the .rdb$field_source
from rdb$relation_fields
So i have one table with Tablename/Columnname/TypeofData.
I got little experience in SQL. I hope my question is clear enough
Just join RDB$FIELDS table:
SELECT
R.RDB$RELATION_NAME,
R.RDB$FIELD_NAME,
R.RDB$FIELD_SOURCE,
F.RDB$FIELD_LENGTH,
F.RDB$FIELD_TYPE,
F.RDB$FIELD_SCALE,
F.RDB$FIELD_SUB_TYPE
FROM
RDB$RELATION_FIELDS R
JOIN RDB$FIELDS F
ON F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE
JOIN RDB$RELATIONS RL
ON RL.RDB$RELATION_NAME = R.RDB$RELATION_NAME
WHERE
COALESCE(R.RDB$SYSTEM_FLAG, 0) = 0
AND
COALESCE(RL.RDB$SYSTEM_FLAG, 0) = 0
AND
RL.RDB$VIEW_BLR IS NULL
ORDER BY
R.RDB$RELATION_NAME,
R.RDB$FIELD_POSITION

How to use parameters in a SQL query with NOT EXISTS?

How can I change following query, so that I'm able to parameterize the SparePartNames?
It returns all ID's of repairs where not all mandatory spareparts were changed, in other words where at least one part is missing.
Note that the number of spareparts might change in future not only the names. Is it possible without using a stored procedure with dynamic SQL? If not, how could this SP look like?
Edit: Note that i do not need to know how to pass a list/array as parameter, this is asked myriads of time on SO. I've also already a Split table-valued-function. I'm just wondering how i could rewrite the query to be able to join(or whatever) with a list of mandatory parts, so that i'll find all records where at least one part is missing. So is it possible to use a varchar-parameter like '1264-3212,1254-2975' instead of a list of NOT EXISTS? Sorry for the confusion if it was not clear in the first place.
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND (NOT EXISTS
(SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
WHERE (td.fiData = d.idData)
AND (sp.SparePartName = '1264-3212'))
OR (NOT EXISTS
(SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
WHERE (td.fiData = d.idData)
AND (sp.SparePartName = '1254-2975'))
)
)
Unfortunately I don't see how I could use sp.SparePartName IN/NOT IN(#sparePartNames) here.
One way to do it is to create a function to split delimited strings:
CREATE FUNCTION [dbo].[Split]
(
#Delimiter char(1),
#StringToSplit varchar(512)
)
RETURNS table
AS
RETURN
(
WITH Pieces(pieceNumber, startIndex, delimiterIndex)
AS
(
SELECT 1, 1, CHARINDEX(#Delimiter, #StringToSplit)
UNION ALL
SELECT pieceNumber + 1, delimiterIndex + 1, CHARINDEX(#Delimiter, #StringToSplit, delimiterIndex + 1)
FROM Pieces
WHERE delimiterIndex > 0
)
SELECT
SUBSTRING(#StringToSplit, startIndex, CASE WHEN delimiterIndex > 0 THEN delimiterIndex - startIndex ELSE 512 END) AS Value
FROM Pieces
)
populate a table variable with the spare part names:
DECLARE #SpareParts TABLE
(
SparePartName varchar(50) PRIMARY KEY CLUSTERED
);
INSERT INTO #SpareParts
SELECT Value FROM dbo.Split(',', '1264-3212,1254-2975');
and then join to the table variable:
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND EXISTS (
SELECT 1
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
LEFT JOIN #SpareParts AS s ON s.SparePartName = sp.SparePartName
WHERE td.fiData = d.idData
AND s.SparePartName IS NULL
)
Assuming there is (or will be) a table or view of mandatory spare parts, a list of exists can be replaced with a left join to tabDataDetail / tabSparePart pair on SparePartName; non-matches are reported back using td.fiSparePart is null.
; with mandatorySpareParts (SparePartName) as (
select '1264-3212'
union all
select '1254-2975'
)
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND exists
(
SELECT null
from mandatorySpareParts msp
left join ( tabDataDetail AS td
INNER JOIN tabSparePart AS sp
ON sp.idSparePart = td.fiSparePart
AND td.fiData = d.idData
)
ON msp.SparePartName = sp.SparePartName
WHERE td.fiSparePart is null
)
Part names should be replaced by their id's, which would simplify left join and speed the query up.
EDIT: i've errorneously left filtering of td in where clause, which invalidated left join. It is now in ON clause where it belongs.
Use a table-variable and join on that.

Can you use a SELECT INTO statement with a CTE that contains a UDF?

Can I do this:
With ZipCodeCTE as
{
select nvl(HH.GeoUSZip5 , **ZipCodeKeyLookUp**(HH.[CityName],HH.[StateName])) as TotalZipCode
from ODSDataArchive.archive.HHJob_Data_201202 HH
}
/* This Is a SELECT INTO statement that inserts
data into [Jobs].[dbo].[FactRPP]*/
SELECT [dbo].[FactJobsDaily].jobdid,
[dbo].[FactJobsDaily].DateKey,
[dbo].[FactJobsDaily].YearMonth,
[dbo].[FactJobsDaily].AccountKey,
[dbo].[FactJobsDaily].BridgeSocKey,
[dbo].[FactJobsDaily].HostSiteKey,
[dbo].[FactJobsDaily].JobClickedCount,
[dbo].[FactJobsDaily].JobResultsPageCount,
(select DZ.ZipCodeKey
from dimensions.dbo.DimZipCode DZ
where DZ.ZipCodeKey IN
(Select CAST(TotalZipCode AS INT)
from ZipCodeCTE))
INTO [Jobs].[dbo].[FactRPP]
from dbo.FactJobsDaily
inner join ODSDataArchive.archive.HHJob_Data_201202
on dbo.FactJobsDaily.JobDID = ODSDataArchive.archive.HHJob_Data_201202.DID
and dbo.FactJobsDaily.datekey = ODSDataArchive.archive.HHJob_Data_201202.datekey
inner join dimensions.dbo.Dimzipcode dzc
on ODSDataArchive.archive.HHJob_Data_201202.geoUSZip5 = dimensions.dbo.Dimzipcode.ZipCode
where [dbo].[FactJobsDaily].yearmonth= 201202
and [dbo].[FactJobsDaily].isactivekey = 1
-- and ODSDataArchive.archive.HHJob_Data_201202.geoUSZip5 <> ''
-- and ODSDataArchive.archive.HHJob_Data_201202.geoUSZip5 IS NOT NULL
and ODSDataArchive.archive.HHJob_Data_201202.status = 0
and ODSDataArchive.archive.HHJob_Data_201202.CountryName = 'US'
order by [dbo].[FactJobsDaily].jobdid;
Because the CTE translates into a regular query the short answer is yes.