Computed Column with relationships - sql-server-2008

I have a table, MapLocation, which has a column and two relationships with tables that have a field that really need to be displayed as a single concatenated value. I was thinking this was a perfect case for a computed column, but not sure how to go about it.
MapLocation MaoNo Section
_____________________ _____________________ _____________________
MapNoId MapNoId SectionId
SectionId MapNumber (int) Section (int)
Identifier (nvarchar)
LocationName (nvarchar)
LocationName = "MapNUmber - SectionNumber - Identifier"
ex: 20 - 03 - SW4
How would I write that? I haven't done much with computed columns or concatenating in SQL.
Edit:
I need an actual computed column that is automatically updated, im looking for the formula. Or is this more of a function/trigger? Its possible, I certainly barely know what I'm doing. The idea is that I dont want to have to do two more server calls and concatenate these values client side.

You would use something like this to get the value:
select cast(n.MapNumber as nvarchar(10)) + ' - ' -- cast the MapNumber
+ cast(s.SectionId as nvarchar(10)) + ' - ' -- cast the SectionId
+ l.Identifier
from MapLocation l
left join MaoNo n
on l.MapNoId = n.MapNoId
left join Section s
on l.SectionId = s.SectionId
Then if you need to perform an UPDATE:
update l
set l.LocationName = (cast(n.MapNumber as nvarchar(10)) + ' - '
+ cast(s.SectionId as nvarchar(10)) + ' - '
+ l.Identifier)
from MapLocation l
left join MaoNo n
on l.MapNoId = n.MapNoId
left join Section s
on l.SectionId = s.SectionId
Edit #1 - you can use a TRIGGER:
CREATE TRIGGER trig_LocationName
ON MapLocation
AFTER INSERT
AS
Begin
update MapLocation
set LocationName = (cast(n.MapNumber as nvarchar(10)) + ' - '
+ cast(s.SectionId as nvarchar(10)) + ' - '
+ i.Identifier)
from Inserted i
left join MaoNo n
on i.MapNoId = n.MapNoId
left join Section s
on i.SectionId = s.SectionId
where MapLocation.MapNoId = i.MapNoId -- fields here to make sure you update the correct record
End

Related

SSRS Report Parameters passed out

