How to create a view with 14000 Columns in it? - sql

I have two tables
Shop
Item.
and a third table which shows availability of Item in a shop with cost of the item in that shop.
Shop_Item_Mapping.
Some sample data of Item would be (1, Candy) where 1 is id and Candy is name of the Item.
(2 , Chocolate)
(3 , Chair)
(4 , Mobile)
(5 , Bulb)
Some sample data of Shop table would be
(1 , Address Of Shop)
(2 , Address Of Shop)
(3 , Address of the shop)
Now my mapping table shows me that which Item is available in which shop and at what cost.
Shop_Item_Mapping ( Shop_id , Item_Id , Cost of Item).
So my mapping tables have these entries
SID , IID , Cost
(1 , 1, 5)
(1 , 2 ,10)
(1 ,4 ,2300)
(2 ,3 ,50)
(2 , 5 ,10)
(3 , 1 , 4)
(3 , 2 , 5 )
(3 , 4 , 2500 )
(3 , 5 , 12 )
Now I have a query that I want all those shops which have both Mobile ( id = 4 ) and Chocolates(id = 2) with mobile price < 3000 and chocolate price less than 7.
I am trying to make a view where I will have data like this
Shop_ID , I1 , I2 , I3 , I4 , I5 where I1 , I2 , I3 , I4 , I5 are the id of Items and value of these will be cost of the item in that shop.
So my view would be
(1 , 5 , 10 , NULL , 2300 , NULL )
(2 , NULL , NULL , 50 , NULL , 10)
(3 , 4 , 5 , NULL , 2500 , 12 ).
I am able to do so when my items are less. But IF I have more than 15000 Items in my repository, Can I create a view with these many columns ?

