Related
Say I have a table which has two columns i.e. Quantity and Percentages where my percentages are in decimals. Now I want to multiply these two columns and Round the value down to 2 decimals. Rounding down here means that all the numbers from 1-9 are rounded down. Is there an inbuilt function in SQL to do so as there is in Excel?
Examples:
13.567 should round to 13.56
136.7834 should round to 136.78
0.7699 should round to 0.76
I have tried searching for such a function online but couldn't come across an appropriate solution.
There's a FLOOR function, which can be adapted to your use case:
SELECT FLOOR(value * 100) / 100 AS RoundedValue
You can use TRUNCATE () for this rounddown
select TRUNCATE(2.847, 2) as rounddown
or
SELECT Floor(135.675); //for integer rounding, like 135
You can also use
select round(123.456, 2, 1) as rounddown
The 3rd parameter being non-zero will cause a truncation after the number of decimal points specified in the 2nd parameter.
DB Fiddle
https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/sqlref/src/tpc/db2z_bif_truncate.html
https://www.w3schools.com/sql/func_sqlserver_floor.asp
The solution to the problem is to truncate the extra decimal which can be achieved by using the extra parameter of the ROUND function which is ROUND(number, decimal_places, 0/1). Here if the last parameter is anything other than 0, it will truncate the rather than rounding off which is equivalent to the ROUNDDOWN() function of excel that I was looking for.
Alternatively, you can use the TRUNCATE() function, passing the number of decimal places to keep as the second parameter, which will drop off any extra decimals, acting as a ROUNDDOWN() function.
I hope this rounding utility helps somebody:
CREATE FUNCTION `get_round`(val DOUBLE, nDigits INT, RoundStyle VARCHAR(255)) RETURNS double
NO SQL
BEGIN
DECLARE a DOUBLE DEFAULT 0;
SET nDigits = ifnull(nDigits, 0);
CASE
WHEN UCASE(RoundStyle) IN ('ROUND NEAREST','', 'NEAREST', '', 'RND','ROUND', 'DEFAULT','DFLT', null) THEN #normal rounding, but up from 10.50#
SET a = round(val, nDigits);
WHEN UCASE(RoundStyle) IN('ROUND UP', 'UP') THEN #ROUND 10.554 to 10.56
SET a = ceil(val * (power(10, nDigits) )) / (power(10, nDigits));
WHEN UCASE(RoundStyle) IN('ROUND DOWN', 'DOWN') THEN #ROUND 10.555 to 10.55
SET a = truncate(val, nDigits) ;
WHEN UCASE(RoundStyle) IN('ROUND BANKER', 'BANKER','BANKERS ROUNDING') THEN #ROUND TO THE NEAREST EVEN 10.555 is 10.56 and 10.565 is 10.56
SET a = IF(ABS(val - TRUNCATE(val, nDigits)) * POWER(10, nDigits + 1) = 5
AND NOT CONVERT(TRUNCATE(ABS(val) * POWER(10, nDigits), 0), UNSIGNED) % 2 = 1,
TRUNCATE(val, nDigits), ROUND(val, nDigits));
WHEN UCASE(RoundStyle) IN('ROUND UP INTEGER', 'INT UP','UP INT') THEN #10.4 rounds to 11.0
SET a = ceiling(val);
WHEN UCASE(RoundStyle) IN('ROUND DOWN INTEGER', 'INT DOWN','DOWN INT') THEN #10.6 rounds to 10.0
SET a = floor(val);
END CASE;
RETURN ifnull(a, 0);
END
yes there are some Function in sql for round
ex:
SELECT ProductName, Price, FlOOR(Price) AS RoundedPrice
FROM Products;
I need to mask integer field in mysql such that 9999911111 becomes 9900001111. I want to keep first 2 digits and last 4 digits and need to mark rest of the digits as 0 for the integers stored in the field.
I have created a query and it's working but I am not sure whether this is right way to do for integers or not.
update table_name
set field_name=CONCAT(SUBSTR(field_name, 1, 2),
REPEAT('0', CHAR_LENGTH(field_name) - 6),
SUBSTR(field_name, CHAR_LENGTH(field_name)-3, CHAR_LENGTH(field_name)));
Just trying a different approach .
SET #myVar = 344553543534;
SELECT #myVar - (SUBSTRING(#myVar, 4, LENGTH(#myVar) - 7) * 10000) ;
Above mentioned formula will give 344000003534 as the result. Tried with different combination and found it working.
So your query need to change as given below
UPDATE table_name
SET field_name=
(field_name - (SUBSTRING(field_name, 4, LENGTH(field_name) - 7) * 10000));
Explanation :
Consider Number, a = 344553543534;
Expected Result, b = 344000003534;
c = (a - b) = 344553543534 - 344000003534 = 553540000;
Now if you consider the result, c, 55354 is the numbers where masking required, and 0000 indicates the last 4 number to be left open.
So to get masked value, we can use the formula, b = a - c;
So now to get c, used SUBSTRING(a, 4, LENGTH(a) - 7) * 10000
EDIT : To keep only first two numbers, use 3 instead of 4 and 6 instead of 7. I assumed that you needed to keep first 3.
SET #myVar = 344553543534;
SELECT #myVar - (SUBSTRING(#myVar, 3, LENGTH(#myVar) - 6) * 10000) ;
I have a table that contains color options for a product. The color options include a hex color code, which is used to generate the UI (HTML).
I would like to sort the rows so that the colors in the UI look like a rainbow, instead of the current order that sorts based off of the Name of the color (not very useful).
Here is what my query looks like. I get the R G B decimal values from the hex code. I just don't know how to order it.
I've looked into color difference algorithms. They seem more useful to compare 2 colors' similarity, not sort.
I'm using MySQL:
select a.*, (a.c_r + a.c_g + a.c_b) color_sum
from (
select co.customization_option_id,
co.designer_image_url,
concat(co.name, " (",cog.name, ")") name,
co.customization_option_group_id gr,
designer_hex_color,
conv(substr(designer_hex_color, 1, 2), 16, 10) c_r,
conv(substr(designer_hex_color, 3, 2), 16, 10) c_g,
conv(substr(designer_hex_color, 5, 2), 16, 10) c_b
from customization_options co
left join customization_option_groups cog
on cog.id = co.customization_option_group_id
where co.customization_id = 155
and co.customization_option_group_id
in (1,2,3,4)) a
order by ????
You want to sort hex codes by wavelength, this roughly maps onto the hue-value. Given a hexcode as a six character string: RRGGBB.
You just need to make a function that takes in a hexcode string and outputs the hue value, here's the formula from this Math.SO answer:
R' = R/255
G' = G/255
B' = B/255
Cmax = max(R', G', B')
Cmin = min(R', G', B')
Δ = Cmax - Cmin
I wanted to see if this would work, so I whipped up a sample program in Ruby, it samples 200 random colors uniformly from RGB-space, and sorts them, the output looks like a rainbow!
Here's the Ruby source:
require 'paint'
def hex_to_rgb(hex)
/(?<r>..)(?<g>..)(?<b>..)/ =~ hex
[r,g,b].map {|cs| cs.to_i(16) }
end
def rgb_to_hue(r,g,b)
# normalize r, g and b
r_ = r / 255.0
g_ = g / 255.0
b_ = b / 255.0
c_min = [r_,g_,b_].min
c_max = [r_,g_,b_].max
delta = (c_max - c_min).to_f
# compute hue
hue = 60 * ((g_ - b_)/delta % 6) if c_max == r_
hue = 60 * ((b_ - r_)/delta + 2) if c_max == g_
hue = 60 * ((r_ - g_)/delta + 4) if c_max == b_
return hue
end
# sample uniformly at random from RGB space
colors = 200.times.map { (0..255).to_a.sample(3).map { |i| i.to_s(16).rjust(2, '0')}.join }
# sort by hue
colors.sort_by { |color| rgb_to_hue(*hex_to_rgb(color)) }.each do |color|
puts Paint[color, color]
end
Note, make sure to gem install paint to get the colored text output.
Here's the output:
It should be relatively straight-forward to write this as a SQL user-defined function and ORDER BY RGB_to_HUE(hex_color_code), however, my SQL knowledge is pretty basic.
EDIT: I posted this question on dba.SE about converting the Ruby to a SQL user defined function.
This is based on the answer by #dliff. I initially edited it, but it turns out my edit was rejected saying "it should have been written as a comment or an answer". Seeing this would be too large to post as a comment, here goes.
The reason for editing (and now posting) is this: there seems to be a problem with colors like 808080 because their R, G and B channels are equal. If one needs this to sort or group colors and keep the passed grayscale/non-colors separate, that answer won't work, so I edited it.
DELIMITER $$
DROP FUNCTION IF EXISTS `hex_to_hue`$$
CREATE FUNCTION `hex_to_hue`(HEX VARCHAR(6)) RETURNS FLOAT
BEGIN
DECLARE r FLOAT;
DECLARE b FLOAT;
DECLARE g FLOAT;
DECLARE MIN FLOAT;
DECLARE MAX FLOAT;
DECLARE delta FLOAT;
DECLARE hue FLOAT;
IF(HEX = '') THEN
RETURN NULL;
END IF;
SET r = CONV(SUBSTR(HEX, 1, 2), 16, 10)/255.0;
SET g = CONV(SUBSTR(HEX, 3, 2), 16, 10)/255.0;
SET b = CONV(SUBSTR(HEX, 5, 2), 16, 10)/255.0;
SET MAX = GREATEST(r,g,b);
SET MIN = LEAST(r,g,b);
SET delta = MAX - MIN;
SET hue=
(CASE
WHEN MAX=r THEN (60 * ((g - b)/delta % 6))
WHEN MAX=g THEN (60 * ((b - r)/delta + 2))
WHEN MAX=b THEN (60 * ((r - g)/delta + 4))
ELSE NULL
END);
IF(ISNULL(hue)) THEN
SET hue=999;
END IF;
RETURN hue;
END$$
DELIMITER ;
Again, I initially wanted to edit the original answer, not post as a separate one.
If your products can have lots of color probably a good UI will require a color picker, normally those are rectangular, so not really something possible with the order by.
If the products have a manageable number of colors you have different choice, the easiest to implement is an order table, where for every possible color is defined an order position, this table can then be joined to your query, something like
SELECT ...
FROM (SELECT ...
...
...
, ci.color_order
FROM customization_options co
LEFT JOIN customization_option_groups cog
ON cog.id = co.customization_option_group_id
LEFT JOIN color_ ci
ON designer_hex_color = ci.color
WHERE ...) a
ORDER BY color_order
Another way to go is to transform the RGB color to hue and use this as the order.
There are different formula for this conversion, depending on wich order you want the primary color to have, all of them can be found on the wikipedia page for hue, I can update the answer to help you convert one of those to T-SQL, if needed.
MySQL function Hex to Hue. Based on Tobi's answer. :)
CREATE FUNCTION `hex_to_hue`(hex varchar(6)) RETURNS float
BEGIN
declare r float;
declare b float;
declare g float;
declare min float;
declare max float;
declare delta float;
declare hue float;
set r = conv(substr(hex, 1, 2), 16, 10)/255.0;
set g = conv(substr(hex, 3, 2), 16, 10)/255.0;
set b = conv(substr(hex, 5, 2), 16, 10)/255.0;
set max = greatest(r,g,b);
set min = least(r,g,b);
set delta = max - min;
set hue=
(case
when max=r then (60 * ((g - b)/delta % 6))
when max=g then (60 * ((b - r)/delta + 2))
when max=b then (60 * ((r - g)/delta + 4))
else null
end);
RETURN hue;
END
I am redesigning a customer database and one of the new pieces of information I would like to store along with the standard address fields (Street, City, etc.) is the geographic location of the address. The only use case I have in mind is to allow users to map the coordinates on Google maps when the address cannot otherwise be found, which often happens when the area is newly developed, or is in a remote/rural location.
My first inclination was to store latitude and longitude as decimal values, but then I remembered that SQL Server 2008 R2 has a geography data type. I have absolutely no experience using geography, and from my initial research, it looks to be overkill for my scenario.
For example, to work with latitude and longitude stored as decimal(7,4), I can do this:
insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest
but with geography, I would do this:
insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest
Although it's not that much more complicated, why add complexity if I don't have to?
Before I abandon the idea of using geography, is there anything I should consider? Would it be faster to search for a location using a spatial index vs. indexing the Latitude and Longitude fields? Are there advantages to using geography that I am not aware of? Or, on the flip side, are there caveats that I should know about which would discourage me from using geography?
Update
#Erik Philips brought up the ability to do proximity searches with geography, which is very cool.
On the other hand, a quick test is showing that a simple select to get the latitude and longitude is significantly slower when using geography (details below). , and a comment on the accepted answer to another SO question on geography has me leery:
#SaphuA You're welcome. As a sidenote be VERY carefull of using a
spatial index on a nullable GEOGRAPHY datatype column. There are some
serious performance issue, so make that GEOGRAPHY column non-nullable
even if you have to remodel your schema. – Tomas Jun 18 at 11:18
All in all, weighing the likelihood of doing proximity searches vs. the trade-off in performance and complexity, I've decided to forgo the use of geography in this case.
Details of the test I ran:
I created two tables, one using geography and another using decimal(9,6) for latitude and longitude:
CREATE TABLE [dbo].[GeographyTest]
(
[RowId] [int] IDENTITY(1,1) NOT NULL,
[Location] [geography] NOT NULL,
CONSTRAINT [PK_GeographyTest] PRIMARY KEY CLUSTERED ( [RowId] ASC )
)
CREATE TABLE [dbo].[LatLongTest]
(
[RowId] [int] IDENTITY(1,1) NOT NULL,
[Latitude] [decimal](9, 6) NULL,
[Longitude] [decimal](9, 6) NULL,
CONSTRAINT [PK_LatLongTest] PRIMARY KEY CLUSTERED ([RowId] ASC)
)
and inserted a single row using the same latitude and longitude values into each table:
insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)
Finally, running the following code shows that, on my machine, selecting the latitude and longitude is approximately 5 times slower when using geography.
declare #lat float, #long float,
#d datetime2, #repCount int, #trialCount int,
#geographyDuration int, #latlongDuration int,
#trials int = 3, #reps int = 100000
create table #results
(
GeographyDuration int,
LatLongDuration int
)
set #trialCount = 0
while #trialCount < #trials
begin
set #repCount = 0
set #d = sysdatetime()
while #repCount < #reps
begin
select #lat = Location.Lat, #long = Location.Long from GeographyTest where RowId = 1
set #repCount = #repCount + 1
end
set #geographyDuration = datediff(ms, #d, sysdatetime())
set #repCount = 0
set #d = sysdatetime()
while #repCount < #reps
begin
select #lat = Latitude, #long = Longitude from LatLongTest where RowId = 1
set #repCount = #repCount + 1
end
set #latlongDuration = datediff(ms, #d, sysdatetime())
insert into #results values(#geographyDuration, #latlongDuration)
set #trialCount = #trialCount + 1
end
select *
from #results
select avg(GeographyDuration) as AvgGeographyDuration, avg(LatLongDuration) as AvgLatLongDuration
from #results
drop table #results
Results:
GeographyDuration LatLongDuration
----------------- ---------------
5146 1020
5143 1016
5169 1030
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152 1022
What was more surprising is that even when no rows are selected, for example selecting where RowId = 2, which doesn't exist, geography was still slower:
GeographyDuration LatLongDuration
----------------- ---------------
1607 948
1610 946
1607 947
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608 947
If you plan on doing any spatial computation, EF 5.0 allows LINQ Expressions like:
private Facility GetNearestFacilityToJobsite(DbGeography jobsite)
{
var q1 = from f in context.Facilities
let distance = f.Geocode.Distance(jobsite)
where distance < 500 * 1609.344
orderby distance
select f;
return q1.FirstOrDefault();
}
Then there is a very good reason to use Geography.
Explanation of spatial within Entity Framework.
Updated with Creating High Performance Spatial Databases
As I noted on Noel Abrahams Answer:
A note on space, each coordinate is stored as a double-precision floating-point number that is 64 bits (8 bytes) long, and 8-byte binary value is roughly equivalent to 15 digits of decimal precision, so comparing a decimal(9,6) which is only 5 bytes, isn't exactly a fair comparison. Decimal would have to be a minimum of Decimal(15,12) (9 bytes) for each LatLong (total of 18 bytes) for a real comparison.
So comparing storage types:
CREATE TABLE dbo.Geo
(
geo geography
)
GO
CREATE TABLE dbo.LatLng
(
lat decimal(15, 12),
lng decimal(15, 12)
)
GO
INSERT dbo.Geo
SELECT geography::Point(12.3456789012345, 12.3456789012345, 4326)
UNION ALL
SELECT geography::Point(87.6543210987654, 87.6543210987654, 4326)
GO 10000
INSERT dbo.LatLng
SELECT 12.3456789012345, 12.3456789012345
UNION
SELECT 87.6543210987654, 87.6543210987654
GO 10000
EXEC sp_spaceused 'dbo.Geo'
EXEC sp_spaceused 'dbo.LatLng'
Result:
name rows data
Geo 20000 728 KB
LatLon 20000 560 KB
The geography data-type takes up 30% more space.
Additionally the geography datatype is not limited to only storing a Point, you can also store LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon and more. Any attempt to store even the simplest of Geography types (as Lat/Long) beyond a Point (for example LINESTRING(1 1, 2 2) instance) will incur additional rows for each point, a column for sequencing for the order of each point and another column for grouping of lines. SQL Server also has methods for the Geography data types which include calculating Area, Boundary, Length, Distances, and more.
It seems unwise to store Latitude and Longitude as Decimal in Sql Server.
Update 2
If you plan on doing any calculations like distance, area, etc, properly calculating these over the surface of the earth is difficult. Each Geography type stored in SQL Server is also stored with a Spatial Reference ID. These id's can be of different spheres (the earth is 4326). This means that the calculations in SQL Server will actually calculate correctly over the surface of the earth (instead of as-the-crow-flies which could be through the surface of the earth).
Another thing to consider is the storage space taken up by each method. The geography type is stored as a VARBINARY(MAX). Try running this script:
CREATE TABLE dbo.Geo
(
geo geography
)
GO
CREATE TABLE dbo.LatLon
(
lat decimal(9, 6)
, lon decimal(9, 6)
)
GO
INSERT dbo.Geo
SELECT geography::Point(36.204824, 138.252924, 4326) UNION ALL
SELECT geography::Point(51.5220066, -0.0717512, 4326)
GO 10000
INSERT dbo.LatLon
SELECT 36.204824, 138.252924 UNION
SELECT 51.5220066, -0.0717512
GO 10000
EXEC sp_spaceused 'dbo.Geo'
EXEC sp_spaceused 'dbo.LatLon'
Result:
name rows data
Geo 20000 728 KB
LatLon 20000 400 KB
The geography data-type takes up almost twice as much space.
CREATE FUNCTION [dbo].[fn_GreatCircleDistance]
(#Latitude1 As Decimal(38, 19), #Longitude1 As Decimal(38, 19),
#Latitude2 As Decimal(38, 19), #Longitude2 As Decimal(38, 19),
#ValuesAsDecimalDegrees As bit = 1,
#ResultAsMiles As bit = 0)
RETURNS decimal(38,19)
AS
BEGIN
-- Declare the return variable here
DECLARE #ResultVar decimal(38,19)
-- Add the T-SQL statements to compute the return value here
/*
Credit for conversion algorithm to Chip Pearson
Web Page: www.cpearson.com/excel/latlong.aspx
Email: chip#cpearson.com
Phone: (816) 214-6957 USA Central Time (-6:00 UTC)
Between 9:00 AM and 7:00 PM
Ported to Transact SQL by Paul Burrows BCIS
*/
DECLARE #C_RADIUS_EARTH_KM As Decimal(38, 19)
SET #C_RADIUS_EARTH_KM = 6370.97327862
DECLARE #C_RADIUS_EARTH_MI As Decimal(38, 19)
SET #C_RADIUS_EARTH_MI = 3958.73926185
DECLARE #C_PI As Decimal(38, 19)
SET #C_PI = pi()
DECLARE #Lat1 As Decimal(38, 19)
DECLARE #Lat2 As Decimal(38, 19)
DECLARE #Long1 As Decimal(38, 19)
DECLARE #Long2 As Decimal(38, 19)
DECLARE #X As bigint
DECLARE #Delta As Decimal(38, 19)
If #ValuesAsDecimalDegrees = 1
Begin
set #X = 1
END
Else
Begin
set #X = 24
End
-- convert to decimal degrees
set #Lat1 = #Latitude1 * #X
set #Long1 = #Longitude1 * #X
set #Lat2 = #Latitude2 * #X
set #Long2 = #Longitude2 * #X
-- convert to radians: radians = (degrees/180) * PI
set #Lat1 = (#Lat1 / 180) * #C_PI
set #Lat2 = (#Lat2 / 180) * #C_PI
set #Long1 = (#Long1 / 180) * #C_PI
set #Long2 = (#Long2 / 180) * #C_PI
-- get the central spherical angle
set #Delta = ((2 * ASin(Sqrt((power(Sin((#Lat1 - #Lat2) / 2) ,2)) +
Cos(#Lat1) * Cos(#Lat2) * (power(Sin((#Long1 - #Long2) / 2) ,2))))))
If #ResultAsMiles = 1
Begin
set #ResultVar = #Delta * #C_RADIUS_EARTH_MI
End
Else
Begin
set #ResultVar = #Delta * #C_RADIUS_EARTH_KM
End
-- Return the result of the function
RETURN #ResultVar
END
Like the title says, I'm trying to implement the programmatic parts of RFC4226 "HOTP: An HMAC-Based One-Time Password Algorithm" in SQL. I think I've got a version that works (in that for a small test sample, it produces the same result as the Java version in the code), but it contains a nested pair of hex(unhex()) calls, which I feel can be done better. I am constrained by a) needing to do this algorithm, and b) needing to do it in mysql, otherwise I'm happy to look at other ways of doing this.
What I've got so far:
-- From the inside out...
-- Concatinate the users secret, and the number of time its been used
-- find the SHA1 hash of that string
-- Turn a 40 byte hex encoding into a 20 byte binary string
-- keep the first 4 bytes
-- turn those back into a hex represnetation
-- convert that into an integer
-- Throw away the most-significant bit (solves signed/unsigned problems)
-- Truncate to 6 digits
-- store into otp
-- from the otpsecrets table
select (conv(hex(substr(unhex(sha1(concat(secret, uses))), 1, 4)), 16, 10) & 0x7fffffff) % 1000000
into otp
from otpsecrets;
Is there a better (more efficient) way of doing this?
I haven't read the spec, but I think you don't need to convert back and forth between hex and binary, so this might be a little more efficient:
SELECT (conv(substr(sha1(concat(secret, uses)), 1, 8), 16, 10) & 0x7fffffff) % 1000000
INTO otp
FROM otpsecrets;
This seems to give the same result as your query for a few examples I tested.
This is absolutely horrific, but it works with my 6-digit OTP tokens. Call as:
select HOTP( floor( unix_timestamp()/60), secret ) 'OTP' from SecretKeyTable;
drop function HOTP;
delimiter //
CREATE FUNCTION HOTP(C integer, K BINARY(64)) RETURNS char(6)
BEGIN
declare i INTEGER;
declare ipad BINARY(64);
declare opad BINARY(64);
declare hmac BINARY(20);
declare cbin BINARY(8);
set i = 1;
set ipad = repeat( 0x36, 64 );
set opad = repeat( 0x5c, 64 );
repeat
set ipad = insert( ipad, i, 1, char( ascii( substr( K, i, 1 ) ) ^ 0x36 ) );
set opad = insert( opad, i, 1, char( ascii( substr( K, i, 1 ) ) ^ 0x5C ) );
set i = i + 1;
until (i > 64) end repeat;
set cbin = unhex( lpad( hex( C ), 16, '0' ) );
set hmac = unhex( sha1( concat( opad, unhex( sha1( concat( ipad, cbin ) ) ) ) ) );
return lpad( (conv(hex(substr( hmac, (ascii( right( hmac, 1 ) ) & 0x0f) + 1, 4 )),16,10) & 0x7fffffff) % 1000000, 6, '0' );
END
//
delimiter ;