I am currently building a number of logging and analysis tools to keep tabs on our SQL environment. We are currently using SQL Server 2014.
What I want to do is keep check of all the parameters that are passed to our reports during the day. All of the reports are currently using stored procedures so in my table or a select statement based on a table is output the stored procedure with the parameters for every time the report was run.
At the end of the day I would then like to be able to take the outputted statement and run it in SSMS without having to use the report. I have been looking at the ExceutionLogStorage table and the ExecutionLog view's and though it has most of the information that I need, the parameters are not in an easily usable state.
Has anyone done something similar to what I have described?
You need to add logging part in your original SP, for example:
Alter procedure a
(#parameter)
As
Begin
..
..
Insert into loggingTable(col)
Values(#parameter)
..
..
End
Then query directly against that loggingTable for getting the history of used parameters
A Google search around this topic quickly brought up the following blog post already identified by the OP as useful and shown below (this query itself is actually an expansion of work linked to by LONG's answer below)
SELECT TOP 1 ParValue
FROM (
SELECT els.TimeEnd
, IIF(CHARINDEX('&' + 'ParameterName' + '=', ParsString) = 0, 'ParameterName',
SUBSTRING(ParsString
, StartIndex
, CHARINDEX('&', ParsString, StartIndex) - StartIndex)) AS ParValue
FROM (SELECT ReportID, TimeEnd
, '&' + CONVERT(VARCHAR(MAX), Parameters) + '&' AS ParsString
, CHARINDEX('&' + 'ParameterName' + '=', '&' + CONVERT(VARCHAR(MAX), Parameters) + '&')
+ LEN('&' + 'ParameterName' + '=') AS StartIndex
FROM ExecutionLogStorage
WHERE UserName='UserName' -- e.g. DOMAIN\Joe_Smith
) AS els
INNER JOIN [Catalog] AS c ON c.ItemID = els.ReportID
WHERE c.Name = 'ReportName'
UNION ALL
SELECT CAST('2000-01-01' AS DateTime), 'ParameterName'
) i
ORDER BY TimeEnd DESC;
Both these approaches though really only give us a starting point since they (variously) rely upon us knowing in advance the report name and parameter names. Whilst we can quickly make a couple of changes to Ken Bowman's work to get it to run against all executions of all reports, we still have the problem that the query hardcodes the parameter name.
The parameters required to execute a report are stored on the Catalog table in the Parameter column. Although the column has a datatype ntext, it is actually storing an XML string. Meaning we can use an XPath query to get at the parameter names
with
CatalogData as (
select ItemID, [Path], [Name], cast(Parameter as xml) 'ParameterXml'
from Catalog
where [Type] = 2),
ReportParameters as (
select ItemID, [Path], [Name], ParameterXml, p.value('Name[1]', 'nvarchar(256)') 'ParameterName'
from CatalogData
cross apply ParameterXml.nodes('/Parameters/Parameter') as Parameters(p))
select *
from ReportParameters;
Executing this query will list all reports on the server and their parameters. Now we just need to combine this with Ken Bowman's query. I've gone with a CTE approach
with
CatalogData as (
select ItemID, [Path], [Name], cast(Parameter as xml) 'ParameterXml'
from Catalog
where [Type] = 2),
ReportParameters as (
select ItemID, [Path], [Name], p.value('Name[1]', 'nvarchar(256)') 'ParameterName'
from CatalogData
cross apply ParameterXml.nodes('/Parameters/Parameter') as Parameters(p))
select
els.TimeEnd
, c.[Name]
, rp.ParameterName
, iif(
charindex(
'&' + rp.ParameterName + '=', ParametersString) = 0
, rp.ParameterName, substring(ParametersString
, StartIndex, charindex('&', ParametersString, StartIndex) - StartIndex
)) 'ParameterValue'
from (
select
ReportID
, TimeEnd
, rp.ParameterName
, '&' + convert(varchar(max), Parameters) + '&' 'ParametersString'
, charindex(
'&' + rp.ParameterName + '=',
'&' + convert(varchar(max), Parameters) + '&'
) + len('&' + rp.ParameterName + '=') 'StartIndex'
from
ExecutionLogStorage
inner join ReportParameters rp on rp.ItemID = ReportID) AS els
inner join [Catalog] c on c.ItemID = els.ReportID
inner join ReportParameters rp on rp.ItemID = c.ItemID and rp.ParameterName = els.ParameterName;
Note that the parameter values are passed to the report as part of a URL, so you'll still need get rid the literal space encoding and so on. Also, this doesn't (yet...) work for multi-value parameters.

8 million records query performance

I have the query outlined as below. At present it takes over 8 min to run given there are over 8 million records within the zt_Arrival_Data table, while the zt_Tpl_Tuple_Stats_2 tale only carries 9774 records, with the total output of simply 6946 unique records.
In what way can I structure this query to improve performance?
SELECT distinct b.Tuple_ID
, LTRIM(RTRIM(a.ORIGIN_CITY)) + ', ' + LTRIM(RTRIM(a.ORIGIN_STATE)) AS Origin_TX
, LTRIM(RTRIM(a.DESTINATION_CITY)) + ', ' + LTRIM(RTRIM(a.DESTINATION_STATE)) AS Destination_TX
, LTRIM(RTRIM(a.ORIGIN_CITY)) + ' - ' + LTRIM(RTRIM(a.CUSTOMER_NAME)) AS Origin_Customer_TX
, LTRIM(RTRIM(a.ORIGIN_CITY)) + ' - ' + LTRIM(RTRIM(a.DESTINATION_CITY)) AS Origin_Destination_TX
, LTRIM(RTRIM(a.CUSTOMER_NAME)) AS Customer_Name
, LTRIM(RTRIM(a.CUSTOMER_NAME)) + ', ' + LTRIM(RTRIM(a.CUSTOMER_NO)) AS Customer_TX
, CASE
WHEN LTRIM(RTRIM(a.CUSTOMER_TYPE)) = 'C' THEN 'Customer'
WHEN LTRIM(RTRIM(a.CUSTOMER_TYPE)) = 'I' THEN 'Internal'
WHEN LTRIM(RTRIM(a.CUSTOMER_TYPE)) = 'S' THEN 'Shop'
WHEN LTRIM(RTRIM(a.CUSTOMER_TYPE)) = '' THEN 'zUnkown'
ELSE LTRIM(RTRIM(a.CUSTOMER_TYPE))
END AS Customer_Type
, CASE
WHEN a.CARE_OF_NAME = '' THEN 'zUnknown'
ELSE a.CARE_OF_NAME
END AS Care_of_Name
, LTRIM(RTRIM(a.ORIGIN_CITY )) AS Origin_City
, LTRIM(RTRIM(a.ORIGIN_STATE )) AS Origin_State
, LTRIM(RTRIM(a.DESTINATION_CITY )) AS Destination_City
, LTRIM(RTRIM(a.DESTINATION_STATE )) AS Destination_State
, LTRIM(RTRIM(b.BusinessGroup_TX )) AS BusinessGroup_TX
, b.Fleet_TX AS Fleet_TX
, c.Leg_TX AS Leg_TX
FROM zt_Arrival_Data a
INNER JOIN zt_Tpl_Tuple_Stats_2 b
ON LTRIM(RTRIM(a.ORIGIN_CITY)) + ', ' + LTRIM(RTRIM(a.ORIGIN_STATE)) = b.ORIGIN_TX
AND LTRIM(RTRIM(a.DESTINATION_CITY)) + ', ' + LTRIM(RTRIM(a.DESTINATION_STATE)) = b.DESTINATION_TX
AND a.CUSTOMER_NO = b.CUSTOMER_CD
AND a.BUSINESS_GROUP = b.BusinessGroup_TX
AND a.[FLEET_ID (GEN PLANT)] = b.Fleet_TX
JOIN zt_LegMap c ON c.Leg_CD = b.Leg_CD
It is far better to trim the data on data entry where it only has to happen once than to do this sort of thing against a large table in a select.
It is an especially bad design that you you have to concatenate in order to join. You lose the ability to use indexes when you do these things. In SQL Server I would create a calculated persisted column that Ci could join on instead, not sure if mysql has such things. But you should investigate doing this.
From my experience I've learned that when you need to format fields in order to join the tables you should format the columns of the table which has LESS rows to match the one with MORE rows, which must be compared unaltered.
Some idea to start with:
FROM zt_Arrival_Data a
INNER JOIN zt_Tpl_Tuple_Stats_2 b
ON a.ORIGIN_CITY = <format the b table columns to match a.ORIGIN_CITY>
AND a.DESTINATION_STATE = <format the b table columns to match a.DESTINATION_STATE>
AND a.DESTINATION_CITY = <format the b table columns to match a.DESTINATION_CITY>
AND a.ORIGIN_STATE = <format the b table columns to match a.ORIGIN_STATE>
AND a.CUSTOMER_NO = b.CUSTOMER_CD
AND a.BUSINESS_GROUP = b.BusinessGroup_TX
AND a.[FLEET_ID (GEN PLANT)] = b.Fleet_TX

MYSQL output in tree format OR Adding level (Parent-Child)

Below is what I have in my table.
myTable
++++++++++++++++++++
Parent + Child
++++++++++++++++++++
C1 + G1
C1 + G2
C1 + G3
G3 + D1
G3 + D2
C1 + G4
G4 + D3
G4 + D4
C2 + G5
C2 + G6
C2 + G7
C2 + G8
++++++++++++++++++++
What, I want is as below using MYSQL.
C1
G1
G2
G3
D1
D2
G4
D3
D4
C2
G5
G6
G7
G8
Please let me know if this is possible in MYSQL. The output is something like TREE.
Update 1
If I get new table like below is also fine so that I can use this example.
++++++++++++++++++++++++++++++++++++++++
Parent + Child + PLevel + CLevel
++++++++++++++++++++++++++++++++++++++++
C1 + G1 + 1 + 2
C1 + G2 + 1 + 2
C1 + G3 + 1 + 2
G3 + D1 + 2 + 3
G3 + D2 + 2 + 3
C1 + G4 + 1 + 2
G4 + D3 + 2 + 3
G4 + D4 + 2 + 3
C2 + G5 + 1 + 2
C2 + G6 + 1 + 2
C2 + G7 + 1 + 2
C2 + G8 + 1 + 2
++++++++++++++++++++++++++++++++++++++++
NOTE : I have started level with 1 (in example I have level starting from 0). If I get this new table with level starting from 0 is also fine.
Although you can't do with a single query, you can do with a stored procedure... The only pre-requirement, you need to add 2 more records to your existing sample table to represent that "C1" and "C2" ARE the top level... Add a record where the "Parent" field is blank, and the child level is "C1" and another for "C2". This will "prepare" the top-most parent level. for subsequent hierarchy association, otherwise you have no starting "basis" of the top-level hierarchy. It also requires a "primary key" column (which I've created in this script as "IDMyTable" which is just 1-x sequential, but would assume you have an auto-increment column on your table to use instead).
I've included all the output columns to show HOW it's built, but the premise of this routine is to create a table based on the expected column outputs, yet extra to hold the hierarchical representation downstream as it's being built. To MAKE SURE they retain the correct orientation as the layers get deeper, I'm concatinating the "ID" column -- you'll see how it works in the final result set.
Then, in the final result set, I am pre-padding spaces based on however deep the hierarchy data is.
The loop will add any records based on their parent being found in the preceding result set, but only if the ID has not already been added (prevent duplicates)...
To see how the cyclical order was constantly appended to, you can run the last query WITHOUT the order by and see how each iteration qualified and added the previous hierarchy level was applied...
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetHierarchy2`()
BEGIN
-- prepare a hierarchy level variable
set #hierlvl := 00000;
-- prepare a variable for total rows so we know when no more rows found
set #lastRowCount := 0;
-- pre-drop temp table
drop table if exists MyHierarchy;
-- now, create it as the first level you want...
-- ie: a specific top level of all "no parent" entries
-- or parameterize the function and ask for a specific "ID".
-- add extra column as flag for next set of ID's to load into this.
create table MyHierarchy as
select
t1.IDMyTable,
t1.Child AS Parent,
#hierlvl as IDHierLevel,
cast( t1.IDMyTable as char(100)) FullHierarchy
from
MyTable t1
where
t1.Parent is null
OR t1.Parent = '';
-- how many rows are we starting with at this tier level
set #lastRowCount := ROW_COUNT();
-- we need to have a "primary key", otherwise our UPDATE
-- statement will nag about an unsafe update command
alter table MyHierarchy add primary key (IDMyTable);
-- NOW, keep cycling through until we get no more records
while #lastRowCount > 0 do
-- NOW, load in all entries found from full-set NOT already processed
insert into MyHierarchy
select
t1.IDMyTable,
t1.Child as Parent,
h1.IDHierLevel +1 as IDHierLevel,
concat_ws( ',', h1.FullHierarchy, t1.IDMyTable ) as FullHierarchy
from
MyTable t1
join MyHierarchy h1
on t1.Parent = h1.Parent
left join
MyHierarchy h2
on t1.IDMyTable = h2.IDMyTable
where
h2.IDMyTable is null;
set #lastRowCount := row_count();
-- now, update the hierarchy level
set #hierLevel := #hierLevel +1;
end while;
-- return the final set now
select
*, concat( lpad( ' ', 1 + (IDHierLevel * 3 ), ' ' ), Parent ) as ShowHierarchy
from MyHierarchy
order by FullHierarchy;
END
MySQL and RDBMS's in general are not great at this sort of structure. You'll probably have to use client-side recursion to do this.
If the recursion is limited to just three deep, like your example, you can do it with joins, but it's not very scalable for deeper trees.
first Create Recursive function for calc level.
function fn_CalcLevel(int #ID)
As Begin
Declare #ParentID int
Select #ParentID = ParentID From Table1 where ID = #ID
IF (#ParentID IS NULL) Return 1 Else Return 1+fn_CalcLevel(#ParentID)
End
then Create your Query Such as Below
Select *, fn_CalcLevel(Table1.ID) as Level
From Table1
If you restructured your table a bit you could use something like:
SELECT Child,CONCAT(LPAD('',Clevel,' '),Child),etc from tablename
The restructuring is that you would need the root node in as a row with parent node of 0. You can add your own ordering with parent / child / C level to get the sequence as desired.
I know this is from a few years back, but it might save someone else some effort!

Why is my CAST returning null/nothing?

When I attempt to cast my FLOATS into CHARS in this procedure, I get null values in the database. Location is a Geospatial field. What am I doing wrong?
CREATE DEFINER=`me`#`%` PROCEDURE `UpdateLocationByObjectId`(IN objectId INT,
IN latitude FLOAT,
IN longitude FLOAT)
BEGIN
UPDATE Positions P
JOIN Objects O ON P.Id = O.PositionId
SET P.Location = GeomFromText('Point(' + CAST(latitude AS CHAR(10)) + ' ' + CAST(longitude AS CHAR(10)) +')')
WHERE O.ObjectId = objectId;
END
If I use this as a test, it works fine.
CREATE DEFINER=`me`#`%` PROCEDURE `UpdateLocationByObjectId`(IN objectId INT,
IN latitude FLOAT,
IN longitude FLOAT)
BEGIN
UPDATE Positions P
JOIN Objects O ON P.Id = O.PositionId
SET P.Location = GeomFromText('Point(10 10')')
WHERE O.ObjectId = objectId;
END
Change this line
SET P.Location = GeomFromText('Point(' + CAST(latitude AS CHAR(10)) + ' '
+ CAST(longitude AS CHAR(10)) +')')
To
SET P.Location = GeomFromText(concat('Point(' , CAST(latitude AS CHAR(10)) , ' '
, CAST(longitude AS CHAR(10)) ,')'))
The + operator is adding your text values ('10' + '10') = 20
So the center part evaluates to 'Point(' + 20 + ')', adding text that cannot be read as number + numbers evaluates to NULL.
Only the concat function can concatenate strings.
In fact this code will work just as well:
SET P.Location = GeomFromText(concat('Point(', latitude, ' ', longitude,')'))

Questions on SQL Server 2008 Full-Text Search

I have some questions about SQL 2K8 integrated full-text search.
Say I have the following tables:
Car with columns: id (int - pk), makeid (fk), description (nvarchar), year (int), features (int - bitwise value - 32 features only)
CarMake with columns: id (int - pk), mfgname (nvarchar)
CarFeatures with columns: id (int - 1, 2, 4, 8, etc.), featurename (nvarchar)
If someone searches "red honda civic 2002 4 doors", how would I parse the input string so that I could also search in the "CarMake" and "CarFeatures" tables?
Trying to parse search criteria like that will be a pain. A possible alternate solution would be to create a view that creates a long description of the car and create a full text index on that. So that view might look like:
Create View dbo.CarData
WITH SCHEMABINDING
As
Select dbo.Cars.Id
, dbo.CarMake.Manufactuer
+ ' ' + dbo.Cars.[Year]
+ Coalesce(' ' + dbo.Cars.Description,'')
+ ' ' + Case When Features & 1 <> 0 Then (Select Name From dbo.CarFeature Where Id = 1) Else '' End
+ ' ' + Case When Features & 2 <> 0 Then (Select Name From dbo.CarFeature Where Id = 2) Else '' End
+ ' ' + Case When Features & 4 <> 0 Then (Select Name From dbo.CarFeature Where Id = 4) Else '' End
+ ' ' + Case When Features & 8 <> 0 Then (Select Name From dbo.CarFeature Where Id = 8) Else '' End
+ ' ' + Case When Features & 16 <> 0 Then (Select Name From dbo.CarFeature Where Id = 16) Else '' End As Description
From dbo.Cars
Join dbo.CarMake
On CarMake.Id = Cars.MakeId
With a fulltext index on that view, then you might be able to take your search criteria and do:
Select ...
From CarData
Where Contains(Description, Replace('red honda civic 2002 4 doors', ' ', ' AND '))
Now, this is far from perfect. For example, it will result in '...4 AND doors' and thus find car models in 2004 with 2 doors or 4WD and 2 doors. In addition, I did not see color in your schema so I'm not sure how that would get into the mix.
It would obviously be substantially simpler to force the user to break up the search criteria into its constituent pieces instead of trying to implement a Google-like search. Thus, you would restrict the user to selecting the color from a drop list, selecting the make from another drop list and so on. If you did this, then you wouldn't need the above mentioned View and could instead query against the columns in the tables.
Btw, the features column being a bitwise value makes searches more of a pain as you will need to do a bitwise AND operation on each value to determine if it has the feature in question. It would be better to break out the Feature to Car mapping into a separate table.