Seriously? 14,000 columns in a view? You have a serious design issue here. However if you want to have a go, try this dynamic pivot query. It works with the limited data you have provided:
DECLARE #ColumnList VARCHAR (MAX)
DECLARE #SQL VARCHAR(MAX)
-- Create a list of distinct Item IDs which will become column headers
SELECT #ColumnList = COALESCE(#ColumnList + ', ','') + 'ItemID' + CAST(I.ItemID AS VARCHAR(12)) FROM (SELECT DISTINCT ItemID FROM Item) I
SET #SQL = '
SELECT
ShopID, ' + #ColumnList + '
FROM
(
SELECT
s.ShopID,
ItemID = ''ItemID'' + Cast(i.ItemID as varchar(12)),
sim.ItemCost
FROM
dbo.Shop_Item_Mapping AS sim
JOIN dbo.Shop AS s ON sim.ShopID = s.ShopID
JOIN dbo.Item AS i ON SIM.ItemID = i.ItemID
) T
PIVOT
(
MIN(ItemCost)
FOR T.ItemID IN (' + #ColumnList + ')
) AS PVT'
exec (#SQL)
Edited field names as per updated question.

Related

Oracle get UNIQUE constraint violation error too late

What should I check why Oracle server takes more then 20 sec to return UNIQUE constraint violation error for specific data?
One of our processes is processing over 30000 data one day with multi process and some time gets UNIQUE constraint violation error in 1 sec
but it takes more then 20 sec to return UNIQUE constraint violation error for specific data.
Query is same as below. (Modified only table name)
MERGE
INTO TableA S
USING (
SELECT NVL(:sccm_cd , ' ') SCCM_CD
, NVL(:oder_dt , ' ') ODER_DT
, NVL(:mrkt_dstn_cd, ' ') MRKT_DSTN_CD
, NVL(:oder_no , ' ') ODER_NO
, NVL(:cncd_unpr , 0) CNCD_UNPR
, B.SLBY_FEE_GRD_CD
, B.ACCT_MNGR_EMPL_NO
, C.AO_FEE_GRD_CD
FROM DUAL A
, TableB B
, TableC C
WHERE 1 = 1
AND B.SCCM_CD = :sccm_cd
AND B.ACNO = :acno
AND C.SCCM_CD(+) = B.SCCM_CD
AND C.EMPL_NO(+) = B.ACCT_MNGR_EMPL_NO
) T
ON ( S.sccm_cd = T.sccm_cd
AND S.oder_dt = T.oder_dt
AND S.mrkt_dstn_cd = T.mrkt_dstn_cd
AND S.oder_no = T.oder_no
AND S.cncd_unpr = T.cncd_unpr
)
WHEN MATCHED THEN
UPDATE
SET S.cncd_qty = S.cncd_qty + NVL(:cncd_qty ,0)
, S.slby_fee = S.slby_fee + NVL(:slby_fee ,0)
, S.slby_fee_srtx = S.slby_fee_srtx + NVL(:slby_fee_srtx,0)
, S.idx_fee_amt = S.idx_fee_amt + NVL(:idx_fee_amt ,0)
, S.cltr_fee = S.cltr_fee + NVL(:cltr_fee ,0)
, S.trtx = S.trtx + NVL(:trtx ,0)
, S.otc_fee = S.otc_fee + NVL(:otc_fee ,0)
, S.wht_fee = S.wht_fee + NVL(:wht_fee ,0)
WHEN NOT MATCHED THEN
INSERT (
sccm_cd
, oder_dt
, mrkt_dstn_cd
, oder_no
, cncd_unpr
, acno
, item_cd
, slby_dstn_cd
, md_dstn_cd
, cncd_qty
, stlm_dt
, trtx_txtn_dstn_cd
, proc_cmpl_dstn_cd
, item_dstn_cd
, slby_fee_grd_cd
, slby_fee
, slby_fee_srtx
, idx_fee_amt
, cltr_fee
, trtx
, wht_fee
, otc_fee
, acct_mngr_empl_no
, ao_fee_grd_cd
)
VALUES
( T.sccm_cd
, T.oder_dt
, T.mrkt_dstn_cd
, T.oder_no
, T.cncd_unpr
, :acno
, :item_cd
, :slby_dstn_cd
, :md_dstn_cd
, NVL(:cncd_qty ,0)
, DECODE(:mrkt_dstn_cd, 'TN', T.oder_dt, :stlm_dt)
, :trtx_txtn_dstn_cd
, '0'
, :item_dstn_cd
, NVL(:slby_fee_grd_cd, T.SLBY_FEE_GRD_CD)
, NVL(:slby_fee ,0)
, NVL(:slby_fee_srtx ,0)
, NVL(:idx_fee_amt ,0)
, NVL(:cltr_fee ,0)
, NVL(:trtx ,0)
, NVL(:wht_fee , 0)
, NVL(:otc_fee , 0)
, T.acct_mngr_empl_no
, T.ao_fee_grd_cd
)
There could be multiple reasons for it. I will list here some of the possible causes for this behavior.
Concurrency issue
Your insert might be waiting for other operations, like other inserts or updated or deletions.
Network issues
It is possible that for some reason your network is overwhelmed with requests or, if the server is remote, this could be an internet speed issue as well.
Server load
The server might be overwhelmed with lots of jobs to do.
Slow query
It's also possible that the select you use in your insert command is very slow. It would make sense to test its speed. Also, it would make sense to test insert speed as well.

Having Trouble with FOR XML Path - No Concatenating

I have a situation where an order can contain multiple license purchases - and if the order does contain multiple licenses, I want to display the license descriptions in a single cell with the values separated by commas. If we were on SQL 2017, I could use STRING_AGG but we are on SQL 2016 so I am trying the tried and true STUFF / FOR XML Path method.
From the screenshot below, Customer 4341073 had two license purchases on Order ID 18519173:
When I add the STUFF / FOR XML Path to the T-SQL, I am not able to achieve the desired result of showing the license description in the same record - each license still has it's own row.
SELECT x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
STUFF ( (
SELECT ',' + lt.description
FROM dbo.LicenseTypes AS lt
--INNER JOIN #XMLPATH ON lt.id = x.OrderLicenseTypeID
WHERE lt.id = x.OrderLicenseTypeID
--GROUP BY ',' + lt.description
FOR XML PATH ( '' )
) , 1 , 1 , '' ) AS Licenses
FROM #XMLPATH AS x
--GROUP BY x.CustomerID ,
-- x.ATOLicenseTypeID ,
-- x.ATOLicense ,
-- x.AuthorizationBeginDate ,
-- x.AuthorizationEndDate ,
-- x.OrderID ,
-- x.OrderDate ,
-- x.OrderLicenseTypeID;
I've tried different ways to join the sub-query to the outer query and added and removed GROUP BY to achieve the desired result but nothing is working for me.
Any suggestions on where I am going wrong with this query?
Sample dataset:
DROP TABLE IF EXISTS #XMLPATH;
CREATE TABLE #XMLPATH
(
CustomerID INT ,
ATOLicenseTypeID INT ,
ATOLicense VARCHAR (500) ,
AuthorizationBeginDate DATE ,
AuthorizationEndDate DATE ,
OrderID INT ,
OrderDate DATETIME ,
OrderLicenseTypeID INT
);
INSERT INTO #XMLPATH
VALUES ( 4341073, 52, 'Temporary Resident Fishing', N'2019-01-07T00:00:00', N'2019-01-07T00:00:00', 18519136, N'2019-01-07T12:01:55.317', 2141 ) ,
( 4341073, 52, 'Temporary Resident Fishing', N'2019-01-07T00:00:00', N'2019-01-07T00:00:00', 18519173, N'2019-01-07T12:34:13.107', 204 ) ,
( 4341073, 52, 'Temporary Resident Fishing', N'2019-01-07T00:00:00', N'2019-01-07T00:00:00', 18519173, N'2019-01-07T12:34:13.107', 2141 );
SELECT * FROM #XMLPATH;
SELECT x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
STUFF ( (
SELECT ',' + lt.description
FROM dbo.LicenseTypes AS lt
--INNER JOIN #XMLPATH ON lt.id = x.OrderLicenseTypeID
WHERE lt.id = x.OrderLicenseTypeID
--GROUP BY ',' + lt.description
FOR XML PATH ( '' )
) , 1 , 1 , '' ) AS Licenses
FROM #XMLPATH AS x
GROUP BY x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
x.OrderLicenseTypeID;
In order to get all rows of one OrderID as one result-row, you must not include the separating information (the OrderLicenseTypeID) into the GROUP BY. But then you have the issue you've encountered: You cannot use this ID within your FOR XML construct.
The trick is (as your out-commented trials show), to add the source table to the sub-select and filter there with a grouped column. But you have to use different aliases to deal with them as two different sets. Try this:
(I had to add one more temp table to test this...)
SELECT x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
STUFF ( (
SELECT ',' + lt.description
FROM #XMLPATH x2
INNER JOIN #LicenseTypes AS lt ON lt.id=x2.OrderLicenseTypeID
WHERE x2.OrderID = x.OrderID --you might need to add more columns here....
--in most cases we want to add an ORDER BY
FOR XML PATH ( '' )
) , 1 , 1 , '' ) AS Licenses
FROM #XMLPATH AS x
GROUP BY x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate;
Btw: Starting with v2017 there is STRING_AGG(), which makes this much easier...

creating dynamic pivot using sql [duplicate]

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 7 years ago.
i don't want to use static pivot because my values are dynamic, here is what my table look like
ItemName Date Qty
Tomato 11/29/2015 20
Tomato 11/30/2015 15
Apple 12/2/2015 41
Mango 12/3/2015 23
Onion 12/4/2015 11
but i want to look like this but i can't hard code the ItemName and the Date because they are always dynamic.
Try like Below Query
--Sample Table Creation
create table veg
( itemname varchar(50),
[date] date,
qty int
)
insert into veg
values
('Tomato', '11/29/2015', 20),
('Tomato', '11/30/2015' , 15),
('Apple' , '12/2/2015' ,41),
('Mango' , '12/3/2015' , 23),
('Onion' , '12/4/2015' , 11)
--dynamic sql
DECLARE #col AS NVARCHAR(MAX)
DECLARE #val AS NVARCHAR(MAX)
SET #col = STUFF((SELECT distinct ',' + QUOTENAME(c.date)
FROM veg c
FOR XML PATH('')
),1,1,'')
--select #col
set #val = 'SELECT itemname, ' + #col + ' from
(
SELECT itemname,date,qty
FROM veg
) SOURCE
PIVOT
(
SUM(qty)
FOR date IN (' + #col + ')
) p '
exec (#val)

How to filter the String based on , and delete the duplicates

Let's Assume I have DataTable in Ms sql that represents simple matrix
Existing Table
ids_cloumn
========================
RX ,BX , AS , RX ,BX , AS
XR ,Xs , AS
XR ,Xs , AS,XR ,Xs , AS
RX ,BX , AS ,
RX ,BX , AS ,
I want to filter the duplicated data in string and data is separated by ' , '
ids_column
========================
RX ,BX , AS
XR ,Xs , AS
XR ,Xs , AS
RX ,BX , AS ,
RX ,BX , AS ,
right now i am using this But this approach is unsuccessful
declare #i int
declare #c char
declare #rst varchar(8000)
set #i=1
set #rst=substring(‘aaassrt’,1,1)
set #c=”
while #i<=len(‘aaassrt’)
begin
set #c=substring(‘aaassrt’,#i,1)
if charindex( #c,#rst,1)=0
set #rst=#rst+#c
set #i=#i+1
end
select #rst
i hope this will help you xQuery but i am not able to remove last comma of string :( another thing for my below query you have to keep specific space between character
declare #temp table (Id int,val nvarchar(33))
insert into #temp values (1,'RX ,BX ,AS ,RX ,BX ,AS')
insert into #temp values (2,'XR ,Xs ,AS ')
insert into #temp values (3,'XR ,Xs ,AS ,XR ,Xs ,AS')
insert into #temp values (4,'RX ,BX ,AS ,')
insert into #temp values (5,'RX ,BX ,AS ,')
select Id,
REPLACE(cast(cast('<d>'+ replace(LTRIM(RTRIM(val)), ' ,','</d><d>')+'</d>' as xml)
.query('distinct-values(/d)') as varchar), ' ', ' ,')AS [val]
from #temp;
RESULT
This is one way on doing it:
declare #data table(ids nvarchar(100))
insert into #data(ids) values
('1 , 2 , 4 , 23 , 1 , 2 , 4 , 23 ,')
, ('1 , 3 , 4 , 3 , 4 ')
, ('2 , 3 , 6 ,')
, ('3 , 5 , 8 , 3 , 5 ')
, ('1 , 7 , 9 ')
Select distinct xml.ids, LTRIM(RTRIM(x.id.value('.', 'varchar(5)'))) as id
From (
Select ids
, CAST('<x>'+REPLACE(ids, ',', '</x><x>') + '</x>' as xml) as data
From #data
) as xml
Cross Apply data.nodes('x') as x(id)
Where LTRIM(RTRIM(x.id.value('.', 'varchar(5)'))) <> ''
Like it has been said by everyone else, you should not store comma separated value in one single column.
You should record it in rows in another table and join it to your main table.

Stop Recursion Once Condition Satisfied

I am using a CTE to explode out a Bill of Materials and need to locate all those materials that have recursive components.
What I was attempting, was to limit the number of cycles (levels) deep, by setting BOM_Level in the child node to a maximum bound:
exec pr_sys_drop_object '#BOMExploded'
;with BOM
as
(
select
Prod_Plant_CD
, Demand_Plant_CD
, material_cd
, component_cd
, component_quantity
, component_quantity AS Calculated_Component_Quantity
, BOM_Level
, Demand_Quantity
, CONVERT(float,1) AS Produced_Ratio
, Material_CD AS Demand_Material_CD
from #firstLevel a
UNION ALL
SELECT
b.Plant_CD as 'Prod_Plant_CD'
, a.Demand_Plant_CD
, b.Material_CD
, b.Component_CD
, b.component_quantity
, b.component_quantity
, a.BOM_Level + 1
, a.Demand_Quantity
, a.Produced_Ratio * a.Component_Quantity -- Produced Quantity for the current level = Produced Quantity (level -1) * Component_Quantity (level -1)
, a.Demand_Material_CD
FROM BOM a
inner join #BOM_ProdVersion_Priority b
on a.component_cd = b.material_cd
inner join #base_routes c
on a.Demand_Plant_CD = c.Recipient_Plant_CD
and b.Plant_CD = c.Source_Plant_CD
and c.Material_CD = b.Material_CD -- Need to have material_cd to link
where b.Material_CD != b.Component_CD
and b.Component_Quantity > 0
and BOM_Level < 5 -- Give the max number of levels deep we are allowed to cyncle to
)
select *
into #BOMExploded
from BOM
OPTION (MAXRECURSION 20)
Using this method however, would require a post-process to locate when the cycling on the recursive component level started, then back trace.
How can a CTE recursive query be stopped given a certain condition?
ie. when top-level material_cd = component_cd for a deeper BOM_Level
If I understand you correctly, you don't need to stop at a certain depth/level, or rather you want to stop at a certain level, but you also need to stop in case you start cycling through materials repeatedly.
In the case of the following recursive path: mat_1->mat_2->mat_3->mat_1, you would want to stop before that last mat_1 starts cycling again to mat_2 and so on.
If that's correct, then your best bet is to add a Path field to your recursive query that tracks each term that it finds as it moves down the recursive path:
exec pr_sys_drop_object '#BOMExploded'
;with BOM
as
(
select
Prod_Plant_CD
, Demand_Plant_CD
, material_cd
, component_cd
, component_quantity
, component_quantity AS Calculated_Component_Quantity
, BOM_Level
, Demand_Quantity
, CONVERT(float,1) AS Produced_Ratio
, Material_CD AS Demand_Material_CD
, CAST(material_cd AS VARCHAR(100)) AS Path
from #firstLevel a
UNION ALL
SELECT
b.Plant_CD as 'Prod_Plant_CD'
, a.Demand_Plant_CD
, b.Material_CD
, b.Component_CD
, b.component_quantity
, b.component_quantity
, a.BOM_Level + 1
, a.Demand_Quantity
, a.Produced_Ratio * a.Component_Quantity -- Produced Quantity for the current level = Produced Quantity (level -1) * Component_Quantity (level -1)
, a.Demand_Material_CD
, a.Path + '|' + b.material_cd
FROM BOM a
inner join #BOM_ProdVersion_Priority b
on a.component_cd = b.material_cd
inner join #base_routes c
on a.Demand_Plant_CD = c.Recipient_Plant_CD
and b.Plant_CD = c.Source_Plant_CD
and c.Material_CD = b.Material_CD -- Need to have material_cd to link
where b.Material_CD != b.Component_CD
and b.Component_Quantity > 0
and BOM_Level < 5 -- Give the max number of levels deep we are allowed to cyncle to
and a.path NOT LIKE '%' + b.material_cd + '%'
)
select *
into #BOMExploded
from BOM
OPTION (MAXRECURSION 20)
Now you have a path that is pipe delimited and you can test your current material_cd to see if it's already in the path. If it is, then you end that leg of the recursion to prevent cycling.
Updating to include a version where we capture material_cd cycles and only report those at the end of the recursion:
exec pr_sys_drop_object '#BOMExploded'
;with BOM
as
(
select
Prod_Plant_CD
, Demand_Plant_CD
, material_cd
, component_cd
, component_quantity
, component_quantity AS Calculated_Component_Quantity
, BOM_Level
, Demand_Quantity
, CONVERT(float,1) AS Produced_Ratio
, Material_CD AS Demand_Material_CD
, CAST(material_cd AS VARCHAR(100)) AS Path
, CAST(NULL AS CHAR(5)) as Cycle_Flag
, 0 as Cycle_Depth
from #firstLevel a
UNION ALL
SELECT
b.Plant_CD as 'Prod_Plant_CD'
, a.Demand_Plant_CD
, b.Material_CD
, b.Component_CD
, b.component_quantity
, b.component_quantity
, a.BOM_Level + 1
, a.Demand_Quantity
, a.Produced_Ratio * a.Component_Quantity -- Produced Quantity for the current level = Produced Quantity (level -1) * Component_Quantity (level -1)
, a.Demand_Material_CD
, a.Path + '|' + b.material_cd
, CASE WHEN a.path NOT LIKE '%' + b.material_cd + '%' then 'Cycle' END AS Cycle_Flag,
, CASE WHEN a.path NOT LIKE '%' + b.material_cd + '%' then a.Cycle_Depth + 1 END as Cycle_Depth
FROM BOM a
inner join #BOM_ProdVersion_Priority b
on a.component_cd = b.material_cd
inner join #base_routes c
on a.Demand_Plant_CD = c.Recipient_Plant_CD
and b.Plant_CD = c.Source_Plant_CD
and c.Material_CD = b.Material_CD -- Need to have material_cd to link
where b.Material_CD != b.Component_CD
and b.Component_Quantity > 0
and a.cycle_depth < 2 --stop the query if we start cycling, but only after we capture at least one full cycle
)
select *
into #BOMExploded
from BOM
WHERE cycle_flag IS NOT NULL
OPTION (MAXRECURSION 20)
This will capture cycle_depth which is a counter that measures how deep into a cycle we get. We stop the recursion after we get to cycle_depth of 1 so the cycle can be captures in the final select.